Rustで自作データベースを作る その29: verifyのmismatch codeを追加して、差分の種類を機械可読にする
今日からゴールデンウィーク!!
前回は、verify の schema detail mode を導入して、
verify-table --detailsverify-db --details- missing / unexpected columns
- type / nullable mismatch
- primary key mismatch
- truncation 付き detail report
を追加し、schema mismatch の中身を限定的に確認できるようにしました。
ここまでで verify はかなり育っています。
- summary で一致 / 不一致が分かる
- PK details で key 差分が見える
- schema details で列差分が見える
ただ、ここまで来ると今度は別の課題が見えてきます。
情報は見える。でもプログラムから扱うには、まだ少し人間向けすぎる
たとえば今ほしいのは、こんなことです。
- この mismatch は row count 系なのか
- PK 集合系なのか
- schema 型差分なのか
- nullable 差分なのか
- CLI や CI から安定した code で扱えないか
そこで今回は、verify の mismatch code を追加します。
今回のテーマはこれです。
差分の「説明文」だけでなく、「種類」を機械可読に返す
なぜ details の次に code なのか
ここまでの verify は、人間にとってかなり見やすくなっています。
たとえば text 出力で、
mismatch: schema differs
column_mismatches:
- name
expected_type: String
actual_type: Bytes
のように出れば、読む人には十分分かります。
でも、これを自動化しようとすると少しつらいです。
- summary 文字列を parse したくなる
- details の存在有無を推測に使いたくなる
- mismatch の系統ごとに分岐したいのに安定キーがない
そこで必要なのが、structured code です。
status / code / details は別物
ここでまず整理したいのが、この 3 つの違いです。
status
- success
- mismatch
- failed
mismatch code
- mismatch の種類
- 例:
row_count_mismatch
details
- 具体的な中身
- 例: missing PK や missing column
この 3 つは似て見えてかなり役割が違います。
図にするとこうです。
status
-> 大分類
mismatch code
-> mismatch の種別
details
-> mismatch の中身
この分離がかなり大事です。
今回追加する mismatch code
最初は増やしすぎない方がいいです。
今すでに verify が扱っている範囲だけを code にします。
最小なら、たとえばこうです。
schema_missing_columns
schema_unexpected_columns
schema_type_mismatch
schema_nullable_mismatch
schema_primary_key_mismatch
row_count_mismatch
primary_key_set_mismatch
table_missing_in_target
table_unexpected_in_target
これだけあれば、かなり十分です。
なぜ summary 文字列だけでは足りないのか
文字列だけだと、どうしても不安定になります。
たとえば、
"schema differs""row count and primary key set differ""table missing in target"
こういう文字列は人間には分かりやすいです。
でも機械にとっては扱いづらいです。
- wording が変わるかもしれない
- 多言語化で壊れる
- 部分一致判定が必要になる
一方で enum/code なら、こうです。
row_count_mismatch
primary_key_set_mismatch
かなり安定しています。
今回の全体像
verify report はこう整理できます。
VerifyReport
├─ status
├─ mismatch_codes
└─ details
つまり、
- status は大きな分類
- mismatch_codes は軽量な機械可読 summary
- details は必要時の深掘り
です。
この layering はかなりきれいです。
mismatch code は details の代わりではない
ここも重要です。
mismatch code は details を置き換えるものではありません。
役割はこうです。
mismatch code
- 何系統の問題かを軽く判定する
- details がなくても使える
details
- その問題の中身を掘る
- opt-in の詳細
たとえば、
mismatch_codes:
- schema_type_mismatch
- row_count_mismatch
- primary_key_set_mismatch
だけでも、自動処理にはかなり使えます。
その上で必要なら details を見る、という形です。
今回も default verify に載せてよい
前回の details は opt-in でした。
でも mismatch code は details より軽いです。
だから今回は、default verify でも mismatch code を返してよい です。
これはかなり自然です。
- summary より一歩詳しい
- でも details ほど重くない
- 人にも機械にも useful
つまり verify はこうなります。
default verify
-> status + mismatch_codes
verify --details
-> status + mismatch_codes + details
かなりバランスが良いです。
code は compare 結果から派生させたい
ここも設計上かなり大事です。
mismatch code を details から後付けで作るのではなく、
summary 判定と同じ compare 結果から派生 させるのが良いです。
図にするとこうです。
compare result
├─ status
├─ mismatch_codes
└─ details
これなら、
- summary と code がズレにくい
- code と details がズレにくい
- compare ロジックが一箇所に寄る
かなりきれいです。
たとえば schema type mismatch ならこう出る
例として name 列が String ではなく Bytes だったとします。
この時、verify report ではこうなります。
status: mismatch
mismatch_codes:
- schema_type_mismatch
そして --details ならさらにこうなります。
column_mismatches:
- name
expected_type: String
actual_type: Bytes
つまり、
- code は種類
- details は具体例
という分担がかなり明確です。
row count mismatch と PK set mismatch も code 化する
row 側も同じです。
たとえば row count が違うだけなら、
mismatch_codes:
- row_count_mismatch
PK 集合も違えば、
mismatch_codes:
- row_count_mismatch
- primary_key_set_mismatch
このように複数 code を並べられます。
これがあると、CI や admin script でもかなり扱いやすいです。
success / failed の時はどうするか
ここも決めておきたいポイントです。
-
successの時はmismatch_codes = [] -
failedの時は mismatch code を無理に出さない
これが自然です。
理由は簡単です。
- success に mismatch code は不要
- failed は compare 自体が終わっていないかもしれない
つまり mismatch code は、mismatch 専用の軽量分類 です。
database verify では per-table codes が自然
database verify の時は、やはり per-table に code を持つのが自然です。
たとえばこうです。
users:
mismatch_codes:
- schema_type_mismatch
orders:
mismatch_codes:
- row_count_mismatch
- primary_key_set_mismatch
必要なら DB 全体として union を持ってもよいですが、
まずは per-table が一番実用的です。
text 出力でも十分 useful
CLI text では、こんな形で十分です。
status: mismatch
table: users
mismatch_codes:
- schema_type_mismatch
- row_count_mismatch
- primary_key_set_mismatch
もし --details を付ければ、その下に具体的な内容が続きます。
この layering はかなり分かりやすいです。
JSON 出力は特に強い
JSON では mismatch code の価値がかなり高いです。
たとえばこうです。
{
"status": "mismatch",
"table_name": "users",
"mismatch_codes": [
"schema_type_mismatch",
"row_count_mismatch",
"primary_key_set_mismatch"
],
"details": {
"primary_key_details": { ... },
"schema_details": { ... }
}
}
この形なら、外部ツールからかなり扱いやすいです。
- code だけ見て分岐
- 必要なら details まで読む
この二段構えができます。
文字列 summary は残してよい
ここで気をつけたいのは、code を入れたからといって summary 文字列を消す必要はないことです。
mismatch_summary: "row count and primary key set differ"
mismatch_codes:
- row_count_mismatch
- primary_key_set_mismatch
これは両立してよいです。
- summary: 人間向け
- codes: 機械向け
という役割だからです。
だから文字列を parser にさせない
ただし大事なのは、summary を機械処理の正本にしないことです。
今後使うべきなのは mismatch_codes です。
summary はあくまで人が読む補助。
この立て付けを docs にも明記しておくと良いです。
verify_table_import の内部イメージ
概念的にはこんな感じです。
let schema_compare = compare_schema(...);
let row_count_compare = compare_row_count(...);
let pk_compare = compare_primary_keys(...);
let mismatch_codes = collect_mismatch_codes(
&schema_compare,
&row_count_compare,
&pk_compare,
);
let details = if options.include_details {
Some(build_details(schema_compare, pk_compare, ...))
} else {
None
};
TableVerifyReport {
status: compute_status(...),
mismatch_codes,
details,
...
}
ポイントは、同じ compare 結果から全部を作ることです。
これで mismatch の種類を外から扱いやすくなる
この設計が入ると、たとえば外からはこういう使い方ができます。
-
row_count_mismatchだけなら件数ズレとして扱う -
schema_type_mismatchがあれば schema 差分として扱う -
primary_key_set_mismatchがあれば key 差分として扱う
つまり mismatch を「種類別」に扱えるようになります。
これはかなり大きいです。
end-to-end のサンプルはかなり映える
今回の記事で見せやすいのは、schema と row の両方がズレたケースです。
db-cli verify-table --db ./target-db --in users.json --format json
出力イメージ:
{
"status": "mismatch",
"table_name": "users",
"mismatch_codes": [
"schema_type_mismatch",
"row_count_mismatch",
"primary_key_set_mismatch"
]
}
そして --details を付けると、具体的な差分が付きます。
db-cli verify-table --db ./target-db --in users.json --details
status: mismatch
table: users
mismatch_codes:
- schema_type_mismatch
- row_count_mismatch
- primary_key_set_mismatch
かなり “summary と details の使い分け” が分かりやすいです。
verify の layering がさらに明確になる
ここまで来ると verify はかなり多層ですが、整理するとむしろ分かりやすいです。
status
-> success / mismatch / failed
mismatch_codes
-> どの系統の mismatch か
details
-> mismatch の具体的な中身
この 3 層構造は、かなり自然です。
今回まだやらないこと
ここは明確に書いた方が記事として締まります。
今回 やらない のは、たとえば次です。
- row value diff
- auto repair
- migration proposal
- full diff engine
- checksum tree
- overwrite / merge import
- interactive mismatch explorer
なぜ今はやらないのか。
それは、今回必要なのが
差分の種類を機械可読にすること
だからです。
つまり今欲しいのは、
- mismatch の系統が code で分かる
- details がなくても扱える
- 必要なら details まで深掘れる
- でも full diff には行かない
という最小契約です。
今回の責務分離
今回の mismatch code で見えてくる責務分離は、かなりきれいです。
status
└─ 大分類
mismatch code
└─ mismatch の機械可読な種別
details
└─ mismatch の具体内容
CLI
└─ それを text/json で見せる
compare core
└─ すべての判定の正本
特に大事なのは、
- code が details の代わりをしようとしない
- code と details が同じ比較基盤から出る
- default verify の軽さを壊さない
という点です。
所感
今回のタスクは、かなり “人向けの verify” から “機械にも優しい verify” へ進む回だと思います。
details があると人間にはかなり便利です。
でも自動化や将来の運用を考えると、やはり欲しいのは code です。
- 安定している
- 比較しやすい
- 分岐しやすい
- 文字列 wording に依存しない
この違いは大きいです。
しかも今回は code を details と競合させず、
その手前の軽量分類として置いているのがかなり筋が良いと思います。
まとめ
今回は、verify の mismatch code を導入して、
VerifyMismatchCode- verify report の
mismatch_codes - CLI / JSON での code 表示
- summary / code / details の 3 層化
を追加し、差分の種類を機械可読にできるようにするタスクでした。
要するに、T-028 で作った details の次に、
差分の“系統”を安定した code で扱えるようにする回です。
これで自作DBは、単に export/import/verify できるだけでなく、
ズレた時にそのズレを人にも機械にも扱いやすい形で表現できる小さな DB システム にさらに近づきます。
次は、この基盤の上に row value diff ではなく、まずは verify の code から簡単な remediation hint を任意表示する read-only helper を入れるのが自然なテーマになります。
mismatch の種類が code で分かるようになったので、次は「その code なら次に何を見ればよいか」を軽く案内できるようにすると、さらに運用しやすくなりそうです。
次回もぜひご覧ください!
Discussion