次の場合を想像してください:
1,000個のクライアントが「Somestuff」コレクションのコンテンツを表示するMeteorページに接続されています。
「Somestuff」は、1,000個のアイテムを保持するコレクションです。
誰かが「Somestuff」コレクションに新しいアイテムを挿入します
何が起こるか:
Meteor.Collection
sが更新されます。つまり、それらすべてに挿入が転送されます(つまり、1つの挿入メッセージが1,000クライアントに送信されたことを意味します)どのクライアントを更新する必要があるかを判断するためのサーバーのCPUのコストはいくらですか?
リスト全体ではなく、挿入された値のみがクライアントに転送されるのは正確ですか?
これは実際にどのように機能しますか?そのような規模のベンチマークや実験はありますか?
簡単な答えは、新しいデータのみがネットワークに送信されるということです。仕組みは次のとおりです。
Meteorサーバーには、サブスクリプションを管理する3つの重要な部分があります。publish function。サブスクリプションが提供するデータのロジックを定義します。 Mongoドライバー。データベースの変更を監視します。およびmerge box。これは、クライアントのすべてのアクティブなサブスクリプションを結合し、ネットワークを介してクライアントに送信します。
Meteorクライアントがコレクションをサブスクライブするたびに、サーバーはpublish関数を実行します。パブリッシュ機能の仕事は、クライアントが持つべきドキュメントのセットを把握し、各ドキュメントプロパティをマージボックスに送信することです。新しいサブスクライブクライアントごとに1回実行されます。 _this.userId
_を使用した任意の複雑なアクセス制御など、必要なJavaScriptをパブリッシュ関数に配置できます。パブリッシュ関数は、_this.added
_、_this.changed
_、および_this.removed
_を呼び出して、データをマージボックスに送信します。詳細については、 フルパブリッシュドキュメント を参照してください。
ただし、ほとんどの発行関数は、低レベルadded
、changed
、およびremoved
APIをいじる必要はありません。発行関数がMongoカーソルを返す場合、MeteorサーバーはMongoドライバーの出力(insert
、update
、およびremoved
コールバック)をマージボックスの入力(_this.added
_、_this.changed
_および_this.removed
_)。パブリッシュ関数ですべてのアクセス許可チェックを事前に実行し、ユーザーコードを邪魔することなく、データベースドライバーをマージボックスに直接接続できることは非常に便利です。また、自動公開がオンになっている場合、この少しでも非表示になります。サーバーは各コレクション内のすべてのドキュメントに対してクエリを自動的に設定し、それらをマージボックスにプッシュします。
一方、データベースクエリの公開に限定されません。たとえば、_Meteor.setInterval
_内のデバイスからGPS位置を読み取る、または別のWebサービスから従来のREST APIをポーリングする)発行関数を作成できます。低レベルのadded
、changed
、およびremoved
DDP APIを呼び出して、マージボックスに変更を送信します。
Mongoドライバーのジョブは、ライブクエリの変更についてMongoデータベースを監視することです。これらのクエリは継続的に実行され、added
、removed
、およびchanged
コールバックを呼び出して結果が変化すると更新を返します。
Mongoはリアルタイムデータベースではありません。したがって、ドライバーがポーリングします。アクティブなライブクエリごとに、最後のクエリ結果のメモリ内コピーを保持します。各ポーリングサイクルで、新しい結果を以前に保存された結果と比較し、違いを説明するadded
、removed
、およびchanged
イベントの最小セットを計算します。複数の呼び出し元が同じライブクエリのコールバックを登録する場合、ドライバーはクエリのコピーを1つだけ監視し、登録された各コールバックを同じ結果で呼び出します。
サーバーがコレクションを更新するたびに、ドライバーはそのコレクションの各ライブクエリを再計算します(Meteorの今後のバージョンは、更新時に再計算するライブクエリを制限するスケーリングAPIを公開します)。 Meteorサーバーをバイパスした帯域外データベース更新をキャッチします。
merge boxの役割は、次の結果(added
、changed
およびremoved
の呼び出し)を結合することですクライアントのすべてのアクティブな公開機能が単一のデータストリームになります。接続されたクライアントごとに1つのマージボックスがあります。クライアントのminimongoキャッシュの完全なコピーを保持します。
サブスクリプションが1つだけの例では、マージボックスは基本的にパススルーです。しかし、より複雑なアプリでは、重複する可能性のある複数のサブスクリプションを設定できます。 2つのサブスクリプションが同じドキュメントに同じ属性を設定している場合、マージボックスはどちらの値を優先するかを決定し、それをクライアントにのみ送信します。サブスクリプションの優先度を設定するためのAPIはまだ公開していません。現時点では、クライアントがデータセットをサブスクライブする順序によって優先順位が決定されます。クライアントが最初に作成するサブスクリプションの優先順位が最も高く、2番目のサブスクリプションが次に高くなります。
マージボックスはクライアントの状態を保持するため、パブリッシュ機能がどのようにフィードするかに関係なく、最小量のデータを送信して各クライアントを最新の状態に保つことができます。
これで、シナリオの準備が整いました。
1,000の接続されたクライアントがあります。それぞれが同じライブMongoクエリ(Somestuff.find({})
)にサブスクライブされます。クエリは各クライアントで同じであるため、ドライバーは1つのライブクエリのみを実行しています。アクティブなマージボックスは1,000個あります。そして、各クライアントのパブリッシュ関数は、マージボックスの1つにフィードするライブクエリにadded
、changed
、およびremoved
を登録しました。マージボックスには他に何も接続されていません。
まず、Mongoドライバー。クライアントの1つがSomestuff
に新しいドキュメントを挿入すると、再計算がトリガーされます。 Mongoドライバーは、Somestuff
内のすべてのドキュメントに対してクエリを再実行し、結果をメモリ内の前の結果と比較し、1つの新しいドキュメントがあることを見つけ、1,000個の登録済みinsert
コールバックのそれぞれを呼び出します。
次に、公開機能。ここではほとんど何も起きていません。1,000個のinsert
コールバックのそれぞれが、added
を呼び出してデータをマージボックスにプッシュします。
最後に、各マージボックスは、クライアントのキャッシュのメモリ内コピーに対してこれらの新しい属性をチェックします。いずれの場合も、値はまだクライアント上に存在せず、既存の値をシャドウイングしないことがわかります。そのため、マージボックスは、クライアントへのSockJS接続でDDP DATA
メッセージを送信し、サーバー側のメモリ内コピーを更新します。
合計CPUコストは、1つのMongoクエリを比較するコストに、クライアントの状態をチェックして新しいDDPメッセージペイロードを構築する1,000個のマージボックスのコストを加えたものです。ネットワーク上を流れる唯一のデータは、データベース内の新しいドキュメントに対応する1,000個のクライアントのそれぞれに送信される単一のJSONオブジェクトと、サーバーへの1つのRPCメッセージです元の挿入を行ったクライアントから。
これが間違いなく計画したものです。
より効率的なMongoドライバー。 ドライバーの最適化 0.5.1では、個別のクエリごとに1つのオブザーバーのみを実行します。
すべてのDBの変更がクエリの再計算をトリガーする必要はありません。いくつかの自動化された改善を行うことができますが、最良のアプローチは、開発者がどのクエリを再実行する必要があるかを指定できるAPIです。たとえば、1つのチャットルームにメッセージを挿入しても、2番目のルームのメッセージに対するライブクエリが無効にならないことは開発者にとって明らかです。
Mongoドライバー、公開機能、およびマージボックスは、同じプロセスで実行する必要はなく、同じマシン上で実行する必要もありません。一部のアプリケーションは、複雑なライブクエリを実行し、データベースを監視するためにより多くのCPUを必要とします。他には少数の個別のクエリしかありませんが(ブログエンジンを想像してください)、おそらく接続された多くのクライアント-これらはマージボックスにより多くのCPUを必要とします。これらのコンポーネントを分離すると、各ピースを個別にスケーリングできます。
多くのデータベースは、行が更新されたときに起動し、古い行と新しい行を提供するトリガーをサポートしています。この機能を使用すると、データベースドライバーは変更をポーリングする代わりにトリガーを登録できます。
私の経験から、Meteorで膨大なコレクションを共有しているときに多くのクライアントを使用することは、バージョン0.7.0.1の時点では基本的に実行不可能です。理由を説明しようと思います。
上記の投稿と https://github.com/meteor/meteor/issues/1821 で説明されているように、meteorサーバーは各クライアントの公開データのコピーを- マージボックス。これにより、Meteorマジックが発生しますが、ノードプロセスのメモリ内に大規模な共有データベースが繰り返し保持されます。 (流星が静的であることを示す方法はありますか(変更されることはありません)?)などの静的コレクションに対して可能な最適化を使用する場合でも、 NodeプロセスのCPUおよびメモリ使用量の問題。
このケースでは、完全に静的な15,000個のドキュメントのコレクションを各クライアントに公開していました。問題は、これらのドキュメントを接続時にクライアントのマージボックス(メモリ内)にコピーすると、基本的にNodeプロセスがほぼ1秒間100%CPUになり、メモリが大幅に追加使用されることです。これは本質的にスケーラブルではありません、接続しているクライアントはサーバーをひざまずかせ(同時接続はお互いをブロックします)、メモリ使用量はクライアントの数で直線的に増加します。 〜60MBメモリ使用量。ただし、転送された生データは約5MBでした。
私たちの場合、コレクションは静的であるため、nginxによってgzip圧縮された.json
ファイルとしてすべてのドキュメントを送信し、それらを匿名コレクションにロードすることでこの問題を解決しました。ノードプロセスにCPUやメモリを追加せず、ロード時間を大幅に短縮したデータ。このコレクションに対するすべての操作は、サーバー上のはるかに小さな出版物からの_id
sを使用して行われ、Meteorの利点のほとんどを保持することができました。これにより、アプリをより多くのクライアントに拡張できました。さらに、アプリは主に読み取り専用であるため、各Nodeインスタンスは単一であるため、複数のMeteorインスタンスを負荷分散(単一のMongoを使用)で実行することにより、スケーラビリティをさらに向上させました。 -スレッド。
ただし、複数のクライアント間で書き込み可能な大規模なコレクションを共有するという問題は、Meteorで解決する必要があるエンジニアリング上の問題です。おそらく、各クライアントのすべてのコピーを保持するよりも良い方法がありますが、それには分散システムの問題として真剣に考える必要があります。大量のCPUとメモリの使用に関する現在の問題は、スケールしません。
この質問に答えるために使用できる実験:
meteor create --example todos
WKIの使用方法に関するヒントについては、この 記事 を参照してください。少し古くなっていますが、特にこの質問に関しては、ほとんど有効です。
これはまだ1年前なので、「Meteor 1.0」以前の知識があると思います。私はまだこれを検討しています。 http://meteorhacks.com/does-meteor-scale.html は、「Meteorのスケーリング方法」につながります。記事 http://meteorhacks.com/how-to-scale-meteor.html