コンテンツにスキップ

Change Log と Cookie

sync ループは、2 つのデータ構造を使ってブラウザと Worker の間で 状態を動かします。どちらも風変わりなものではありませんが、詳しく 理解する価値があります。停止 / 行の欠落 / 妙な pull として表面化 する問題は、すべて最終的にこのどちらかへ辿れるからです。

サーバー上のすべてのユーザーテーブル書き込みは、_plasma_changes にちょうど 1 行を生成します。

CREATE TABLE _plasma_changes (
row_version BIGINT PRIMARY KEY AUTOINCREMENT,
table_name TEXT NOT NULL,
row_id TEXT NOT NULL,
op TEXT NOT NULL, -- 'put' or 'del'
value TEXT NULL, -- JSON of the full row for 'put', NULL for 'del'
created_at BIGINT NOT NULL, -- unix ms
client_group_id TEXT NULL,
client_id TEXT NULL,
mutation_id BIGINT NULL
)

client_group_id / client_id / mutation_id カラムは、どの client の mutation がその変更を生んだかを識別します(生のドライバ 書き込みや cron ジョブなら NULL)。row_version が pull カーソル です。

changeLogSuppressed: true と宣言されたテーブルは、トリガーを 完全にスキップします — サーバーが追跡はするが決して伝播しない、 ローカルの一時テーブルです。Offline mode を 参照してください。

_plasma_changes は、読み取りごとではなく書き込みごとに成長 します。ユーザーあたり 1 日 10 書き込み・1 万ユーザーのアプリは、 1 日あたり約 10 万行を追加します。放っておくと、やがて遅くなり ます。

compactChangeLog(safeUpToVersion) は、同じ (table_name, row_id) に対する中間の put を最新のものだけに 畳み込み、打ち消し合う put+del のペアを取り除きます。Worker の cron から実行してください。

すべての pull レスポンスは、client が次の pull で送り返す cookie 文字列を運びます。次のような見た目です。

c1:eyJhIjoiMTIzIiwiYiI6IjQ1NiJ9

c1: プレフィックスはエンコーディングのバージョンを識別します。 base64 はリージョン ID をキーとする JSON にデコードされます。

{ "0": "123", "1": "456" }

単一リージョンのデプロイ(一般的なケース)では、オブジェクトは 1 つのキー "0" を持ち、その値は client が観測した最大の row_version です。pull クエリは WHERE row_version > 123 に なります。

マルチリージョンのデプロイはリージョンごとのカーソルを運びます。 各リージョンの SequencerDO が自分の空間で単調増加の id を発行 します。Multi-region guide がトポロジー をカバーしています。

なぜ単なる数値ではないのか?

Section titled “なぜ単なる数値ではないのか?”

2 つの理由があります。

  1. 前方互換性。 将来のプロトコルバージョンは、エンコーディング の契約を壊すことなく、cookie により多くの構造(リージョン カーソル、部分レプリケーションのための causal メタデータ)を 入れられます。
  2. サーバーの曖昧さ。 サーバー上の生のドライバ書き込みは、pull ループが安全にスキップできない row_version に着地するかも しれません。バージョンを cookie で包むことで、サーバーは「この cookie は不透明だ、自分で +1 しようとするな」を契約として エンコードできます。

_plasma_client_mutations — 重複排除の台帳

Section titled “_plasma_client_mutations — 重複排除の台帳”

change log の兄弟です。

CREATE TABLE _plasma_client_mutations (
client_group_id TEXT NOT NULL,
client_id TEXT NOT NULL,
last_mutation_id INTEGER NOT NULL,
PRIMARY KEY (client_group_id, client_id)
)

すべての push がこの行を更新します。すでに観測済みの mutation ID を持つ再送された push は no-op です — client の IDB は outbox エントリを消去しているかもしれませんが、サーバーは二重実行を 拒否します。

pull レスポンス上の lastMutationIDs は、このテーブルを pull している client のグループにフィルタした射影です。そのため client は、自分の outbox エントリのうちどれが確認済みかを正確に知り ます。

origin 一時テーブル — _plasma_origin

Section titled “origin 一時テーブル — _plasma_origin”

plasma が内部で使う 3 つ目のテーブルがあります。現在の mutation の (clientGroupID, clientID, mutationID) を AFTER-write トリガーへ 運ぶ、単一行の一時テーブルです。これが _plasma_changes 上の client_group_id / client_id / mutation_id カラムが埋められる 仕組みです。実装の詳細ですが、_plasma_* テーブル一覧に現れるので、 そこにあると知っておいてください。

file() カラムを使うと、さらに 2 つのテーブルが存在します。

  • _plasma_blobs — アップロードされたハッシュごとに 1 行。 stateabsent / uploading / present / orphaned の いずれかです。
  • _plasma_blob_refs — 逆引きインデックス: (hash, table_name, row_id, column_name)。read-auth の経路 (このハッシュを参照する任意の行が、その行を読める呼び出し側に blob のダウンロードを許可する)と gcOrphanedBlobs() が使い ます。

Files and blobs guide が両方をカバーして います。

  • Push, pull, rebase — これらの 構造がどう読み書きされるか
  • Multi-regionSequencerDO が 関わるとき cookie 構造がどう変わるか