[Feature request] Please add synchronous access to http2 write
メンテナーはふだん 2 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 停滞
- 技術スタック
- nodejs, typescript
調査の方向性
packages/grpc-js/src/server-call.ts の ServerDuplexStreamImpl と、packages/grpc-js/src/server-interceptors.ts の sendMessage 実装から始めてください。既存の stream.write と http2Response.write のパスを比較し、エラーとストリームの状態をどのように処理すべきかも含めて、同期アクセスに必要な API と動作を判断してください。
索引モデルが issue の本文から書いたものです。
説明
Hi! Before I start asking I have to say that it was my pleasure to read through the code of grpc-node library. Splendid work, thank you!
Is your feature request related to a problem? Please describe.
tldr; We noticed that synchronous API is significantly faster in our scenario.
We have a grpc-js server with single BiDi Stream method:
message Request {
oneof payload {
HttpRequest http_request = 1; // plain http request Api Gateway got from user
BackendA backend_a_response = 2;
BackendB backend_b_response = 3;
// ... other backends
}
}
message Response {
optional Metadata metadata = 1; // Headers
optional string content = 2; // Response body. HTML
}
service Frontend {
rpc render (stream Request) returns (stream Response) {}
}
Our NodeJS backend receives chunks of data from Api Gateway. Once he got one chunk he starts an operation of a template rendering.
Once chunk's rendering is done server writes it into the grpc-js response stream. Sequence Diagram of the process if one is interested.
We noticed that this server is losing a lot of client speed to our old plain-http-1.1 server in the A/B experiment.
I was digging around about a week or two and made a conclusion that there is only one significant difference. Grpc-js is using streams as a proxy to http2.write instead of good old Response.write which our old server does.
Streams are asynchronous by their nature. Once one does write, actual writing will happen on nextTick as far I understand. So we have a situation when the writing operation stands aside of its corresponding rendering operation. Roughly something like this happens:
There are a couple of new operations before writing which were not presented before. In our case there are about 700 chunks and 100 rendering operations (one rendering operation can produce several chunks). And it makes delay: about 280ms in the 75 percentile (p75) and 211ms in p50 for Largest Content Paint. Our old website's LSP value is about 1000(p50)-1500ms(p75), so loosing 20% of speed is a lot. While in general LCP is a good metric for the web-sites we prefer to rely on Hero Element Rendering - we are tracking the most important element arriving time. So according this metric grpc-js is loosing about 370ms of p75 and 300ms of p50. Again it is about 20% degradation =(
Describe alternatives you've considered
I've been trying a lot of things during the last couple of weeks. First of all I've proven to myself and my colleagues using cpuprofiles and codes' listings that there is no other reason for degradation different from I described above.
The second thing I tried was chunk compressing using zlib and brotli. It was а failing effort. First of all zlib is making some cpu intensive work which delays the moment of chunk's readiness. However the most important thing that zlib is using streams too, so I got more of nextTicks and result was actually worse than without compression. So we went back to balancer's compression.
The last effort was successful but is a nasty one. I started using grpc-js private methods. Actually the most important one: sendMessage(https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/src/server-interceptors.ts#L837) which synchronously calls this.stream.write which is actually http2Response.write.
write(data: ResponseType, _: string = 'utf-8', callback: (error: null | undefined | Error) => void = () => {}) {
try {
this._sendMetaData();
// NASTY ONE!
this._call.call.sendMessage(data, callback as () => void);
return true;
} catch (error: any) {
// ANOTHER ONE. Actually it is a copy-paste from ServerDuplexStreamImpl
this.pendingStatus = serverErrorToStatus(error);
this.end();
return false;
}
}
And this effort made significant difference!
We deployed this version to the production yesterday (about 21:00 of our local time)
As one can see we win about 200-300ms per each metric (LCP and Hero Element). Now we are loosing about 50ms against the plain http version and it is ok. It is not so much. And we are ready that packing grpc-messages is not free. Also we have a new one proxy before our server.
Describe the solution you'd like
In general I want synchronous way of using http2.response. For example it can be a new method writeSync for ServerDuplexStreamImpl<RequestType, ResponseType> class.
Additional context
¯_(ツ)_/¯
Thanks for reading all of this! Ready to your questions
- 主要言語
- TypeScript
- スター
- 4.8k
- フォーク
- 717
- 平均マージ
- 1日 18時間
- マージ済み PR(30日)
- 17
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
grpc/grpc-node のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 2 日以内に返信
-
package: @grpc/grpc-js
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
grpc/grpc-node#2993 · コメント 3 件 · リアクション 4 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 65/100
grpc/grpc-node#3091 · リアクション 1 件 ·
メンテナーはふだん 2 日以内に返信
-
feature request
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
grpc/grpc-node#3077 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 76/100
grpc/grpc-node#3068 · コメント 2 件 · リアクション 1 件 ·
メンテナーはふだん 2 日以内に返信
似ている issue
-
area/frontend good first issue kind/cooldown
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
voidzero-dev/oxc-angular-compiler#511 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
langchain-ai/deepagentsjs#898 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 92/100
anomalyco/models.dev#8509 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
bug documentation P2 UI/UX
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信