web-dev-qa-db-ja.com

Googleドキュメントは編集の衝突にどのように対処しますか?

私は、Googleドキュメントと同様の機能(複数の人が同時に作業できるようにする)を備えた独自のJavascriptエディターを作成することをいじくり回してきました。私が理解していないことの1つ:

ユーザーAとユーザーBが10msのネットワーク遅延で互いに直接接続されているとしましょう。エディターが(ドキュメントが理解しているように)差分システムを使用していると仮定します。このシステムでは、編集は「インデックス3に「テキスト」を挿入」のように表され、差分にはタイムスタンプが付けられ、すべてのクライアントによって時系列で適用されます。

「xyz123」というテキストを含むドキュメントから始めましょう。

ユーザーAはタイムスタンプ001msでドキュメントの先頭に「abc」と入力し、ユーザーBはタイムスタンプ005msで「xyz」と「123」の間に「hello」と入力します。

どちらのユーザーも、結果が「abcxyzhello123」になることを期待しますが、ネットワーク遅延を考慮すると次のようになります。

  • ユーザーBは、時間011msにユーザーAによる「インデックス0の挿入 'abc'」の編集を受け取ります。時系列を維持するために、ユーザーBはインデックス3でユーザーBの挿入を元に戻し、インデックス0でユーザーAの「abc」を挿入してから、インデックス3でユーザーBの挿入を再挿入します。インデックス3は「abc」と「xyz」の間にあります。 、」したがって、「abchelloxyz123」を与える
  • ユーザーAは、ユーザーBによる「インデックス3に「hello」を挿入」の編集を015msの時間に受信します。ユーザーBの挿入がユーザーAの後に行われたことを認識し、インデックス3(現在は「abc」と「xyz」の間に)に「hello」を挿入するだけで、「abchelloxyz123」が得られます。

もちろん、「abchelloxyz123」は「abc」と同じではありません。 xyzhello123 "

文字通りすべてのキャラクターに独自のIDを割り当てる以外に、Googleがこの問題を効果的に解決する方法を想像することはできません。

私が考えたいくつかの可能性:

  • ユーザーBが編集の1ミリ秒前に挿入ポイントを移動した場合にまったく同じ問題が発生することを除いて、差分を使用してインデックスを送信する代わりに挿入ポイントを追跡することは機能します。
  • ユーザーBに「 'xyz'の後に挿入」などの情報を送信して、ユーザーAがこれが発生したことをインテリジェントに認識できるようにすることもできますが、ユーザーAがテキスト「xyz」を挿入するとどうなりますか?
  • ユーザーBは、これが発生したことを認識し(ユーザーAの差分を受信し、それが競合であると判断した場合)、ユーザーBの編集を元に戻す差分と、ユーザーBの「hello」「abc」.lengthインデックスをさらに挿入する新しい差分を送信できます。正しい。これに伴う問題は、(1)ユーザーAがテキストに「ジャンプ」を表示し、(2)ユーザーAが編集を続ける場合、ユーザーBはその差分を継続的に修正する必要があることです-「修正」差分でさえオフであり、必要です修正するには、複雑さが指数関数的に増加します。
  • ユーザーBは、最後にタイムスタンプが付けられたdiffが-005msか何かであるというプロパティを、そのdiffと一緒に送信できます。その後、AはBがその変更について知らなかったことを認識し(Aのdiffは001msであったため)、競合解決を行います。問題は、(1)ほとんどのコンピュータークロックがミリ秒に対して正確ではないことを考慮して、すべてのユーザーのタイムスタンプがわずかにずれていること、および(2)ユーザーAと25ミリ秒遅れているがユーザーBと2ミリ秒遅れている3番目のユーザーCがある場合、ユーザーCが-003msで「x」と「y」の間にテキストを追加すると、ユーザーBはユーザーCの編集を参照ポイントとして使用しますが、ユーザーAはユーザーCの編集(したがってユーザーBの参照ポイント)について知りません。 22msまで。共通のサーバーを使用してすべての編集にタイムスタンプを付けると、これは解決できると思いますが、それはかなり複雑なようです。
  • 各キャラクターに一意のIDを付けて、インデックスの代わりにそれらのIDを処理することもできますが、それはやり過ぎのようです...

http://www.waveprotocol.org/whitepapers/operational-transform を読んでいますが、この問題を修正するためのあらゆるアプローチを聞きたいと思います。

24
MatthewSot

シナリオのトポロジとさまざまなトレードオフに応じて、レプリカの同時変更を実現するためのさまざまな可能性があります。

中央サーバーの使用

最も一般的なシナリオは、すべてのクライアントが通信する必要がある中央サーバーです。

サーバーは、各参加者のドキュメントがどのように見えるかを追跡できます。次に、AとBの両方が、変更を含む差分をサーバーに送信します。次に、サーバーは変更をそれぞれの追跡ドキュメントに適用します。次に、3方向マージを実行し、変更をマスタードキュメントに適用します。次に、マスタードキュメントとトラッキングドキュメントの差分をそれぞれのクライアントに送信します。これは 差分同期 と呼ばれます。

別のアプローチはoperation(al)変換と呼ばれ、従来のバージョン管理システムでのリベースに似ています。中央サーバーは必要ありませんが、参加者が3人以上の場合は、中央サーバーを使用すると作業がはるかに簡単になります( OT FAQ を参照)。要点は、ある編集で変更を変換して、別の編集の変更がすでに行われたと編集が想定するようにすることです。例えば。 Aは、Bの編集insert(3, hello)をその編集insert(0, abc)に対して変換し、結果はinsert(6, hello)になります。

リベースとOT)の違いは、異なる順序で編集を適用した場合、リベースは一貫性を保証しないことです(たとえば、BがAの編集を逆にリベースした場合、これは一方、OT)の約束は、適切な変換を行う場合に任意の順序を許可することです。

中央サーバーなし

ピアツーピアシナリオを処理できるOTアルゴリズムが存在します(制御層での実装の複雑さの増加とメモリ使用量の増加のトレードオフがあります)。単純なタイムスタンプの代わりに、 バージョンベクトル を使用して、編集の基になっている状態を追跡できます。次に(OTアルゴリズム、具体的には変換プロパティ2)の機能に応じて)、受信した編集を次のように変換できます。受信した順序に合わせるか、バージョンベクトルを使用して編集に半順序を課すことができます。この場合、編集を元に戻して変換することにより、履歴を「書き換え」て、課された順序に従う必要があります。バージョンベクトルによって。

最後に、 [〜#〜] crdt [〜#〜] に基づくWOOT、Treedoc、Logootと呼ばれるアルゴリズムのグループがあり、操作を可能にする特別に設計されたデータ型で問題を解決しようとします。通勤するので、それらが適用される順序は重要ではありません(これは、各文字のIDの考え方に似ています)。ここでのトレードオフは、メモリ消費と操作構築のオーバーヘッドです。

36
Marcel Klehr