1. ホーム
  2. benchmarking

[解決済み] 巨大なコレクションを多数のクライアントで共有する場合、Meteorはどの程度効率的か?

2022-12-29 09:15:55

質問

次のようなケースを想定してください。

  • 1,000人のクライアントが、"Somestuff"コレクションのコンテンツを表示するMeteorページに接続しています。

  • Somestuff"は、1,000のアイテムを保持するコレクションです。

  • 誰かが新しいアイテムを "Somestuff"コレクションに挿入します。

何が起こるか

  • すべて Meteor.Collection が更新されます。つまり、挿入メッセージはすべてのクライアントに転送されます(これは、1つの挿入メッセージが1,000のクライアントに送信されることを意味します)。

どのクライアントが更新される必要があるかをサーバーが判断するための CPU コストはいくらですか?

挿入された値のみがクライアントに転送され、リスト全体は転送されないというのは正確でしょうか?

実際のところ、これはどのように動作するのでしょうか? そのような規模のベンチマークや実験がありますか?

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

簡単な答えは、新しいデータだけが電線に送られるようにすることです。 以下は の仕組みです。

Meteorサーバーには、サブスクリプションを管理する3つの重要なパーツがあります。 サブスクリプションを管理するMeteorサーバの3つの重要な部分があります。 発行機能 これは、サブスクリプションが提供するデータのロジックを定義しています。 サブスクリプションが提供するデータに関するロジックを定義します。 Mongo ドライバ で、これは データベースの変更を監視する マージボックス は、クライアントのアクティブな購読をすべて結合し クライアントのアクティブなサブスクリプションをすべて結合し、ネットワーク経由でクライアントに送信します。 クライアントに送信します。

パブリッシュ機能

Meteorクライアントがコレクションを購読するたびに、サーバーは パブリッシュ関数 . publish関数の仕事は、クライアントが持つべきドキュメントの集合を見つけ出し クライアントが持つべきドキュメントの集合を見つけ出し、それぞれのドキュメントのプロパティ をマージボックスに送ることです。 これは、新しい購読クライアントごとに一度だけ実行されます。 あなたは のような、任意の JavaScript を発行関数に置くことができます。 を使った任意に複雑なアクセス制御 this.userId . 発行 関数は,マージボックスにデータを送るために this.added , this.changedthis.removed . を参照してください。 フルパブリッシュドキュメント を参照してください。 を参照してください。

ほとんどのパブリッシュ関数は、低レベルの added , changedremoved API を使っていますが。 publish関数がMongoの カーソルを返す場合、Meteorのサーバーは自動的にMongoの ドライバの出力を接続します ( insert , update そして removed コールバック) の入力に マージボックス ( this.added , this.changedthis.removed ). これはとても素晴らしいことです。 は、すべての権限チェックをパブリッシュ関数で前もって行い データベースドライバをマージボックスに直接接続することができます。 に直接接続できます。 そして、自動公開が有効な場合、この小さな部分さえも隠されています。 サーバーは自動的に各コレクションのすべてのドキュメントに対するクエリーをセットアップし、それらをマージボックスにプッシュします。 サーバーは自動的に各コレクションのすべてのドキュメントに対するクエリーをセットアップし、それらをマージボックスにプッシュします。

一方、データベースクエリのパブリッシュに限定されるわけではありません。 たとえば、GPS の位置を読み取る発行関数を書くことができます。 の中のデバイスから Meteor.setInterval の中にあるデバイスからGPS位置を読み取ったり、他のウェブサービスからレガシーREST APIをポーリングしたりするパブリッシュ関数を書くことができます。 を他のウェブサービスからポーリングすることができます。 そのような場合、低レベルの呼び出しによって を呼び出すことで、マージボックスに変更を加えます。 added , changedremoved DDP APIです。

Mongoのドライバ

このドライバは Mongo ドライバの の仕事は、Mongo データベースがライブクエリに変更されないか監視することです。 ライブクエリの変更を監視することです。 これらのクエリは継続的に実行され、結果が変わると次のように更新を返します。 を呼び出すことで、結果が変わるたびに更新を返します。 added , removed そして changed のコールバックがあります。

Mongoはリアルタイムのデータベースではありません。 そのため、ドライバはポーリングします。 ドライバは の最後のクエリ結果をインメモリに保存します。 ポーリングサイクルごとに ポーリングサイクルごとに、新しい結果と前回保存した結果を比較します。 の最小集合を計算します。 added , removed そして changed というイベントがあり、その差分を記述します。 複数の呼び出し元が同じライブクエリに対して 同じライブクエリのコールバックを登録した場合、ドライバはクエリのコピーを1つだけ監視し、登録されたコールバックを同じ結果で呼び出します。 を監視し、登録された各コールバックを同じ結果で呼び出します。

サーバーがコレクションを更新するたびに、ドライバはそのコレクションに対するライブクエリを再計算します。 ライブクエリの再計算を行います。 スケーリングAPIを公開する予定です)。 ドライバは ドライバはまた、10秒間のタイマーで各ライブクエリをポーリングしています。 また、ドライバは、Meteorサーバを迂回した帯域外のデータベース更新を捕捉するために、各ライブクエリを10秒タイマーでポーリングします。

マージボックス

の仕事は マージボックス は、結果を結合することです ( added , changedremoved の呼び出し)を単一のデータストリームに変換します。 ストリームに変換します。 接続されている各クライアントにマージボックスが1つあります。 これは、クライアントのminimongoキャッシュの完全なコピーを保持します。 クライアントのminimongoキャッシュの完全なコピーを持っています。

サブスクリプションが1つだけの例では、マージボックスは本質的にパススルーです。 本質的にパススルーです。 しかし、より複雑なアプリでは、複数のサブスクリプションを持つことができます。 サブスクリプションがあり、それらは重複する可能性があります。 2 つのサブスクリプションが両方とも同じドキュメントに同じ属性を設定する場合 2 つのサブスクリプションが同じドキュメントに同じ属性を設定する場合、マージボックスはどちらの値が優先するかを決定し が優先され、その値のみがクライアントに送信されます。 サブスクリプションの優先順位を設定するための API はまだ公開されていません。 をまだ公開していません。 今のところ、優先順位は は、クライアントがデータセットを購読した順番で決まります。 クライアントが最初に が最も高い優先度を持ち、2 番目のサブスクリプションがその次に高い優先度を持つ、という具合です。 サブスクリプションが次に高くなり、以下同様です。

マージボックスはクライアントの状態を保持するため、パブリッシュがどのようなものであっても、各クライアントを最新の状態に保つために最小限のデータを送信することができます。

マージボックスはクライアントの状態を保持しているため、パブリッシュ関数が何を送信しても、各クライアントを最新に保つために最小限のデータを送信できます。 関数が何を送り込んでも、各クライアントを最新に保つための最小限のデータを送ることができます。

更新時に発生すること

さて、これでシナリオの舞台が整いました。

1,000 人の接続されたクライアントがいます。 各クライアントは同じライブの Mongo クエリ ( Somestuff.find({}) ). クエリは各クライアントで同じなので、ドライバが実行しているのは は 1 つのクエリを実行するだけです。 アクティブなマージボックスは1,000個あります。 そして 各クライアントのパブリッシュ関数が登録した added , changed そして removed を含むライブクエリで、マージボックスの1つにフィードされます。 他には何もマージ ボックスに接続されていません。

最初に Mongo ドライバです。 クライアントの1つが新しいドキュメントを を挿入します。 Somestuff に挿入されると、それが再計算のトリガーとなります。 Mongo ドライバが再実行するのは にあるすべてのドキュメントに対してクエリを再実行します。 Somestuff を実行し、その結果を前の結果と比較し メモリ内の以前の結果と比較し、新しい文書が1つあることを発見し 登録された1,000の各 insert コールバックを呼び出します。

次に、パブリッシュ関数です。 ここではほとんど何も起こりません。 1,000の insert コールバックがデータをマージボックスにプッシュします。 呼び出し added .

最後に、各マージボックスはこれらの新しい属性をクライアントキャッシュの クライアントのキャッシュのメモリ内コピーと照合します。 それぞれの場合、マージ ボックスはその値がまだクライアント上になく、既存の値の影になっていないことを確認します。 値はまだクライアントに存在せず、既存の値をシャドウしていないことがわかります。 そこで はDDPを発行する。 DATA メッセージを発行します。 を発行し、サーバー側のインメモリコピーを更新します。

CPUコストの合計は、1つのMongoクエリの差分と、1,000個のマージボックスがクライアントの状態をチェックし、新しいクエリを構築するコストです。 1,000 個のマージボックスがクライアントの状態をチェックし、新しい DDP メッセージのペイロードを構築するコストです。 DDP メッセージ ペイロードを構築するコストです。 ワイヤーを介して流れるデータは、1つの 1,000個のクライアントに送信される1つのJSONオブジェクトで、データベースの新しいドキュメントに対応します。 ドキュメントに対応する1つのJSONオブジェクトと、1つのRPCメッセージです。 サーバーへの を送信します。 メッセージをサーバーに送信します。

最適化

以下は、私たちが間違いなく計画していることです。

  • より効率的な Mongo ドライバ。 我々は ドライバを最適化しました。 0.5.1 で最適化し、個別のクエリごとにひとつのオブザーバを実行するだけにしました。

  • すべてのDB変更がクエリの再計算をトリガするわけではありません。 私たちは 自動的な改善も可能ですが、最良のアプローチは、開発者が再実行が必要なクエリを指定できる API で、開発者が再実行が必要なクエリを指定できるようにすることです。 例えば 例えば、あるチャットルームにメッセージを挿入した場合 あるチャットルームにメッセージを挿入しても、他のチャットルームのメッセージのライブクエリが無効になってはいけないことは、開発者にとって明らかです。 を無効にしてはなりません。

  • Mongoドライバー、パブリッシュ機能、マージボックスは同じプロセスで実行する必要はありませんし、同じマシン上で実行する必要もありません。 を同じプロセス、あるいは同じマシンで実行する必要はありません。 アプリケーションによっては は複雑なライブクエリを実行するので、データベースを監視するためにもっと多くの CPU を必要とします。 また、ブログエンジンなどのように、クエリの数は少ないが、多くのクライアントが接続されているアプリケーションもあります。 これらのアプリケーションでは、マージボックスにより多くのCPUが必要となります。 このような場合は、マージボックスのためにさらにCPUが必要になります。 これらのコンポーネントを分離することで、各コンポーネントを独立してスケールすることができます。 を個別に拡張することができます。

  • 多くのデータベースは、行が更新されたときに起動するトリガーをサポートしており、古い行と新しい行を提供します。 古い行と新しい行を提供するトリガーをサポートしています。 この機能により、データベースドライバは は変更のためにポーリングする代わりにトリガーを登録することができます。