CRDT(共同編集のしくみ)
複数の端末でオフラインのまま同じ文書を編集し、つながったときに必ず同じ内容にそろう CRDT のしくみ。文字ごとの ID と並べ方、位置だけを送った場合のずれ、カウンターの CRDT を確かめられます。
例(オフラインで編集してからつないだところを表示します)
A は「ねこ」の前に「白い」、B は末尾に「です」を、どちらもオフラインで入力してからつなぎます。位置だけを送ると、A が先に文字を足したぶん B の「末尾」の位置がずれて、文の途中に入ってしまいます。
端末 A
オンライン(変更はすぐ届きます)
文字と ID(数字はランポート時計、英字は作った端末)
位置だけを送った場合
白いねこがですすき
端末 B
オンライン(変更はすぐ届きます)
文字と ID(数字はランポート時計、英字は作った端末)
位置だけを送った場合
白いねこがすきです
CRDT: そろっている
すべての端末が同じ内容です
位置だけを送った場合: 食い違い
全員が同じ変更を受け取ったのに、端末ごとに内容が食い違っています
カウンターの CRDT(いいねの数など)
3 台がそれぞれ +1 / −1 してから「同期」を押します。数だけを送って最後に書いた値で上書きすると、ほかの端末で足した分が消えます。PN-Counter は端末ごとに「足した数」と「引いた数」を分けて持ち、同期では端末ごとに大きいほうをとるので、何も失われません。
端末 A
PN-Counter: 0
P = {A:0, B:0, C:0} N = {A:0, B:0, C:0}
数だけ(後勝ち): 0
端末 B
PN-Counter: 0
P = {A:0, B:0, C:0} N = {A:0, B:0, C:0}
数だけ(後勝ち): 0
端末 C
PN-Counter: 0
P = {A:0, B:0, C:0} N = {A:0, B:0, C:0}
数だけ(後勝ち): 0
使い方
- 例を選ぶと、端末をオフラインにして別々に編集し、オンラインに戻して同期したところを表示します。
- 各端末の入力欄は自由に書き換えられます。「オンライン / オフライン」で端末を切り離し、別々に編集してから戻してみましょう。オンラインの端末どうしは、編集するとすぐに同期します。
- 入力欄の下には、文字ごとの ID(ランポート時計の数と、作った端末の名前)と、どの文字の直後に入れたか(←)を表示します。
- 「位置だけを送った場合」は、「3 文字目に X を入れた」のように位置と文字だけを相手に送り、届いた順に当てはめる素朴な方式の結果です。
CRDT とは
CRDT(Conflict-free Replicated Data Type)は、複数の端末がそれぞれ手元のデータを書き換えても、互いの変更を受け取りさえすれば、受け取った順番によらず必ず同じ状態にそろうように設計されたデータ構造です。マージが「順番を入れ替えても同じ(交換法則)」「まとめ方を変えても同じ(結合法則)」「同じものを何度マージしても同じ(冪等)」を満たすので、中央のサーバーで順番を決めなくても、オフラインで編集してあとから同期できます。
文字列の CRDT(RGA)
位置だけを送ると、相手がその前に文字を足したり消したりしていた場合に位置がずれてしまいます。RGA では文字ごとに世界で 1 つの ID を付け、「何文字目」ではなく「どの文字の直後」に入れたかを記録します。同じ文字の直後に同時に入った文字は、ID の大きいほう(新しいほう、同じなら端末名の大きいほう)を前にする、という全員共通の規則で並べます。消した文字はすぐには捨てず印を付けて残すので、消された文字の直後に入れた文字も迷子になりません。
共同編集でよく使われるもう 1 つの方法は、届いた操作の位置を相手の操作に合わせて変換する OT(Operational Transformation)です。Google ドキュメントは OT、Figma や Automerge・Yjs などは CRDT の考え方を使っています。このアプリは 1 文字ずつの単純な形で、実際のライブラリは文字をまとめて持つなどしてメモリと通信量を抑えています。