コンテンツにスキップ
FDL
Esc
↑↓移動↵開く⌘Jプレビュー
このページの内容

書いてから読まれるまで

1 つの変更を作業カタログから読み手まで追い、各段階のリクエストと、FDL の各部分が Git の何に当たるかを示します。

このページでは、1 つの変更を書き手のマシンから読み手まで追い、各段階で送るリクエストを示します。各段階でリポジトリに残るファイルはリポジトリの中身に、決まりは仕様にあります。

各部分と Git での呼び名

FDL は Git の役割と名前をそのまま使い、保存の仕方は独自に決めています。Git がコミットをどう持つかを知っていれば、この表でリポジトリを読めます。

FDL Git 補足
リポジトリ(ストアの 1 つのプレフィックス) サーバー上のベアリポジトリ サーバーのプロセスは要りません
fdl.json config 形式、リポジトリの id、data_dir を持ちます
versions/<ID>/version.json コミット 親、作者、メッセージ、カタログの sha256 を持ちます
versions/<ID>/ducklake.duckdb ツリー どのテーブルがあり、それぞれがどのデータファイルでできているかを持ちます
データファイル(Parquet) ブロブ 一度書いたら変えません。1 つのファイルを多くのバージョンが指せます
refs/heads/<name> ブランチ 条件付き書き込みで、前にだけ進みます
refs/tags/<name> タグ 一度作ったら動かしません
HEAD HEAD 既定のブランチの名前を持ちます
refs.json info/refs 素の HTTP の読み手が ref を名前で探せるようにします
versions/<ID>/files.json 到達可能性ビットマップ カタログから作る索引です。読み手はこれが無くても読めます
version.json.sig 署名付きコミット ssh-keygen -Y sign で作る SSH 署名です
作業カタログ(SQLite) 作業ツリーとインデックス バージョンから作り、次のバージョンにします
POST <root>/?batch Git LFS の batch API ストレージへ直接アップロードする URL を渡します
expired.json なし バージョンにカタログが無くなった理由を書きます

次の 3 つは、意図して Git と変えています。

  • バージョンの ID は UUIDv7 で、中身のハッシュではありません。DuckDB のリリースが違うと、同じカタログでも違うバイト列で書かれることがあり、中身のハッシュは道具によって変わってしまうためです。中身の確認には version.json の sha256 を使います
  • packfile はありません。ファイルはそれぞれストアの 1 つのオブジェクトなので、読み手は Parquet ファイルのうち要る部分だけを取りに行けます
  • マージはありません。バージョンの親は 1 つです。push を断られた書き手は、新しいバージョンの上で作り直します

バージョンを書く

checkout

fdl checkout は HEAD を読み、それが指す ref を読み、そのバージョンの version.json を読んでから、カタログを書き手のマシンの SQLite ファイルに写します。これが作業カタログです。バージョンに expired.json があれば、checkout は止まります。

作業カタログのデータパスは、ストアによって変わります。

  • フォルダか S3: リポジトリのデータフォルダです。DuckDB は新しい Parquet ファイルをリポジトリへ直接書きます
  • HTTP: 書き手のマシンのフォルダです。リポジトリに既にあるファイルは data_dir の下の URL で指し、HTTP で読みます

書く

書き手は作業カタログに対して、DuckDB で直接、または dbt などの道具を通して SQL を流します。文ごとに作業カタログに DuckLake のスナップショットができます。読み手から見えるものはまだ何も変わりません。新しい Parquet ファイルがリポジトリに書かれていても、どのバージョンもそれを指していないためです。

commit

fdl commit は作業カタログを DuckDB ファイルに写し、その data_path をリポジトリのデータフォルダにし、そこから dataset.json を作り、新しい ID と、親として前のバージョンを持つ version.json を書きます。テーブルの行が親と同じなら、コミットしません。新しいバージョンは、push するまで書き手のマシンにしかありません。

push

push は、読み手を守る順にファイルを書きます。あるファイルが指す先は、そのファイルより先に書きます。

  1. リポジトリに無い新しいデータファイル
  2. versions/<ID>/ducklake.duckdb
  3. versions/<ID>/dataset.json と files.json
  4. versions/<ID>/version.json
  5. ref。If-Match: <1 の前に読んだ ETag> で書きます。最初のバージョンなら If-None-Match: * です
  6. refs.json。HEAD とすべての ref を読み直して書き直します

読み手は ref から読み始め、ref は 5 で書きます。5 より前で止まった push は、どの ref も指さないファイルを残すだけで、読み手からは見えません。1 のあとに別の書き手が ref を動かしていれば、ETag が合わず、ストアは 5 を断ります。断られた書き手は checkout し直し、新しいバージョンの上で作り直します。

refs.json は ref のあとに書くので、少しの間は ref より古いことがあります。そのため、今のバージョンが要る読み手は ref 自体を読みます。

HTTP での push

HTTP では、すべてのキーがリポジトリの URL の下のパスになり、ホストがそこへの GET や PUT などに答えます(HTTP での pushを参照)。順番は上と同じで、2 つの段階が変わります。

  • 1 の前に、書き手はこれから上げるファイルのキーと大きさを POST <root>/?batch で送ります。ホストはファイルごとに、アップロード用の URL を返すか、既にあると答えるか、ホスト経由で送らせるために 413 を返します。URL をもらったファイルは PUT でストレージへ直接送られ、データはホストを通りません。batch の無いホストは 404・405・501 のどれかを返し、すべてのファイルがホストを通ります
  • 5 で、ホストは ref を動かす前に push を確かめます。バージョンがそろっていること、その親が今 ref の指すバージョンであること、カタログが version.json と合うこと、カタログが指すデータファイルがすべてリポジトリにあることです。確認に通らなければ、理由を付けて 409 を返します。ref を自分で動かすホストは refs.json も自分で書き、書き手が書こうとすると 403 を返します

ホストは、書き手に ref を一切動かさせないこともできます。その場合、書き手はバージョンを上げて 4 で止まり、ホストがあとで、たとえば誰かが変更を承認したときに ref を動かします。

バージョンを読む

読み手に要るのは GET だけです。誰がリポジトリを書いたかも、前にホストがいるかどうかも知らなくて構いません。

  1. HEAD か、求められた ref を読みます。HEAD は ref: refs/heads/main を持つので、次に refs/heads/main を読みます。ref はバージョンの ID を持ちます
  2. versions/<ID>/version.json を読みます。id が求めた ID と同じで、fdl のメジャーバージョンを読み手が知っていることを確かめます
  3. versions/<ID>/expired.json があるか確かめます。あれば止まります。そのバージョンは保持期間を過ぎたか取り下げられていて、カタログはもうありません
  4. リポジトリのデータフォルダをデータパスにして、カタログを読み取り専用で ATTACH します
ATTACH 'ducklake:https://example.org/lake/versions/019a3c4e-…/ducklake.duckdb' AS lake (
  DATA_PATH 'https://example.org/lake/data/', OVERRIDE_DATA_PATH true, READ_ONLY
);

ここから先は DuckDB がカタログを読み、クエリに要る Parquet ファイルの部分を取りに行きます。fdl url は 1 から 3 までを行い、4 で使うカタログの URL を出します。

読み手はカタログに書かれたデータパスを使わず、自分で渡します。カタログが持つのは書き手が公開した場所の URL で、ミラーは同じファイルを別の場所から配るためです。読み手がデータパスを渡せば、同じ文が元のホストでもどのミラーでも動きます。

バージョンのカタログは変わらないので、ずっとキャッシュできます。HEAD・refs/・refs.json は Cache-Control: no-store で配るので、読み手は ref が今どこを指しているかを必ず知ることができます。

FDL を知らない読み手

ホストは、HEAD が指すバージョンのカタログを <root>/ducklake.duckdb で配ってもかまいません。DuckLake しか知らない読み手は、その 1 つの URL を ATTACH できます。ホストは FDL-Version ヘッダーでバージョンの ID を送るので、読み手はどのバージョンを受け取ったかを知ることができます。ストアのそのキーにはファイルがありません。ホストが versions/ のカタログを配ります。

バージョンを消す

fdl expire は古いバージョンを保持期間から外します。先に expired.json を置き、次にカタログを消し、version.json は残します。そのため fdl log はそのバージョンと履歴の中の位置を表示し続けます。どの生きているバージョンも指さないデータファイルは、猶予期間のあとで消します。書き手がそのファイルを指すバージョンをこれからコミットするかもしれないためです。

取り下げは、間違って公開した個人データなど、公開してはいけないデータを、それを持つすべてのバージョンから消します。書き手はまず、そのデータを除いた新しいバージョンを作り、影響を受けるブランチをそこへ前に進め、影響を受けるバージョンを指すタグを消します。次に、そのデータを持つデータファイルを猶予期間を置かずにすぐ消し、影響を受けるバージョンに "reason": "withdrawn" の expired.json を置いてカタログを消します。手順はデータファイルにあります。

保持期間を過ぎたか取り下げられたバージョンにたどり着いた読み手は、読む手順の 3 で止まり、理由を伝えます。

このページは役に立ちましたか?