第3章 RHMAP データ同期フレームワークの使用
3.1. データ同期フレームワーク
RHMAP モバイルデータ同期フレームワークには、以下の機能が含まれます。
- モバイルアプリがオフラインでデータを使用および更新できる (ローカルキャッシュ)
- クラウドアプリ経由で複数のクライアントアプリから双方向のデータ同期を管理し、バックエンドデータストアに戻すメカニズムを提供する
- データアップデート (つまり、デルタ) をクラウドアプリから接続済みクライアントに分散できる
- クラウドにある複数のアップデートからデータ競合管理を有効にする
- ネットワーク接続が失われたときに RHMAP アプリはシームレスで稼働し続けることができ、ネットワーク接続が回復したときに復帰できます。
3.1.1. ハイレベルアーキテクチャー
同期フレームワークは、クライアントアプリと Node.js クラウドアプリ API のセットで構成されます。
クライアントアプリは、バックエンドデータに直接アクセスしません。代わりに、同期クライアント API を使用して、デバイスに格納されたデータの読み取りと一覧表示を行い、変更 (作成、更新、および削除) をクラウドアプリに送信します。データにローカルで行われた変更は、クラウドアプリに送信される前にローカルの同期データキャッシュに格納されます。クライアントアプリは、同期クライアント API からリモートデータセットへの変更の通知を受け取ります。
クライアントアプリはクライアント同期サービスを使用して、リモートデータセットに行われた変更 (デルタ) をクラウドアプリから受け取り、同期データキャッシュに格納します。また、クライアントアプリはクライアント同期サービスを使用してローカルで行われた更新をクラウドアプリに送信します。クライアントアプリがオフラインのときは、キャッシュされた更新がデバイス上のローカルストレージにフラッシュされ、ネットワーク接続が再確立される前にクライアントアプリが閉じられる場合に変更を永続化できます。変更は、クライアントアプリが次回オフラインになったときにクラウドアプリにプッシュされます。
クラウドアプリはバックエンドデータに直接アクセスせず、同期クラウド API を介してのみアクセスします。クラウドアプリは、クライアント同期サービスを使用してクライアントアプリからアップデートを受け取るために同期クラウド API を使用します。これらのアップデートはクラウドアプリ同期データキャッシュに格納されます。クラウドアプリは、標準的な CRUDL (create, read, update, delete and list) および collisionHandler 関数を使用して、ホストされたストレージでバックエンドデータを管理するために同期クラウド API を使用します。
標準的なデータハンドラー関数以外に、クラウドアプリにはユーザー定義データアクセス関数も使用できます。
3.1.2. API
同期用のクライアントおよび Node.js API 呼び出しは、以下のガイドで文書化されています。
- JavaScript SDK API の項「Sync」
- Node.js API の項「Sync」
- iOS SDK ドキュメント
- Android SDK Docs
3.1.3. スタートガイド
アプリで同期フレームワークを使用するには、以下の手順を実行します。
クライアントサイドで $fh.sync を初期化します。
//See [JavaScript SDK API](../api/app_api.html#app_api-_fh_sync) for the details of the APIs used here var datasetId = "myShoppingList"; //provide sync init options $fh.sync.init({ "sync_frequency": 10, "do_console_log": true, "storage_strategy": "dom" }); //provide listeners for notifications. $fh.sync.notify(function(notification){ var code = notification.code if('sync_complete' === code){ //a sync loop completed successfully, list the update data $fh.sync.doList(datasetId, function (res) { console.log('Successful result from list:', JSON.stringify(res)); }, function (err) { console.log('Error result from list:', JSON.stringify(err)); }); } else { //choose other notifications the app is interested in and provide callbacks } }); //manage the data set, repeat this if the app needs to manage multiple datasets var query_params = {}; //or something like this: {"eq": {"field1": "value"}} var meta_data = {}; $fh.sync.manage(datasetId, {}, query_params, meta_data, function(){ });通知について
同期フレームワークは、同期ライフサイクル中にさまざまな種類の通知を提供します。使用しているアプリの要件に応じて、アプリがリッスンする通知の種類を選択し、コールバックを追加できます。ただし、これは必須ではありません。通知リスナーがなくても同期フレームワークにより同期が実行されます。
適切な通知リスナーを追加すると、アプリのユーザーエクスペリエンスが向上します。
-
同期フレームワークエラーが発生した状況で重大なエラーメッセージをユーザーに表示します (
client_storage_failedなど)。 -
デバッグを支援するためにエラーと障害をコンソールに表示します (
remote_update_failedやsync_failedなど)。 -
デルタを受け取った場合は、同期データに関連する UI を更新します。たとえば、データに変更がある場合は、
delta_receivedとrecord_delta_receivedを使用できます。 競合を監視します。
$fh.sync API を使用してクライアントで CRUDL 操作を実行します。
-
同期フレームワークエラーが発生した状況で重大なエラーメッセージをユーザーに表示します (
クラウドサイドで $fh.sync を初期化する
この手順はオプションであり、データセットバックエンドとの同期ループ周期を変更するなど、サーバーでデータセットオプションを上書きする場合のみ必要です。デフォルトの同期周期を変更する場合は、以下の項「留意事項」を参照してください。
var fhapi = require("fh-mbaas-api"); var datasetId = "myShoppingList"; var options = { "syncFrequency": 10 }; fhapi.sync.init(datasetId, options, function(err) { if (err) { console.error(err); } else { console.log('sync inited'); } });この時点でアプリで同期フレームワークを使用するか、サンプルアプリを使用して基本的な使用方法を学ぶことができます (クライアントアプリとクラウドアプリ)。
デフォルトのデータアクセス実装が要件を満たさない場合は、オーバーライド関数を提供できます。
3.1.3.1. 不必要な同期ループの回避
クライアントとサーバーの同期頻度は別々に設定されるため、サーバーサイドの同期頻度がクライアントサイドの同期頻度と異なる場合は 1 つの同期ごとに 2 つの同期ループが実行されることがあります。クライアントで長い周期を設定しても、サーバーの同期周囲は変更されません。2 つの同期ループを回避するには、サーバーのデータセットの syncFrequency 値をクライアントの対応するデータセットの sync_frequency 値に設定します。
例
- サーバーサイドデータセットの syncFrequency は 120 秒に設定されます。
- クライアントサイドデータセットの sync_frequency は 120 秒に設定されます。
ただし、クライアントとサーバーで異なる周期が必要な場合は、異なる値を設定できます。
3.1.4. 同期フレームワークの高度な機能の使用
同期フレームワークは、アプリ開発者がデータセットのソースデータを定義することを可能にするフックを提供します。通常、ソースデータは外部のデータベース (MySql、Oracle、MongoDB など) ですが、これは要件ではありません。データセットのソースデータはどんな種類でもかまいません (csv ファイル、FTP メタデータ、複数のデータベーステーブルから取得されたデータなど)。同期フレームワークの唯一の要件は、ソースデータの各レコードが一意の ID を持ち、データが同期フレームワークに JSON オブジェクトとして提供されることです。
バックエンドデータソースと同期するために、アプリ開発者は同期のコードを実装できます。
たとえば、データベースからデータをロードする代わりにバックエンドからデータをリストするときに、ハードコーディングされたデータを返すことができます。
クライアントサイドで $fh.sync を初期化します。
これは Getting Started の手順 1 と同じです。
クラウドサイドで $fh.sync を初期化し、オーバーライドを提供します。
var fhapi = require("fh-mbaas-api"); var datasetId = "myShoppingList"; var options = { "syncFrequency": 10 }; //provide hard coded data list var datalistHandler = function(dataset_id, query_params, cb, meta_data){ var data = { '00001': { 'item': 'item1' }, '00002': { 'item': 'item2' }, '00003': { 'item': 'item3' } } return cb(null, data); } fhapi.sync.init(datasetId, options, function(err) { if (err) { console.error(err); } else { $fh.sync.handleList(datasetId, datalistHandler); } });他のオーバーライドの提供方法については、Node.js API の項「Sync」を参照してください。
3.1.5. さらに詳しく知るために
興味がある場合は、同期フレームワークを理解するのに役に立つ以下の情報をお読みください。
3.1.5.1. データセット
データセットはアプリクライアントとアプリクラウド間で同期するデータを表す JSON オブジェクトです。データセットの構造は以下のとおりです。
{
record_uid_1 : {<JSON Object of data>},
record_uid_2 : {<JSON Object of data>},
record_uid_3 : {<JSON Object of data>},
...
}データセットの各レコードは一意の ID (UID) を持つ必要があります。この UID はデータセットのレコードに対してキーとして使用されます。
同期フレームワークは複数のデータセットを管理できます (各データセットは個別に設定できます)。
各データセットは、同期 API と通信するときに (アプリクライアントとアプリクラウドの両方で) 使用する必要がある一意の名前を持ちます。
3.1.5.2. 競合
競合は、クライアントがアップデートをレコードに送信しようとしたときに、レコードのクライアントのバージョンが古い場合に発生します。通常、競合は、クライアントがオフラインであり、レコードのローカルバージョンに対してアップデートを実行したときに発生します。
競合を処理するには、以下のハンドラーを使用します。
-
handleCollision()- 競合が発生したときに同期フレームワークによって呼び出されます。デフォルトの実装により、"<dataset_id>_collision" という名前のコレクションにデータレコードが保存されます。 -
listCollision()- データ競合のリストを返します。デフォルトの実装により、"<dataset_id>_collision" という名前のコレクションからすべての競合レコードがリストされます。 -
removeCollision()- 競合のリストから競合レコードを削除します。デフォルトの実装により、"<dataset_id>_collision" という名前のコレクションからハッシュ値に基づいた競合レコードが削除されます。
データ競合を処理するハンドラー関数オーバーライドを提供できます。オプションは以下のとおりです。
- データ管理者があとで手動で解決するために競合レコードを格納します。
競合を引き起こしたアップデートを破棄します。これを行うために、
handleCollision()関数は渡された競合レコードに何も処理を行いません。警告これにより、競合を引き起こしたアップデートがクラウドアプリにより破棄されるため、データが失われる可能性があります。
競合を引き起こしたアップデートを適用します。これを行うには、
handleCollision()関数が、データセットに対して定義されたhandleCreate()関数を呼び出す必要があります。警告この結果、競合を引き起こしたアップデートはデータの古いバージョンに基づき、一部のフィールドは古い値に戻されることがあるため、データが失われる可能性があります。
ネイティブ同期クライアントは、類似したインターフェースを使用します。API とサンプルコードは、iOS Github repo と Android Github repo で確認できます。
3.2. 同期に関する用語
3.2.1. 同期プロトコル
3.2.2. 同期サーバー
同期サーバーは、同期プロトコルのサーバー部分であり、fh-mbaas-api モジュールに含まれます。以下のことを行います。
- データセットバックエンドと統合するために同期サーバー API を公開する
- 同期サーバーループを実行して、データセットバックエンドのアップデートが検出されたときにデータセットを更新する
3.2.3. 同期クライアント
同期クライアントは、同期プロトコルのクライアント部分です。3 つの同期クライアント実装があります。
- Javascript (FeedHenry Javascript SDK)
- Objective C (FeedHenry iOS SDK)
- Java (FeedHenry Android SDK)
同期クライアント:
- データセットに対する CRUDL アクションのために同期クライアント API をフロントエンドに公開する
- 同期クライアントループを実行して、特定のデータセットに対して指定された同期周期で同期サーバーを呼び出す
3.2.4. 同期サーバーループ
同期サーバーループは、同期サーバーで継続的に実行される機能です (各実行の間に 500ms の待機が発生)。
各実行中は、すべてのデータセットクライアントに対して反復処理が行われ、データセットをデータセットバックエンドから同期する必要があるかどうかを確認できます。
3.2.5. 同期クライアントループ
同期クライアントループは、同期クライアントで継続的に実行される機能です (各実行の間に 500ms の待機が発生)。
各実行中は、すべてのデータセットクライアントに対して反復処理が行われ、データセットを同期サーバーと同期する必要があるかどうかを確認できます。
3.2.6. 同期周期
同期クライアントでは、これは特定のデータセット用の同期サーバーからのアップデートをチェックする間隔です。
同期サーバーでは、これは特定のデータセット用データセットバックエンドからのアップデートをチェックする間隔です。
詳細については、同期周期の設定を参照してください。
3.2.7. データセット
データセットは、1 つ以上の同期クライアント、同期サーバー、および データセットバックエンド間で同期されるレコードのコレクションです。
3.2.8. データセットバックエンド
同期クライアントと同期サーバー間で同期されたデータ用レコードのシステム。
API を提供する任意のシステムであり、同期サーバーから mysql データベースや SOAP サービスなどと統合できます。
同期サーバーは、データセットバックエンドと統合するためにデータセットハンドラーを使用して同期サーバー API を公開します。
3.2.9. データセットハンドラー
データセットハンドラーは、同期サーバーをデータセットバックエンドに統合する機能です。
データセットに対して CRUDL アクションを実行したり、データセットレコード間で競合を管理したりする多くのハンドラーがあります。
これらのハンドラーのデフォルトの実装では fh.db (RHMAP MBaaS で支援される MongoDB) を使用します。
これらの各ハンドラーはオーバーライドできます。詳細については、Sync Server API を参照してください。
重要: ハンドラーをオーバーライドする場合、Red Hat は、デフォルトの実装を使用している一部のハンドラーとオーバーライドされた実装を使用している他のハンドラーでの異常な動作を回避するために、すべてのハンドラーをオーバーライドすることをお勧めします。
3.2.10. データセットクライアント
データセットクライアントは、クライアントとサーバー間で積極的に同期されている各データセットに対する同期クライアントと同期サーバーに格納された設定です。
以下のようなデータが含まれます。
- データセットの同期周期
- データセットバックエンドを呼び出すときに含めるクエリーパラメーター
- データセットレコードの最新のハッシュ
- データセットバックエンドとの同期が現在実行中であるかどうかに関するデータ
3.2.11. データセットレコード
データセットレコードはデータセット内の個別レコードです。
以下のものが含まれます。
- このレコードが表すローデータ (MySQL テーブルの行値など)
- ローデータのハッシュ
3.2.12. ハッシュ
同期プロトコルで使用されるハッシュには 2 つの種類があります。
- 個別レコードを比較してそれらが異なるかどうかを確認するために使用される個別データセットレコードのハッシュ。
- すべてのレコードに対して反復処理を行わずにクライアントのレコードセットとサーバーのレコードセットを比較するために使用される特定のデータセットクライアントに対するすべてのデータセットレコードのハッシュ。
3.3. 同期サーバーのアーキテクチャー
同期フレームワークの一般的な概要については、同期に関する概要と同期に関する用語を参照してください。
3.3.1. アーキテクチャー
同期サーバーアーキテクチャーに含まれるもの: * HTTP ハンドラー * キューおよびプロセッサー * 同期スケジューラー
これらの各コンポーネントによりデータは MongoDB で永続化されます。˙

3.3.1.1. HTTP ハンドラー
これらのハンドラーは、同期クライアントからの同期要求を処理します。
3.3.1.1.1. 同期 HTTP ハンドラー
データセットクライアントを作成または更新し、保留中のレコードと確認を処理のために適切なキューにプッシュします。
3.3.1.1.2. 同期レコード HTTP ハンドラー
最新のデータをクライアントの状態と比較します。デルタの取得後に、処理されているがまだ同期されていないアップデートがチェックされます。このハンドラーはデルタ内のすべてのレコードを反復処理します。レコードが保留中のキューにある場合や適用された場合、レコードはこのハンドラーによってデルタから削除され、更新されたデルタはクライアントに返されます。
3.3.1.2. キュー
同期フレームワークでは以下のキューが使用されます。
-
fhsync_queue- 同期が必要なデータセット向けのジョブ -
fhsync_ack_queue- 確認が必要な保留中の変更向けのジョブ -
fhsync_pending_queue- 処理が必要な保留中の変更向けのジョブ
メッセージはこれらのキューに格納され、プロセッサーによって消費されます。
3.3.1.3. プロセッサー
各キューには対応するプロセッサーがあります。
-
同期プロセッサー -
fhsync_queueからジョブを取得し、これらのジョブを処理します。 -
確認プロセッサー -
fhsync_ack_queueから確認を取得し、MongoDB からこれらの確認を削除します。 -
保留中プロセッサー -
fhsync_pending_queueから保留中のアイテムを取得し、変更をデータセットバックエンドに適用します。
同期サーバーの各ワーカーは、タスクを分散することを可能にするこれらの各プロセッサーの 1 つのインスタンスを持ちます。
3.3.1.4. 同期スケジューラー
水平的にスケールされた場合、各同期ワーカーは固定された間隔で同期スケジューラーになろうとします。各ワーカーは MongoDB にあるロックを取得しようとします。同期スケジューラーのロックがあるワーカーは、データセットの最後の同期のタイムスタンプと同期周期を確認して同期する必要があるデータセットを決定します。データセットを同期する必要がある場合、ジョブは fhsync_queue に追加されます。
3.4. データ同期設定ガイド
データ同期設定は、クライアントサイドとクラウド (サーバーサイド) に適用できます。
クライアントサイドの同期周期は、sync_frequency 変数を使用して設定されます。sync_frequency の設定の例については、ドキュメンテーションのこの項を参照してください。
クラウドの同期周期は、syncFrequency 変数を使用して設定されます。syncFrequency の設定の例については、ドキュメンテーションのこの項を参照してください。
3.4.1. 同期周期の設定
同期周期は、システムが 2 つの同期プロセスの間に待機する期間です。
重要: クライアントとサーバーで別々に周期を設定できます。ただし、Red Hat は以下のシナリオを回避するために同じ設定を使用することをお勧めします。
- サーバーがデータセットバックエンドからのアップデートをチェックするよりも高い頻度でクライアントが呼び出しを行うため、クライアントから不必要なトラフィックが発生します。
- サーバーがデータセットバックエンドからのアップデートをチェックするよりも低い頻度でクライアントが呼び出しを行うため、アクティビティーがないことが原因でサーバーがキャッシュからデータセットを破棄します。
サーバーの同期周期の値により、同期プロセッサーが実行される頻度が決定されます。同期プロセッサーが実行されるたびに、データセットバックエンドに対してリスト操作が実行され、データとローカルコピーが同期されます。使用しているアプリケーション向けの同期周期の最適な値を決定するために、以下のセクションをお読みください。
使用しているクライアントが他のクライアントの変更をどれぐらい早く認識するか?
クライアントが変更を送信すると、それらの変更はデータセットバックエンドに直接適用されます。パフォーマンスを向上させるために、他のクライアントはローカルコピーからデータを取得します。したがって、他のクライアントは次の同期プロセッサーの実行後まで新しい変更を取得できません。他のクライアントができるだけ早く変更を取得する必要がある場合は、同期周期に低い値を設定することを検討してください。
同期プロセッサーの実行にどれぐらいかかるか?
同期周期の値により、システムが同期プロセッサーの実行の間に待機する時間が決定されます。つまり、同期周期は 1 つの実行が完了してから次の実行が開始されるまでの時間です。したがって、2 つの同期プロセッサーが同時に実行される状況は発生しません。計算式は以下のとおりです。
実際の同期周期 = 同期プロセッサーの実行時間 + 同期周期
これにより、システムがデータセットバックエンドに行う要求の数を簡単に計算できるようになります。
同期プロセッサーの各実行にかかる時間を調べるには、同期統計エンドポイントを問い合わせて
sync_workerが完了するのにかかる平均Job Process Time(ジョブプロセス時間) を確認します。データセットバックエンドサービスはどれぐらいの負荷を処理できるか?
同期プロセッサーが実行されるたびに、データセットバックエンドに対するリスト操作が実行されます。同期周期を設定する場合は、バックエンドで生成される要求の数を予測し、バックエンドがその負荷を処理できるようにする必要があります。
たとえば、データセットの同期周期を 100ms に設定し、各同期プロセッサーの実行に 100ms かかる場合は、サーバーによって約 5 要求/秒がバックエンドに生成されます。ただし、同じバックエンドを使用する同期周期が 100ms の別のデータセットがある場合、バックエンドに対する負荷は 10 要求/秒になります。バックエンドに対して負荷テストを行うことにより、バックエンドがその負荷を処理できるかどうかを調べることができます。
ただし、アプリをスケールするとき、この値は大きくなりません。たとえば、サーバーに複数のワーカーが存在する場合、同期プロセッサーの実行は、ワーカーで繰り返されず、分散されます。この設計により、アプリの負荷が大きいときにバックエンドが保護されます。
サーバーの負荷がどれぐらい増えるか?
データがバックエンドから返されると、サーバーはデータをローカルストレージに保存する必要があります (MongoDB)。システムは変更がある場合のみアップデートを実行します。ただし、ローカルストレージの現在のデータを取得するには、読み取り操作を最初に実行する必要があります。同期プロセッサーの実行がたくさんあるときは、サーバー自体の負荷が増えることがあります。場合によっては、このことを考慮する必要があります (特にデータセットが大きい場合)。
サーバーのパフォーマンスを理解するために、同期統計エンドポイントを使用して CPU 使用率と MongoDB 処理時間を確認できます。
同期周期値を使用して、サーバーがバックエンドに生成する要求の数を制御できます。この値は、バックエンドが負荷を処理でき、サーバー自体が過負荷の状態でない限り、0ms に設定できます。
3.4.2. ワーカーの設定
同期アーキテクチャーで説明されているように、同期データを格納するために使用するさまざまなキューがあります。データを処理するために、各キューに対応するワーカーが作成されます。この唯一のタスクは、キューからジョブを一度に 1 つずつ取得し、処理することです。ただし、あるジョブを完了してから利用可能な次のジョブを取得するまでの時間を表す間隔値があります。この値は、ワーカーのパフォーマンスを最大化するために設定できます。
3.4.2.1. 間隔の目的
キューからジョブを取得する要求は非ブロック操作です。キューにジョブが残っていないと、要求が返され、ワーカーはジョブを再び取得しようとします。
この場合、またはジョブが非常に早く完了する場合は、ワーカーによって主要なイベントループが過負荷の状態になり、他のコード実行が遅くなることがあります。このシナリオを回避するために、各ワーカーには以下の間隔値設定項目があります。
-
pendingWorkerInterval -
ackWorkerInterval -
syncWorkerInterval
デフォルトの間隔値は非常に低い (1ms) ですが、設定可能です。このデフォルト値では、ジョブを実行するのに時間がかかり、主要なイベントループで他の操作を完了することを可能にするいくつかの非ブロックI/O 操作 (リモート HTTP 呼び出しや DB 呼び出しなど) があることを前提とします。この低いデフォルトの間隔によって、ジョブをできるだけ速く処理できるようになり、CPU をより効率的に使用できるようになります。ジョブがない場合は、ワーカーによってリソースが不必要に過負荷の状態にならないようにバックオフメカニズムが呼び出されます。
デフォルト値によってデータセットバックエンドに対して発生する要求が多すぎる場合、またはデフォルトの間隔値を変更する必要がある場合は、1 つ以上のワーカーの設定オプションを上書きできます。
3.4.2.2. ワーカーバックオフ
キューにジョブが残っていない場合、各ワーカーはバックオフストラテジーを持ちます。これにより、ワーカーが不必要な CPU サイクルを使用したり、キューに対する不必要な呼び出しを行ったりすることが防がれます。新しいジョブがキューに格納されている場合、ワーカーは次にキューを確認するときに間隔をリセットします。
各ワーカーの動作は以下の設定オプションで上書きできます。
-
pendingWorkerBackoff -
ackWorkerBackoff -
syncWorkerBackoff
デフォルトでは、すべてのワーカーは、最大遅延値で指数ストラテジーを使用します。最小間隔が 1ms に設定されている場合、ワーカーはジョブを処理してから別のジョブをキューから取得するまで 1ms 待機します。このパターンはキューにアイテムが存在する限り続行します。キューが空になると、間隔は最大間隔 (たとえば、60 秒) に到達するまで指数的 (2ms、4ms、8ms、16ms、… ~16s、~32s) に増えます。次に、ワーカーはジョブがあるかどうかを調べるためにキューを 60 秒ごとに確認します。将来キューでジョブが見つかったら、ワーカーは再び 1ms ごとにキューを確認します。
詳細については、Sync API Doc を参照してください。
3.5. 同期サーバーアップグレードに関する注記
3.5.1. 概要
本項では以下のことを行う開発者を対象とします。
- アプリケーションで同期サーバーを使用する
-
fh-mbaas-apiのバージョンを<7.0.0から>=7.0.0にアップグレードする
fh-mbaas-api@>=7.0.0 をすでに使用している場合は、本項のどの手順も実行しないでください。
このアップグレードでは、同期クライアントに変更は加えられません。
3.5.2. 前提条件
7.0.0 より前は、同期サーバーが fh.db API を使用して同期操作データを MongoDB に格納していました。fh.db は、中間 http API (fh-ditch) を介することがある MongoDB のラッパーです。この結果、同期操作データに対して実行できるアクションは制限されます。また、MongoDB に直接接続されるモジュールの使用も制限されます。fh-mbaas-api@7.0.0 では、同期サーバーで MongoDB への直接接続が必要です。
条件は以下のとおりです。
- ホストされた MBaaS の場合は、アプリデータベースを「アップグレード」する必要があります。
- 自己管理された MBaaS の場合は、デフォルトですべてのアプリが MongoDB の独自のデータベースを取得するため、何も行う必要はありません。
3.5.3. データハンドラー関数署名の変更
同期データハンドラー向けのメソッド署名は、新しい同期フレームワークでは異なります。データハンドラーを実装した場合は、パラメーターの順序を変更する必要があります。これらの変更は、javascript のパラメーター順序規則 (つまり、コールバックが最後のパラメーター) に準拠します。
重要 パラメーターとして各ハンドラーに渡されたコールバック関数が各呼び出しに対して実行されるようにします。これにより、ハンドラーの完了後もワーカーを維持できます。
7.0.0 よりも前のバージョンとそのバージョンのデータハンドラーおよび署名は以下のとおりです。
// <7.0.0
sync.handleList(dataset_id, function(dataset_id, params, callback, meta_data) {});
sync.globalHandleList(function(dataset_id, params, callback, meta_data) {});
// >=7.0.0
sync.handleList(dataset_id, function(dataset_id, params, meta_data, callback) {});
sync.globalHandleList(function(dataset_id, params, meta_data, callback) {});
// <7.0.0
sync.handleCreate(dataset_id, function(dataset_id, data, callback, meta_data) {});
sync.globalHandleCreate(function(dataset_id, data, callback, meta_data) {});
// >=7.0.0
sync.handleCreate(dataset_id, function(dataset_id, data, meta_data, callback) {});
sync.globalHandleCreate(function(dataset_id, data, meta_data, callback) {});
// <7.0.0
sync.handleRead(dataset_id, function(dataset_id, uid, callback, meta_data) {});
sync.globalHandleRead(function(dataset_id, uid, callback, meta_data) {});
// >=7.0.0
sync.handleRead(dataset_id, function(dataset_id, uid, meta_data, callback) {});
sync.globalHandleRead(function(dataset_id, uid, meta_data, callback) {});
// <7.0.0
sync.handleUpdate(dataset_id, function(dataset_id, uid, data, callback, meta_data) {});
sync.globalHandleUpdate(function(dataset_id, uid, data, callback, meta_data) {});
// >=7.0.0
sync.handleUpdate(dataset_id, function(dataset_id, uid, data, meta_data, callback) {});
sync.globalHandleUpdate(function(dataset_id, uid, data, meta_data, callback) {});
// <7.0.0
sync.handleDelete(dataset_id, function(dataset_id, uid, callback, meta_data) {});
sync.globalHandleDelete(function(dataset_id, uid, callback, meta_data) {});
// >=7.0.0
sync.handleDelete(dataset_id, function(dataset_id, uid, meta_data, callback) {});
sync.globalHandleDelete(function(dataset_id, uid, meta_data, callback) {});
// <7.0.0
sync.listCollisions(dataset_id, function(dataset_id, callback, meta_data) {});
sync.globalListCollisions(function(dataset_id, callback, meta_data) {});
// >=7.0.0
sync.listCollisions(dataset_id, function(dataset_id, meta_data, callback) {});
sync.globalListCollisions(function(dataset_id, meta_data, callback) {});
// <7.0.0
sync.removeCollision(dataset_id, function(dataset_id, collision_hash, callback, meta_data) {});
sync.globalRemoveCollision(function(dataset_id, collision_hash, callback, meta_data) {});
// >=7.0.0
sync.removeCollision(dataset_id, function(dataset_id, collision_hash, meta_data, callback) {});
sync.globalRemoveCollision(function(dataset_id, collision_hash, meta_data, callback) {});3.5.4. 動作の変更
同期サーバーが MongoDB に直接接続するようになったため、起動時にセットアップ時間が必要です。sync.init() を現在使用している場合は、これらの呼び出しを sync:ready イベントハンドラーでラップします。たとえば、以下のコードを使用する場合は、
fh.sync.init('mydataset', options, callback);イベントハンドラーに配置するよう変更します。
fh.events.on('sync:ready', function syncReady() {
sync.init('mydataset', options, callback);
});または、同期 API からイベントエミッターを使用できます。
fh.sync.getEventEmitter().on('sync:ready', function syncReady() {
sync.init('mydataset', options, callback);
});
[source,json]3.5.5. ロガーの変更
fh.sync.init() に渡された logLevel オプションは利用できなくなりました。デフォルトでは、新しい同期サーバーは何もログに記録しません。すべてのロギングでは [debug](https://www.npmjs.com/package/debug) モジュールが使用されます。同期サーバーからの出力をログに記録する場合は、以下のように DEBUG 環境変数を設定できます。
DEBUG=fh-mbaas-api:sync
SDK 全体からすべてのログを参照する場合は、以下のように設定します。
DEBUG=fh-mbaas-api:*
debug モジュールの他のすべての環境変数と動作機能が利用可能です。
3.6. 同期サーバーのパフォーマンスとスケーリング
3.6.1. 概要
同期サーバーはスケーラブルとして設計されています。
本項では、同期サーバーのパフォーマンスとスケーリングのオプションについて説明します。
3.6.2. パフォーマンスの検査
同期サーバーのパフォーマンスを検査するには 2 つのオプションがあります。
/mbaas/sync/statsエンドポイントを問い合わせます。デフォルトでは、同期フレームワークによって (実行中の場合) メトリクスデータは Redis に保存されます。また、HTTP GET 要求を
/mbaas/sync/statsエンドポイントに送信してこれらのメトリクスデータの概要を表示できます。このエンドポイントからは以下の情報が利用可能です。
- すべてのワーカーの CPU とメモリーの使用状況
- さまざまなジョブを処理するのかかる時間
- さまざまなジョブキュー内の残りのジョブ数
- さまざまな API 呼び出しにかかる時間
さままな MongoDB 操作にかかる時間
これらの各メトリクスに対して、サンプル値、現在値、最大値、最小値、および平均値の合計数を確認できます。
デフォルトでは、各メトリクスに対して最後の 1000 サンプルが収集されますが、その値は
statsRecordsToKeep設定オプションを使用して制御できます。このエンドポイントは使いやすく、同期サーバーの現在のパフォーマンスを理解するのに十分な情報を提供します。
InfluxDB と Grafana を使用してメトリクスデータを視覚化します。
現在および過去のメトリクスデータを視覚化する場合は、メトリクスデータを InfluxDB に送信するよう同期サーバーに指示し、Grafana でグラフを表示できます。
InfluxDB と Grafana をセットアップするのに役に立つ多くのオンラインチュートリアルがあります。以下に例を示します。
How to setup InfluxDB and Grafana on OpenShift
InfluxDB が実行されたら、以下の設定を更新して同期サーバーがメトリクスデータを送信するよう指示します。
- metricsInfluxdbHost
metricsInfluxdbPort
注記metricsInfluxdbPortが UDP ポートであることを確認します。Grafana でメトリクスデータグラフを表示するには、グラフ付きの新しいダッシュボードを作成する必要があります。これを行う最も簡単な方法は、この Grafana ダッシュボードファイルをインポートすることです。アプリが実行されたら、メトリクスデータを Grafana ダッシュボードに表示できます。
Grafana グラフの設定方法の詳細については、Grafana ドキュメンテーションを参照してください。
3.6.3. パフォーマンスの理解
同期サーバーのパフォーマンスを理解するために確認する必要がある主要なメトリクスは以下のとおりです。
3.6.3.1. CPU 使用率
これは最も重要なメトリクスです。CPU が過負荷である場合は、同期サーバーがクライアント要求に応答できませんが、できるだけ CPU 使用率を上げたいことがあります。
この問題を解決するには、しきい値を確立して同期サーバーをスケールするタイミングを決定します。推奨値は 80% です。
CPU 使用率がこの値未満である場合は、同期サーバーをスケールする必要はなく、ワーカー間隔設定値を少し小さくして CPU 使用率を上げることができます。ただし、CPU 使用率がこのしきい値を超える場合は、同期サーバーのスケーリングを検討してください。
3.6.3.2. キュー内の残りのジョブ
同期サーバーはさまざまなジョブをキューに保存して後で処理します。
キュー内のジョブの数が増加し続け、CPU 使用率が比較的低い場合は、ジョブをより速く処理するためにワーカー間隔設定値を小さくします。
同期サーバーの負荷がすでに大きい場合は、新しいワーカーを作成してジョブを処理できるよう同期サーバーをスケーリングすることを検討してください。
3.6.3.3. API 応答時間
さまざまな同期 API の応答時間の増加が確認され、CPU 使用率が上昇している場合は、同期サーバーに負荷がある状態を意味します。同期サーバーのスケーリングを検討してください。
ただし、CPU 使用率がそれほど変わらない場合は、何かほかのことによってその問題が引き起こされていることを意味するため、その問題を調査する必要があります。
3.6.3.4. MongoDB 操作時間
本番稼働環境では、さまざまな MongoDB 操作の時間は比較的短く、一定です。これらの操作の時間が増え始めた場合は、同期サーバーにより MongoDB に対して生成される操作の数が多すぎる状態であり、MongoDB の制限に到達しようとしていることを意味します。
この場合は、ボトルネックが MongoDB に存在するため、同期サーバーをスケーリングしても問題は解決されません。以下の選択肢を検討します。
-
useCacheフラグを true に設定してキャッシュを有効にします。これにより、データセットレコードを読み取るデータベース要求の数が減少します。 - さまざまなワーカー間隔と同期周期を増加します。
- 可能な場合は、MongoDB をスケーリングします。
3.6.4. 同期サーバーのスケーリング
同期サーバーをスケールする場合は、以下のことを検討します。
3.6.4.1. ホストされた MBaaS でのスケーリング
RHAMP SaaS プラットフォームで同期サーバーをスケールするには以下の 2 つの選択肢があります。
3.6.4.1.1. Node.js クラスターモジュールの使用
単一のアプリ内部でスケールするには、Nodejs Clustering を使用してさらにワーカーを作成します。
3.6.4.1.2. より多くのアプリのデプロイ
別の選択肢は、より多くのアプリをデプロイし、同じ MongoDB に対して既存のアプリとして指定することです。これにより、同期サーバーをさらにスケールすることが可能になります。
より多くのアプリをデプロイする手順は以下のとおりです。
- 同じコードのより多くのアプリを既存のアプリとしてデプロイします。
既存のアプリの MongoDB 接続文字列を見つけます。
これは、App Studio の Environment Varaible (環境変数) 画面にリストされています。FH_MONGODB_CONN_URL という名前のシステム環境変数を探してください。
- 値をコピーし、新しく作成されたアプリで SYNC_MONGODB_URL という名前の新しい環境変数を作成して、値として MongoDB url を貼り付けます。
- アプリを再デプロイします。
この方法では、HTTP 要求処理と同期データ処理を完全に分離できます。
たとえば、このように設定された 2 つのアプリ、App 1 と App 2 が存在し、App 1 が HTTP 要求を受け取るクラウドアプリとします。この場合は、以下のことを行えます。
- ワーカーの同時実行数を 0 に設定して App 1 のすべての同期ワーカーを無効にします。この結果、App 1 は HTTP 要求の処理のみを行うことになります。
- App 2 の同期ワーカーの同時実行数を増加し、同期間隔値を減少します。
ワーカーの同時実行数の設定方法については、$fh.sync.setConfig を参照してください。
3.6.4.2. 自己管理された MBaaS でのスケーリング
自動スケーリング機能を使用して OpenShift でアプリケーションをスケールします。
メトリクスが OpenShift クラスターで有効であることを確認し、oc autoscale コマンドを使用してアプリケーションのスケール方法を設定します。
たとえば、OpenShift 3.2 の場合は、クラスターメトリクスを有効にする方法とアプリケーションをスケールする方法を参照してください。
3.7. 同期サーバーにより作成された MongoDB コレクション
3.7.1. 概要
同期サーバーは、実行中にさまざまなコレクションを MongoDB に保持します。
本書では、同期サーバーが作成するコレクションとその目的について説明します。
データ損失が起こる可能性があるため、これらのコレクションは変更しないでください。
3.7.2. 同期サーバーコレクション
同期サーバーにより作成されたすべてのコレクションには接頭辞 fhsync が付けられます。
3.7.2.1. fhsync_pending_queue
このコレクションは、すべてのデータセットに対してすべてのクライアントから送信された変更を保存するために使用されます。
デバッグに役に立つフィールドは以下のとおりです。
- tries: 値が 0 よりも大きい場合は、変更が同期サーバーによってすでに処理されたことを意味します。
- payload.hash: 保留中の変更の一意の ID。
- payload.cuid: クライアントの一意の ID。
-
payload.action: 変更の種類 (
createやupdateなど)。 - payload.pre: 変更が行われる前のデータ。
- payload.post: 変更が行われたあとのデータ。
- payload.timestamp: 変更がクライアントで行われた日時。
3.7.2.2. fhsync_<datasetId>_updates
fhsync_pending_queue コレクションからの保留中の変更が処理された場合、結果はこのコレクションに保存されます。クライアントは、次の同期のときに結果を取得し、該当するクライアント通知をトリガーします。
デバッグに役に立つフィールドは以下のとおりです。
- hash: 上記コレクションからの保留中の変更の一意な ID。
-
type: 変更が正常に適用された場合。可能な値は
applied、failed、またはcollisionです。
3.7.2.3. fhsync_ack_queue
クライアントは送信された変更の結果を取得したあとに (fhsync_<datasetId>_updates コレクションに保存)、サーバーと受信を確認してサーバーがその受信確認を削除できるようにします。このコレクションは、クライアントにより送信された確認を保存するために使用されます。
デバッグに役に立つフィールドは以下のとおりです。
- payload.hash: fhsync_pending_queue コレクションからの保留中の変更の一意な ID。
3.7.2.4. fhsync_datasetClients
このコレクションは、同期サーバーによって管理されたすべてのデータセットクライアントを永続化するために使用されます。
デバッグに役に立つフィールドは以下のとおりです。
- globalHash: データセットクライアントの現在のハッシュ値。
- queryParam: データセットクライアントに関連付けられたクエリーパラメーター。
- metaData: データセットクライアントに関連付けられたメタデータ。
- recordUids: データセットクライアントに属するすべてのレコードの一意な ID。
- syncLoopEnd: データセットクライアントに対する最後の同期ループが完了した日時。
3.7.2.5. fhsync_<datasetId>_records
このコレクション内のこのデータは、データセットバックエンドからのデータのローカルコピーです。これにより、クライアントからの同期要求が速く処理され、データセットバックエンドに対する要求の数も減少します。
デバッグに役に立つフィールドは以下のとおりです。
- data: データセットバックエンドから返されたレコードの実際のデータ。
- uid: レコードの一意な ID。
- refs: このレコードを含むすべてのデータセットクライアントの ID。
3.7.2.6. fhsync_queue
このコレクションは、fhsync_<datasetId>_records をデータセットバックエンドと同期する要求を保存するために使用されます。
デバッグに役に立つフィールドは以下のとおりです。
- tries: 0 よりも大きい場合は、要求が同期サーバーによってすでに処理されたことを意味します。
3.7.2.7. fhsync_locks
データセットバックエンドと同期できるのは一度に 1 つのワーカーだけです。このためにロックが使用されます。このコレクションは、ロックを永続化するために使用されます。ロックメカニズムを使用して問題をデバッグしない限り、このコレクションを使用する必要性はほとんどありません。
3.7.3. キューコレクションのプルーニング
各キューコレクションに対して、ドキュメントは処理後すぐに削除されません。代わりに、ドキュメントは deleted と示されます。これにより、開発者はドキュメントを監査ログとして使用し、デバッグに役立てたりすることができるようになります。
これらのキューが容量を使用しすぎないようにするために、これらのメッセージには TTL (time to live) を設定できます。TTL 値に到達すると、これらのメッセージはデータベースから削除されます。
詳細については、$fh.sync.setConfig の「queueMessagesTTL」オプションを参照してください。
3.8. 同期サーバーデバッグガイド
3.8.1. クライアントの変更は適用されない
この問題のデバッグを容易に行うために、以下の質問に回答してください。
クライアントはサーバーに変更を送信しましたか?
クライアントが変更を行った後に同期要求の要求本文を確認して変更が送信されたかどうかを調べます。変更は pending アレイにある必要があります。以下に例を示します。
{
"fn":"sync",
"dataset_id":"myShoppingList",
/*...*/
"pending":[{
"inFlight":true,
"action":"update",
"post":{
"name":"Modified Name",
"created":1495123790928
},
"postHash":"2e90b858164184b9ff31e0937cef8ddf4a959ac5",
"timestamp":1495799747404,
"uid":"591dc768a95300322eee1d1f",
"pre":{
"name":"Original Name",
"created":1495123790928
},
"preHash":"421932b23f05f8aef528d73fff3cbf5aa00786a4",
"hash":"f98f595974f7e7e1f07aed6220fab04446f459c9",
"inFlightDate":1495799747850
}]
}
変更が保留中のアレイにない場合は、デバイスがオンラインであることを検証します。クライアントアプリにエラーがないか確認し、該当する同期アクションを呼び出していることを検証します (たとえば、更新の場合は sync.doUpdate())。クライアントアプリで、変更を行うコードの周辺でデバッグしたり、ロギングを追加したりすると役に立ちます。
この変更のレコードが fhsync_pending_queue コレクションにありますか?
「いいえ」の場合は、サーバーが変更を受け取っていないか、受け取るときにエラーが発生しました。
- アプリが変更を正常に送信したことを検証します。送信しなかった場合は、アプリをデバッグして問題を理解してください。アプリにエラーがあるか、サーバーからの応答にエラーがあることが考えられます。
- アプリから変更を受け取ったときにサーバーログでエラーを確認します。エラーがない場合は、デバッグログの有効化を参照してください。
レコードが fhsync_pending_queue コレクションに存在したが、レコードに対するキューの Time To Live (TTL) 期間が経過したため、レコードが削除された可能性があります。これに該当する場合は、TTL を増やすと、デバッグが有効になります。
レコードの deleted フィールドにタイムスタンプがありますか?
「いいえ」の場合は、そのアイテムがまだ処理されていません。
- 通常は、保留中のワーカーが、そのアイテムよりも先にキュー内の他のアイテムを処理しています。そのアイテムが処理のためにキューの最上位に到達するまで待機してください。
- しばらく時間がたってもアイテムが処理されない場合や、キューにこのアイテムしかない場合は、サーバーログでエラーを確認します。エラーがない場合は、デバッグログの有効化を参照してください。
この更新のレコードが fhsync_pending_queue コレクションにありますか?
「いいえ」の場合は、更新の処理中にエラーが発生した可能性があります。
- サーバーログでエラーを確認します。エラーがない場合は、デバッグログの有効化を参照してください。
レコードの type フィールドが failed または collision に設定されていますか?
「はい」の場合は、更新をデータセットバックエンドに適用できない可能性があります。
- 「競合」により、競合ハンドラーが呼び出されたことが考えられます。競合を解決する必要があります。
-
更新の「失敗」により、障害の理由とともに通知がクライアントに送られたことが考えられます。理由はレコードの
msgフィールドに記載されます。
「いいえ」であり、タイプが applied の場合は、作成または更新ハンドラーをデバッグして、なぜそのハンドラーで変更がデータセットバックエンドに適用されたと見なされたのかを確認する必要があります。
type フィールドは、collision、failed、または applied のいずれかである必要があります。
3.8.2. データセットバックエンドに適用された変更は他のクライアントに伝播されない
変更がデータセットバックエンドからクライアントに同期されるまでに十分な時間が経過しましたか?
変更がデータセットバックエンドに適用され、他のクライアントがその変更を取得する前に、以下の 2 つのことが起こる必要があります。
- サーバー上の同期ループはレコードキャッシュの更新を完了する必要があります。つまり、そのデータセットに対してリストハンドラーが呼び出されます。
- クライアント上の同期ループは、サーバーレコードキャッシュからのクライアントローカルレコードキャッシュの更新を完了する必要があります。
十分な時間が経過したら、データセットバックエンドとの同期中のエラーをサーバーログで確認してください。
データセット向けの fhsync_queue コレクションに最近のレコードがありますか?
「いいえ」の場合は、レコードの TTL 値が経過し、レコードが削除された可能性があります。この場合は、TTL 値を増やしてさらにデバッグすることができます。
別の可能性としては、同期スケジューラーでそのデータセットの同期がスケジュールされなかったことが挙げられます。この理由としては、そのデータセットに対して現在アクティブなクライアントがなく、そのデータセットに対してクライアントが最後にアクティブであったときから clientSyncTimeout が経過したことが考えられます。
レコードの deleted フィールドにタイムスタンプがありますか?
「いいえ」の場合は、同期がまだ処理されていないことを意味します。
- 通常は、同期ワーカーが、そのアイテムよりも先にキュー内の他のアイテムを処理しています。そのアイテムがキューの最上位に到達するまで待機してください。
- しばらく時間がたってもアイテムが処理されない場合や、キューにこのアイテムしかない場合は、サーバーログでエラーを確認します。エラーがない場合は、デバッグログの有効化を参照してください。
fhsync_<datasetid>_records キャッシュ内のレコードは最新ですか?
リストハンドラーが呼び出され、結果がレコードキャッシュに追加されている必要があります。レコードキャッシュが更新されたことを検証するには、更新されたレコードの fhsync_<datasetid>_records コレクションを確認します。このレコード内のデータはデータセットバックエンドのデータに一致する必要があります。一致しない場合は、サーバーログでリストハンドラーのエラーと動作を確認します。リストハンドラーにロギングを追加すると役に立つことがあります。
クライアント同期呼び出しが正常に実行されましたか?
クライアントが同期呼び出しを行う場合に、サーバーからの有効な応答があることを確認します。呼び出しが正常に行われた場合は、クライアントが更新済みレコードを取得していることを検証します。サーバーキャッシュに更新済みレコードが含まれるのにクライアントが更新済みレコードを受け取らない場合は、クエリーパラメーターとサーバーに送信されたメタデータが正しいことを確認します。デバッグログを有効にすると、正しくないデータがどのようにクライアントに送信されたのかを簡単に調べることができる場合があります。
3.8.3. デバッグログの有効化
デバッグのために同期ログを有効にするには、サーバーで以下の環境変数を設定します。
DEBUG=fh-mbaas-api:sync
この処理により、大量のログが生成されます。各ログエントリーには、そのログメッセージのコンテキストでアクションを実行する特定のデータセット ID がタグ付けされます (可能な場合)。これらのログは理解しにくいことがありますが、クライアントからの更新とそれらの更新のさまざまな段階を追跡できます。成功したシナリオのログと不成功のシナリオのログを比較し、障害が発生した段階を特定すると役に立つことがあります。
この問題の原因は、(特にエッジケースに関連する) カスタムハンドラー実装にあることが考えられます。カスタムハンドラーにログを追加すると役に立つことがあります。
データセットバックエンド接続の問題 (特に断続的な問題) はデバッグおよび特定が困難なことがあります。データセットバックエンドを外部的に監視または確認すると、役に立つことがあります。

Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.