私はそこにクライアントとサーバーの同期パターンがなければならないと感じています。しかし、私は完全にグーグルアップすることに失敗しました。
状況は非常に単純です。サーバーは中央ノードであり、複数のクライアントが同じデータに接続して操作します。データはアトムに分割することができ、競合が発生した場合、サーバー上にあるものが優先されます(ユーザーが競合を解決できないようにするため)。潜在的に大量のデータがあるため、部分同期が推奨されます。
そのような状況のためのパターン/グッドプラクティスはありますか、またはあなたが知らない場合-あなたのアプローチは何ですか?
以下は、私がそれを解決するためにどのように考えるかです:データと並行して、修正ジャーナルが保持され、すべてのトランザクションにタイムスタンプが付けられます。クライアントが接続すると、最後のチェック以降のすべての変更を統合形式で受信します(サーバーはリストを調べ、削除が続く追加を削除し、各アトムの更新をマージします)。出来上がり、私たちは最新です。
別の方法として、各レコードの変更日を保持し、データ削除を実行する代わりに、削除済みとしてマークするだけです。
何かご意見は?
分散変更管理の仕組みを確認する必要があります。 SVN、CVS、およびデルタ作業を管理する他のリポジトリを見てください。
いくつかのユースケースがあります。
変更を同期します。これには、変更ログ(またはデルタ履歴)アプローチが適しています。クライアントは、デルタをサーバーに送信します。サーバーは、デルタを統合してクライアントに配布します。これは典型的なケースです。データベースはこれを「トランザクションレプリケーション」と呼びます。
クライアントは同期を失いました。バックアップ/復元またはバグが原因です。この場合、クライアントは、デルタを経由せずにサーバーから現在の状態を取得する必要があります。これは、マスターからディテールへのコピーであり、デルタとパフォーマンスがひどいものです。それは一度限りのものです。クライアントが壊れています。これを最適化しようとせず、信頼できるコピーを実装するだけです。
クライアントは不審です。この場合、クライアントとサーバーを比較して、クライアントが最新であり、デルタが必要かどうかを判断する必要があります。
データベース(およびSVN)の設計パターンに従って、すべての変更に順番に番号を付ける必要があります。そうすれば、クライアントは同期を試みる前にささいな要求(「どのリビジョンが必要ですか?」)を行うことができます。そして、それでも、クエリ( "2149以降のすべてのデルタ")は、クライアントとサーバーが処理するのがとても簡単です。
チームの一員として、データの同期を伴うプロジェクトを非常に多く行ったため、この質問に答える能力が必要です。
データの同期は非常に広い概念であり、議論することは多すぎます。さまざまなアプローチの利点と欠点をカバーしています。同期/非同期、クライアント/サーバー/ピアツーピアという2つの観点に基づいた可能な分類の1つを次に示します。同期の実装は、これらの要因、データモデルの複雑さ、転送および保存されるデータ量、およびその他の要件に大きく依存します。そのため、特定の各ケースでは、アプリの要件を満たす最も単純な実装を選択する必要があります。
既存の既製のソリューションのレビューに基づいて、同期の対象となるオブジェクトの粒度が異なるいくつかの主要な同期クラスを説明できます。
そこで、この記事に知識を取り入れました。これは、トピックに興味があるすべての人にとって非常に役立つと思います=> Core Data Based iOSアプリでのデータ同期( http://blog.denivip ru/index.php/2014/04/data-syncing-in-core-data-based-ios-apps /?lang = en )
本当に必要なのは Operational Transform (OT)です。これは多くの場合、競合にも対応できます。
これはまだ活発な研究分野ですが、さまざまなOTアルゴリズムの実装があります。私はこのような研究に何年も関わってきましたので、このルートがあなたに興味があるかどうかをお知らせください。関連するリソースを紹介させていただきます。
質問は明確ではありませんが、私が 楽観的ロック を検討します。サーバーが各レコードに対して返すシーケンス番号を使用して実装できます。クライアントがレコードを保存しようとすると、サーバーから受信したシーケンス番号が含まれます。シーケンス番号が、更新を受信した時点でデータベースにあるものと一致する場合、更新が許可され、シーケンス番号が増分されます。シーケンス番号が一致しない場合、更新は許可されません。
約8年前にアプリ用にこのようなシステムを構築しましたが、アプリの使用量の増加に応じて進化したいくつかの方法を共有できます。
まず、あらゆるデバイスからのすべての変更(挿入、更新、または削除)を「履歴」テーブルに記録することから始めました。たとえば、誰かが「連絡先」テーブルの電話番号を変更した場合、システムはcontact.phoneフィールドを編集し、action = update、field = phone、record = [contact ID]の履歴レコードも追加します。値= [新しい電話番号]。その後、デバイスが同期するたびに、最後の同期以降の履歴アイテムがダウンロードされ、ローカルデータベースに適用されます。これは、上記の「トランザクション複製」パターンのように聞こえます。
1つの問題は、異なるデバイスでアイテムを作成できる場合にIDを一意に保つことです。これを始めたとき、UUIDを知らなかったため、自動インクリメントIDを使用し、中央サーバーで実行される複雑なコードを記述して、デバイスからアップロードされた新しいIDを確認し、競合がある場合は一意のIDに変更します。ローカルデータベースのIDを変更するようにソースデバイスに指示します。新しいレコードのIDを変更するだけでもそれほど悪くはありませんでしたが、たとえば、contactテーブルに新しいアイテムを作成し、イベントテーブルに新しい関連アイテムを作成した場合、外部キーも必要になります。確認して更新します。
最終的に、UUIDでこれを回避できることを知りましたが、その頃にはデータベースがかなり大きくなり、UUIDの完全な実装がパフォーマンスの問題を引き起こすことを恐れていました。したがって、完全なUUIDを使用する代わりに、ランダムに生成された8文字の英数字キーをIDとして使用し始め、競合を処理するために既存のコードをそのまま残しました。私の現在の8文字のキーとUUIDの36文字の間のどこかに、不必要な肥大化なしに競合を排除するスイートスポットがなければなりませんが、競合解決コードを既に持っているので、それを試すことは優先事項ではありませんでした。
次の問題は、履歴テーブルが他のデータベース全体よりも約10倍大きいことでした。これにより、ストレージが高価になり、履歴テーブルのメンテナンスが面倒になる場合があります。テーブル全体を保持することで、ユーザーは以前の変更をロールバックできますが、やり過ぎのように感じ始めました。そこで、同期プロセスにルーチンを追加しました。デバイスが最後にダウンロードした履歴アイテムが履歴テーブルに存在しない場合、サーバーは最近の履歴アイテムを提供せず、代わりにすべてのデータを含むファイルを提供しますそのアカウント。次に、cronジョブを追加して、90日より古い履歴項目を削除します。つまり、ユーザーは90日以内の変更を引き続きロールバックでき、90日ごとに少なくとも1回同期すると、更新は以前と同様に増分されます。ただし、90日以上待機すると、アプリはデータベース全体を置き換えます。
この変更により、履歴テーブルのサイズがほぼ90%縮小されたため、履歴テーブルを維持すると、データベースは10倍ではなく2倍になります。このシステムのもう1つの利点は、必要に応じて履歴テーブルがなくても同期が機能することです。たとえば、一時的にオフラインにするメンテナンスを行う必要がある場合などです。または、異なる価格帯のアカウントに異なるロールバック期間を提供できます。また、ダウンロードする変更が90日を超える場合、通常、完全なファイルの方が増分形式よりも効率的です。
今日からやり直す場合、IDの競合チェックをスキップして、競合を排除するのに十分なキー長を目指し、念のため何らかのエラーチェックを行います。ただし、履歴テーブルと、最近の更新の増分ダウンロードまたは必要な場合の完全ダウンロードの組み合わせは、うまく機能しています。