社内の宣言型データ読み込みシステム LunaDb をスケールアップ

Asana チーム 寄稿者の画像Arvind Vijayakumar
2026年3月5日
facebookx-twitterlinkedin
Gemini said Two female professionals collaborating at a desk in a bright, modern office. One woman sits while the other leans in, smiling and using the computer mouse, with a male colleague working in the background.

Asana では、ウェブアプリの根幹をなす LunaDb というデータ読み込みシステムを構築しました。 名前はデータベースですが、データベースではありません。 むしろ、宣言的にデータを取得するためのGraphQL のようなシステムであり、基本的に最新版のデータと今後のすべてのアップデートを読み込むための手段です。

LunaDb は、バックエンドインフラストラクチャ¹の大幅な書き換えとして、2015年に最初にリリースされました。 この新しいシステムの中心となるコンポーネントは、同期サーバーでした。これは、クライアントの同期からデータの読み込み、アクセス制御まで、すべてを実行するモノリスです。 大きな変更を加えることなく、この初期のアーキテクチャは当初の予想をはるかに超えて拡張し、週あたり数百万人のアクティブユーザーと日あたり数十億のクエリに対応するまでになりました。 

パフォーマンスは引き続き高水準でしたが、トラフィックと機能の複雑さが増すにつれ、同期サーバーの制約により、LunaDb の運用と改善がますます困難になってきました。

データ読み込みインフラストラクチャの概要

データ読み込みインフラストラクチャの概要

運用が困難だった理由

トラフィックの調整にはコストがかかる

同期サーバーは、クライアントへの永続的なウェブソケット接続を直接管理していました。 各 WebSocket はステートフルなクライアントセッションによってサポートされていました。 接続が切断されると、この状態はすべて破棄され、クライアントは必要なすべてのデータを再度サブスクライブすることになります。 多くのセッションでこれが起こると、すぐにコストが高くなります。 そのため、再接続による作業の大幅な増加を考慮して、これらの接続をシフトする際には注意が必要でした。

トラフィックの切り替え

トラフィックの切り替え

同期サーバーの展開はトラフィックの切り替えを意味する

もちろん、トラフィックのシフトを永久に防ぐことはできません。 新しいコードをプッシュしたり、スケールアップ / ダウンを行う場合は、同期サーバーを停止する必要があります。そのためには、終了するインスタンスからすべてのトラフィックをシフトする必要があります。

ローリングアップデート

ローリングアップデート

同期サーバーは起動時にはパフォーマンスが低い

同時に、同期サーバーは、かなりの量のプロセスウォーミングを行った後にのみ高いパフォーマンスを発揮します。 これらの問題の両方を管理することは、かなり微妙なバランスを保つ必要があり、これまで多大なエンジニアリング作業を必要としていました。

同期サーバーは複雑でモニタリングが困難

最後に、同期サーバーは (カスタムのサーバー側関数を介して) 多くの任意のプロダクトコードを実行していたため、ノイジーネイバーによるパフォーマンスの低下に非常に弱く、原因を特定することが困難でした²。 

ノイジー・ネイバー問題

ノイジー・ネイバー問題


「なぜ同期サーバーは複雑で、起動時のパフォーマンスが低いのでしょうか?」と疑問に思うのは当然です。

これらの問題の主な原因は、サーバー側の製品コードです。 同期サーバーコードの主な部分は Scala で記述されています。  セッション状態管理やLunaフレームワークのさまざまな側面に関連する複雑さがあるものの、このフレームワーク/プラットフォームコードはほとんどの場合、期待どおりに動作します(運用やパフォーマンスに関する問題は比較的少ないです)。 

一方、これらのプロダクトサーバー計算値 (SCV と呼びますが、カスタムリゾルバーと考えてください) は Typescript で記述されています。 両方のコードは、Truffle フレームワークを介して複数の言語を使用できるポリグロット VM である GraalVM で一緒に実行されます。 TypeScript で書かれているため、SCV は基本的に起動時にインタプリタで実行されます。その結果、パフォーマンスと CPU 使用率が許容範囲を超えることは予測可能です。 GraalVM は、呼び出された SCV に対してジャストインタイムコンパイルを実行しようとします。 これは良いことです! GraalVM/Truffleはパフォーマンスを大幅に最適化できますが、そうすることにはコストがかかります。 SCV のコンパイルは、CPU やコードキャッシュなどの面で非常にコストがかかる場合があります。 

ポリグロット VM のセットアップ

ポリグロット VM のセットアップ

2 つの言語を使用する理由

SCV の最初の設計は完全に Scala で行われました。 一方、当社のミューテーションおよび非同期ジョブシステムは JavaScript / TypeScript で書かれています。 Scala ベースの SCV は機能していましたが、ミューテーションジョブシステムと非同期ジョブシステムと LunaDb の間でビジネスロジックが重複していたこと、また製品エンジニアが Scala に慣れていなかったことが、製品の開発速度を著しく低下させる要因となりました。 

GraalVM を選ぶ理由

サブスクリプション結果の計算(および再計算)を高速化するために、プロセス内で大量のデータをキャッシュしています。 GraalVM を使用することで、Scala と TypeScript の部分を別々のコンテナに分割することで生じる可能性のある正確性やパフォーマンスの問題を回避しながら、これらのキャッシュを言語間で簡単に共有することができます。


改善が難しかった理由は?

サーバーは非常に多くの処理を行い、運用上比較的脆弱であったため、大規模な変更は避ける傾向がありました。 コードが複雑であることだけでなく、新しい変更を安全にロールアウトするためのオーバーヘッドが高いこともその理由です。

どのように解決するのか?

はい、質問するのが好きです

同期サーバーの運用と改善の難しさを考慮し、アーキテクチャを変更するという難しい決断を下しました。 主に、モノリシックな同期サーバーを 2 つの小さなコンポーネントに置き換えることにしました。

  • クライアント接続と状態解決を管理するセッションブローカー

  • データの読み込みを担当する同期可能なローダー。つまり、セッションブローカーからのクエリを処理するローダー

セッションブローカーと同期可能なローダー

セッションブローカーと同期可能なローダー

これが役立つ理由

この新しいアーキテクチャは、ウェブソケットトラフィックのシフト(セッションブローカーのデプロイ)と新しいプロセスのウォーミング(同期可能なローダーのデプロイ)を即座に分離します。 その結果、セッションブローカーとは別に同期可能なローダーをデプロイすることで、中断を最小限に抑えることができます。

これらの新しいコンポーネントはそれぞれシンプルです。

新しいアーキテクチャにより、製品開発プロセスを大幅に簡素化できます。 当初から、サーバー側の製品コードとクライアント側の製品コードの呼び出しの展開スケジュールが独立していたことが、作業速度の低下と (バージョンの非互換性による) 運用上の負担の原因となっていました。 製品コードを実行するプロセスは Syncable Loader だけとなり、そのデプロイが中断を引き起こすこともなくなったため、新しい製品コードをプッシュするたびに Syncable Loader を再デプロイできるようになりました。

この新しいアーキテクチャにより、ワークロードの種類ごとに異なる同期可能ローダーのプールをデプロイすることで、新機能へのスケールアップを改善できます (受信トレイ、タスク、目標などの異なる機能など)。 セッションブローカーは、データクエリが異なるアップストリームの Syncable Loader にどのようにルーティングされるかを直接制御できるサービスゲートウェイとして機能します。

デザインにおける主な課題は何か?

ありがとうございます。 この新しいアーキテクチャはずっと良さそうですが、どのように実現したのでしょうか? 同期サーバーは、複数の機能を包含しているという点で基本的にモノリスであり、モノリスを分割することはほとんどの場合困難です。 今回の場合、いくつかの主要な設計上の障害を克服する必要がありました。 

PubSub の分割

リアクティビティを実装するためのシステムである PubSub は、新しいデータを読み込み、クライアントに送信する役割を担う単一のプロセス (同期サーバー) を中心に設計されていました。 PubSub を再設計し、現在独立している 2 つのプロセスタイプ (同期可能なローダーとセッションブローカー) 全体で正確性を保証する必要がありました。 

同期サーバーでの実装方法を簡単にご紹介します。 ノート: を読むとよいでしょう。ただし、ここでは前提条件なしでシステムの概要を説明します。

同期サーバーでは、セッションごとにサブスクリプションを追跡します。 無効化パイプラインを使用して、サブスクリプションの更新を継続的にモニタリングしています。 新しい無効化メッセージが送信されるたびに、同期サーバーは影響を受けるすべてのサブスクリプションを再読み込みします。 

同期サーバーは、DBオブジェクト/クエリ結果、カスタムリゾルバ結果、以前のサブスクリプション結果を大量にキャッシュし、サブスクリプションの再読み込みを最適化します(すなわち、リードスルー パターンを使用します)。 キャッシュされたアーティファクトは、サブスクリプションに使用されるのと同じ無効化パイプラインによってパッシブに無効化されます。 キャッシュされたデータを使用しようとする際は常にその有効性を確認し、必要に応じて再計算にフォールバックします。

サブスクリプションの再読み込みとキャッシュデータの無効化との間には、明確な依存関係があることがわかります。 無効化を受信した際、キャッシュされたデータが無効化される前にサブスクリプションを再読み込みすると、古い結果が計算される可能性があります。 データの読み込みとサブスクリプションの管理の両方が同じプロセスで行われる場合、この依存関係を保証するのは簡単です。リロードする前にキャッシュを無効にするだけです。 

競合状態により、更新時に古いデータが生じる可能性があります

競合状態により、更新時に古いデータが生じる可能性があります

提案する新しいアーキテクチャでは、セッションブローカーと同期可能なローダーはどちらも無効化パイプラインの独立したコンシューマーです。 では、サブスクリプションが再読み込みされる前にキャッシュが無効化されるようにするにはどうすればよいでしょうか?

リクエストと応答のバージョン管理

無効化パイプラインでメッセージを同期して配信することで、この問題を解決できたかもしれません。 あるいは、無効化パイプラインのメッセージが、同期可能ローダーより前にセッションブローカーに到達しないようにする順序保証を実行するメカニズムを構築することもできました。 ただし、これらのソリューションにはどちらも理想的でないトレードオフがありました。特に、セッションブローカーと同期可能なローダーの結合が増加するという問題がありました。

代わりに、無効化ストリームにおける相対的な進捗に基づいて、リクエストとレスポンスのバージョンをデータ読み込みプロトコルに追加することで、この問題を解決しました。 ストリームはデータベースへの更新の全体的な順序を表すため、ストリームの進行状況はグローバルなバージョンカウンターのように使用できます。

リクエストと応答のバージョン

リクエストと応答のバージョン

無効化の再読み込み

同期サーバーは大量のデータを読み込みます。サイトのすべての読み取りの大部分を占めます。 当社の新しいアーキテクチャでは、セッションブローカーとSyncableローダーがネットワークを介して大量のデータを交換する必要があります。 新規サブスクリプションの場合、このネットワークオーバーヘッドは比較的無視できる程度です。 ただし、これは無効化による再読み込みでは特に非効率です。多くの場合、完全な応答を返す必要はなく、更新されたデータのみを返すことができます³。 特定のケースは特に悪いものです。 あるユーザーがプロジェクト内の 10,000 件のタスクをページングし、別のユーザーがこのプロジェクト内のタスクの説明を絶えず変更している場合を想像してみてください。無効化のたびにすべてのタスクを送信する必要があります! 更新されたデータのみを返すことが理想的であることは明らかですが、それを効率的に実装するにはどうすればよいでしょうか?

無効化の再読み込み

無効化の再読み込み

フィンガープリント

同期可能なローダーが更新されたデータのデルタを計算するには、リクエスターがすでに持っているデータを知る必要があります。 しかし、リクエストとともに最新のデータを渡すことは、完全な結果を返すのと同じくらいコストがかかります。  データをよりスペース効率の高い方法で表現する必要があります。

ハッシュは、スペースを節約するのに優れた手段です。 私たちが重視する細かいデータの各ビットは、syncable と呼ばれます。 シリアル化された各 Syncable の 128 ビットの Murmur ハッシュを計算し、フィンガープリントとして使用することができます⁴。 具体的には、このフィンガープリントは、そのバージョンの Syncable の識別子です。

Syncable データを追跡する場合は、そのフィンガープリントを代わりに使用できます。 これで、サブスクリプションの完全な応答を追跡したい場合、完全なデータを渡すことなく、一連のフィンガープリントを使用するだけで済みます。


補足: Syncable とは何か、サブスクリプションとの関係は?

Syncable は、サブスクリプションの結果の内容です。 サブスクリプションを読み込むと、結果は一連の Syncable として返されます。 具体的には、Syncable はオブジェクト、クエリ、または SCV 結果のいずれかになります。

サブスクリプションマッピングに同期可能

サブスクリプションマッピングに同期可能

各サブスクリプションは複数の Syncable にマッピングされることは明らかです。 ただし、Syncable は複数のサブスクリプションで共有できます (重複するデータを読み込む場合)。 したがって、サブスクリプションと Syncable の間には、実際には多対多のマッピングがあります。


セッションブローカーからの各リクエストとともに、これらのフィンガープリントのセットを渡します。 Syncable ローダーでは、完全な応答を計算し、そのフィンガープリントのセットを計算し、リクエストと重複するデータを除外し、デルタを返します。

どのように実現するか?

変更内容の大きさと重要性を考慮し、ロールアウトを大まかに 4 つの段階に分けました。 

ステージ 1 - モノリスのリファクタリング

  • 高度に結合されたセッション管理とデータ読み込みのコードを独立したコンポーネントに分割する

ステージ 2 - ローカル syncable-loader

  • 新しいデータ読み込みコンポーネントを使ってローカル gRPC サーバーを作成し、データ読み込みを移行する

ステージ 1~2

ステージ 3 - リモート syncable-loader

  • 新しい syncable-loader バイナリと展開を作成する

  • sync-server のすべてのデータ読み込みを、新しい syncable-loader 展開に移行する

ステージ 4 - 個別の session-broker バイナリ

  • 新しい session-broker バイナリと展開を作成

  • すべてのトラフィックを sync-server から session-broker に移行する

第 3~4 ステージ

どのような課題に直面したか?

非常に多くありました。 何から始めたらいいでしょうか?

大きな応答

すぐに遭遇した問題の 1 つは、応答サイズが大きいことでした。 同期サーバーへのデータの読み込みはすべて同じプロセス内で行われていたため、これまでは大きな問題ではなかった⁵。 しかし、ローカルの gRPC 境界を越えてデータの読み込みを開始すると、多くの問題が発生し始めました。 

一部の応答が大きい可能性は以前から予想していましたが、この問題を調査し始めたとき、本当に驚くべき結果が判明しました。 1 日に数千件の読み込みがあり、定期的に 100 MiB を超えていました! 単一の gRPC メソッドでは、物理的にそれほど大きな応答を返すことができませんでした (http2 の最大フレームサイズに達し始めるため)。 どうすればよいでしょうか?

この問題を体系的に解決する方法をいくつか検討しましたが、最終的には根本原因に対処する必要があると結論付けました。 gRPC サーバーサイドストリーミングを実装することもできましたが、シリアライズコストが急増し、ソケット競合が増加するため、レイテンシーとスループットに悪影響を及ぼす可能性が高かったのです。 これらの応答を却下することもできましたが、発生率が高すぎて、これは受け入れられませんでした。 

そこで、3 段階のアプローチを採用することにしました。まずはすべての大きな応答にマークを付け、問題のあるケースを分析してそれぞれ排除し、その後、応答サイズに厳格な上限を設けるというものです。

1MB を超えるすべてのロードにマークを付け、ソース、使用状況、データの内訳に関する詳細なイベントを記録しました。 コストのかかるユースケースはいくつかありましたが、最も悪名高かったのはおそらく添付ファイルのサムネイル BLOB です。 それらは base64 文字列としてエンコードされ、シリアル化された応答に含まれていたことがわかりました。 少数の場合は許容範囲内でしたが、大量に読み込むとすぐに巨大になりました。たとえば、各タスクの添付ファイルのサムネイルを表示するグリッドベースのビューを読み込む場合などです。

巨大な応答のサムネイルを制限し、小さなサムネイルを使用し、最終的にはバイナリデータを応答から削除することで、このような問題を徐々に修正することができました。 こうした簡単な対策を行った後、巨大な応答の数はなくなり、その後、フレームワークレベルの応答サイズ制限⁶を適用することができました。

トピックの衝突

もう 1 つの奇妙な問題は、Pubsub トピックの衝突でした。 ドメインに関係なく同じサブスクリプション トピックを生成する、コンプライアンスに準拠していないフレームワークの使用があったことが判明しました。 Pubsub が単一のプロセスタイプでのみ発生した場合、その影響は比較的軽微でした。 通常、1 つの Pubsub トピックは 1 つのドメインからのデータに対応します。 しかし、Pubsub がセッションブローカーと Syncable Loader に分割されたことで、2 つのプロセスタイプが特定のトピックのドメインについて意見を異にする可能性が生まれました。 このような状況では、この「ドメインの不一致」により、無効化による再読み込みの割合が安定して高くなることが確認されました。 幸い、修正はかなり簡単でしたが、このバグが検出されることなく、当社のフレームワークにこれほど長く存在し続けたことは興味深いことです。 

ワークロードの再調整

セッションブローカーと同期可能ローダーのワークロードは、元の同期サーバーのワークロードとは大きく異なります。 セッションブローカーはセッション管理のみを担当し、同期可能ローダーはデータの読み込みのみを担当します。 

これがリソース要件にどのように影響するかは正確にはわからなかったため、まずは両方に同様のリソース(CPU/メモリ)リクエストとHorizontal Pod Autoscaler(HPA)設定を適用しました。 

session-brokers

観察の結果、セッションブローカーの方がはるかに軽量であることが明らかになりました。 非常に低い CPU で確実に動作していました (むしろ、メモリの方がはるかに制約になっていました⁷)。 インフラストラクチャセル全体のトラフィックに対応するには、数個のレプリカで十分なようでした。 しかし、実際に HPA の minReplicas を減らしたところ、データのリロードとリロードの遅延が急増することがわかりました。 何が起こっていたのでしょうか?

要するに、キャッシュサイズやスロットラーに関するすべての共有設定を考慮することを怠っていたのです。 レプリカがごくわずかであるため、各セッションブローカーは、通常の同期サーバーよりもポッドあたりの処理するセッション数がはるかに多く (約 3.5 倍) なっていました。 それぞれがはるかに多くのデータを処理していたため、PubSub トピック ⇔ サブスクリプションのキャッシュが完全にいっぱいになり、各エビクションが (安全性のために) リロードをトリガーしていました。 このしきい値を約 6 倍に適切に増やすことで、キャッシュの削除率とリロード率の上昇が解消されました。 同様に、階層的なリロードスロットラーが、新しいトラフィックレートに対して非常に不適切に設定されていることがわかりました。 これらのスロットラー設定を適切なサイズに調整したところ、同様にリロードの遅延とエンドツーエンドの反応性の遅延 (つまり、ウェブアプリが自らの書き込みを確認するまでにかかる時間) が約 5~10 分の 1 という大幅な低減につながりました。

syncable-loaders

一方、同期可能なローダーは予想よりもかなり重くなっていました。 各サーバーは、同等のリソースを備えた同期サーバーよりも、1 秒あたりのサブスクリプションの読み込み数が多くなります (約 1.5 倍)。 セッションブローカーとは異なり、CPU への依存度がはるかに高かったのです⁸。

興味深いことに、CPU のかなりの部分は、TS SCV コードに関連する Truffle のデオプティマイゼーションの増加によるものでした。 これは、おそらく各Syncableローダーがより多くのSCVコードにアクセスしたことが原因でしょう。 いずれにせよ、コードキャッシュサイズ⁹をわずかに増やす必要がありました。 

プロセスウォーミング

データ読み込みのためのプロセスウォーミングは、これまで課題でした。 幸い、新しいアーキテクチャでは、少しシンプルになっています。 syncable-loader のメインインターフェイスはステートレスデータクエリです。そのため、セッションブローカーと syncable-loader 間の既存のトラフィックを再生またはミラーリングするだけでウォーミングを行うことができます。

一方で、sync-server のウォーミングと同じ課題の多くに依然として直面しています。 主な問題は、プロセスのウォーミングに多くの CPU が消費されることです。これにより、起動時にさまざまなノイジーネイバープロブレムが発生します (私たちの場合、k8s のスロットリング制限に達していないため、関連する指標は部分的なストールです)。 ここで行った素晴らしい改善は、インプレースの Pod サイズ変更を使用して、起動時の syncable-loader のリソースに上限を設け、起動後にバーストできるようにしたことです。 

それにもかかわらず、各Podのウォーミングには依然として数分かかります。 JFRプロファイルを見る限り、主なボトルネックはウォーミング中のTS SCVコードのコンパイル不足であると考えられ、そこにはさらに余裕があると考えています。 よりターゲットを絞った方法で関連するパスをより正確にウォーミングすること、およびコンパイルを改善するために TS インターフェイス¹⁰ を再構築することを積極的に検討しています。

セッションブローカーはデータの読み込みを担当しておらず、実際にはウォーミングの必要はまったくありませんでした。

どのように役立ちましたか?

新しいアーキテクチャにより、運用の複雑さが大幅に軽減され、展開とコードのスピードが向上し、将来のスピードとスケールアップの機会が生まれました。 

新しいアーキテクチャは、複雑なトラフィック管理を必要とせず、総読み取りトラフィックの変化に合わせて自動的にスケールします。 各種のトラフィックシフト操作は、Pod を再起動するだけで簡単に処理できます。 すべてのセッションを起動する必要がある場合は、 すべてのセッションブローカーを切り替えます。 キャッシュのクリアが必要ですか? すべての同期可能なローダーを切り替えます。 どちらの操作も、アップタイムに関しては安全です。

これまでは、製品開発の速度は主に同期サーバーのデプロイによってボトルネックが発生していました。 新しい世界では、製品コードをデプロイするには、同期可能なローダーをデプロイするだけで済みます。 これは、syncable-loader を独自のデプロイ可能なセルに移動することで実現しました。このセルは、より頻繁に (最終的には製品コードと一緒に) デプロイできるようにするための取り組み中です。 同期可能ローダーのデプロイは、すでに同期サーバーよりも約 40% 高速 (約 20 分高速) になっています (さらに高速化も安全に行えます)。今後は、さらに高速化を目指していきます。

なお、パフォーマンスの向上はこの作業の目標ではありませんでしたが、設計と実装にどれだけ関わったかを考えると、議論する価値があります。 新しいシステムでは、クエリ結果の計算の遅延が実際に改善されました (おそらく、負荷分散 / チューニング / オートスケーリングの改善によるものです)。 それに対応して、初期サブスクリプションのレイテンシーは著しく改善されています。 一方、エンドツーエンドのミューテーション反映の遅延 (変更が他のセッションに配信されるまでの時間) はほぼ同じです。 トレースによると、これはおそらく、セッションブローカーとsyncable-loader間のPubSubストリームの消費に歪みがあるためであると考えられます(syncable-loaderは、リクエストバージョンが最新になるまでリクエストに対応できません)。

今後の予定

現在のシステムは大幅に改善されていますが、リリースごとに下位互換性/上位互換性に関する考慮が必要となるため、製品の開発速度は依然として制限されています。 しかし、展開速度がすべて改善されたことで、データモデルの変更をすべて一緒に展開できるようになり、下位互換性や上位互換性について考慮する必要がなくなりました。 近い将来、これを実現するために積極的に検討しています。

この作業を行う主な動機の 1 つは、原因を特定するのが難しいパフォーマンスの低下でした。 特に、この作業の結果として大きな改善が見られなかった分野の 1 つです。 良い面としては、今後取り組む主な問題の一つになったことです。 ここで説明した作業の大部分とは異なり、これはかなり部門横断的な問題であり、製品ドメイン、データモデル、フレームワーク、インフラの考慮事項が関わっています。 

機能別の同期可能なローダーワーカープール

機能別の同期可能なローダーワーカープール

プラットフォームインフラストラクチャ (機能ごとのワーカープールなど)、プラットフォームフレームワーク、プラットフォームツール (ブラックボックス/ホワイトボックステストなど) にわたるソリューションの検討に取り組むことを楽しみにしています。


作成者のプロフィール

Arvind Vijayakumar は、LunaDb チームのエンジニアです。Asana のコアデータロードプラットフォームの構築とスケールアップを支援しています。このプラットフォームは、ユーザーが Asana のウェブアプリや API で常に正確で、反応が速く、超高速の更新を確実に確認できるようにする重要なインフラです。

チームへの感謝の言葉

LunaDb のスケールアップに向けたこの作業は、過去数年にわたる長期的なチームの取り組みであり、現在および過去の多くの LunaDb メンバーが関与しています。Brandon Zhang、Alex Matevish、Sean Wentzel、Eric Walton、Spencer Yu、Sophia Yao、George Ong、Tyler Prete、Koushik Ghosh、Natan Dubitski、Vinodh Chandra Murthy などです。


脚注

  1. 注目すべきは、Facebook が GraphQL をオープンソース化する前に開発したことです。

  2. プロセスウォーミングの詳細については、以前の投稿をご覧ください。要約すると、ローカルトラフィックの急増に迅速に対応するために、同期ポッドが任意でバーストできるようにすることに依存しています。

  3. また、フィールド単位ではなく、オブジェクト単位で更新を行っています (歴史的な理由により)。 これにより、無効化のリロードに伴うデータ量が増加します。

  4. 当社は衝突を避けるために 128 ビットの Murmur ハッシュを使用しています。

  5. 特に、私たちは非常に長い間 WebSocket 圧縮を使用しており、これによりクライアントが感じる影響が軽減されている可能性があります。

  6. また、当初はネットワークホップのオーバーヘッドに関する問題もいくつかありました。 調査の結果、オーバーヘッドのほとんどは実際にはサービスメッシュ (Istio/Envoy) に起因することが判明し、この点に焦点を当ててパフォーマンスを向上させるための調整を行いました。

  7. メモリの保持に関する重要な要因は、サーバー側の同期可能なストアを、データのハッシュのみを保持するように書き換えたことでした。 これは簡単な問題ではなかったので、これについてフォローアップの投稿を公開する可能性があります! これがなければ、保存されたクライアントデータにより、セッションブローカーはかなり多くのメモリを使用することになります。

  8. これ以前は、すべての同期サーバーがメモリ最適化されたインスタンスで実行されていました。 一方、同期可能なローダーは、混合インスタンスのような CPU リッチなノードタイプの方が有利です。

  9. この方法で GraalVM 上で TS を実行する際の注目すべき特性は、より標準的な Scala / Java JVM アプリケーションで一般的に使用されるよりも、かなり多くのコードキャッシュを使用することです。

  10. 私たちが知る限り、高い多形性により、TS コンパイルのコストが高くなります。 モノモーフィックインターフェイスへの変更と、レポートポリモーフィックスペシャライゼーションの活用を検討しています。

関連記事

エンジニアリング

How Asana makes me a more effective engineering manager