TT Lab
はじめる
学ぶ 学習パス コース

データパイプライン

版を付けて変更し、両側を生かしておく

TT Labで続きを見る

目標

バージョン番号が付いたスキーマでJSON Linesを扱う、契約ツールcontract.pyを作ります。バージョン間の変化を種類ごとに分類し、後方互換と前方互換をAvroのスキーマ解決ルールどおりに判定し、複数のバージョンが混ざって流れる移行期間を、読み取りスキーマ1つで乗り越えます。

なぜ重要なのか

書き込む側と読み取る側は、同じ瞬間には変わりません。その間には、必ず片方だけが新しいコードである期間があり、その期間にもデータは流れ続けます。そのため、スキーマ変更を設計するときに問うべきことは、「この変更は正しいか」ではなく、「どの順序でデプロイしても生き残るか」です。答えは3つに分かれます。デフォルト値のあるフィールドを加えることは両方向に安全で、デフォルト値のない必須フィールドを加えることは、新しい読み取りコードが古いデータを読めなくなり、型の変更は、広げる方向と狭める方向で、ちょうど反対の結果になります。名前の変更は、スキーマだけを見れば追加1つと削除1つで、エイリアスによってのみ1つの出来事に戻ります。ところが、エイリアスは読み取る側のスキーマのものだけが使われるため、名前の変更は片方向にだけ生き残ります。移行期間を越える方法は、1つしかありません。読み取りスキーマにデフォルト値を十分に付けて、古いバージョンまで読めるようにすることです。このラボは、そのデフォルト値1つが何件を生かすかを、数字で示します。採点ツールは、提出された文言を信じません。一時ディレクトリに、採点ツールが作ったスキーマと行を用意して、自分で作ったツールを実際に実行し、分類と判定を、採点ツールが別に実装した値と突き合わせます。フィールド名と型と金額は、実行ごとに変わります。

ステップ

  1. /root/evolve/gen_stream.pyを作成して実行し、/root/evolve/schemasにv1.jsonからv5.jsonまで、/root/evolve/streamにバージョンごとに12行のv1.jsonlからv5.jsonlまでと、5つのバージョンが混ざったmixed.jsonlを作ってください。
  2. /root/evolve/contract.pyにfields <스키마>(プレースホルダーはスキーマです)を作り、バージョン番号とフィールド名、必須・オプション、デフォルト値を出力させてください。
  3. diff <옛 스키마> <새 스키마>(プレースホルダーは、古いスキーマと新しいスキーマです)を追加して、追加と削除を分類させてください。追加は、デフォルト値があるものとないものに分けて出力します。
  4. diffが、新しいバージョンのaliasesを見て、名前の変更を1つの出来事にまとめるようにしてください。まとめられた名前は、追加と削除の一覧から外れます。
  5. diffが、型の変更を、拡大と縮小に分けて出力するようにしてください。プロモーション表にあれば拡大、なければ縮小です。
  6. compat <옛 스키마> <새 스키마>(プレースホルダーは、古いスキーマと新しいスキーマです)を追加して、後方互換と前方互換を判定し、理由を固定されたコードで出力させてください。
  7. read <읽기 스키마> <스키마폴더> <파일>(プレースホルダーは、読み取りスキーマ、スキーマフォルダ、ファイルです)を追加して、複数のバージョンが混ざった行を1つのバージョンとして読ませ、寛容な読み取りスキーマ/root/evolve/reader.jsonを作って、2つの読み取りの違いを/root/evolve/window.jsonに書いてください。
  8. 5つのバージョンの履歴を、/root/evolve/evolve_report.jsonと/root/evolve/evolve_report.mdとして残してください。

参考

5つのバージョンを作って出力する

/root/evolve/gen_stream.pyを作成して実行し、/root/evolve/schemasにv1.jsonからv5.jsonまで、/root/evolve/streamにv1.jsonlからv5.jsonlまで、そして5つのバージョンが混ざったmixed.jsonlを作ってください。

バージョンごとに変わるものを1つずつにしておくと、あとで判定が何を捉えているかが見えます。v2はデフォルト値のあるフィールドを加え、v3はデフォルト値のないフィールドを加え、v4は名前を変えながら(エイリアスを付けます)型を広げ、v5はその型をまた狭めます。行ごとに_vで、どのバージョンかを書いてください。

必須とオプションをデフォルト値で分ける

/root/evolve/contract.pyにfields <스키마>(プレースホルダーはスキーマです)を作り、version・names・required・optional・defaultsをJSONで出力させてください。デフォルト値のあるフィールドがオプションで、ないフィールドが必須です。

namesは宣言順のまま、requiredとoptionalはソートして出力します。defaultキーがあるかないかだけを見ればよく、デフォルト値が空文字列や0でもオプションです。値ではなく、キーの有無で分けます。

加わったものと、なくなったものを分ける

diff <옛 스키마> <새 스키마>(プレースホルダーは、古いスキーマと新しいスキーマです)を追加して、added_with_default・added_required・removedの3つの一覧を出力させてください。3つの一覧とも、ソートして出力します。

名前の集合の差を求めるだけです。加わったフィールドは、新しいバージョンの宣言でdefaultキーがあるかどうかで、2つに分けます。このステップでは、エイリアスと型の変更は、まだ見なくてもかまいません。

名前の変更を1つの出来事にまとめる

diffの応答にrenamedを加えてください。新しいバージョンにだけあるフィールドのaliasesの中に、古いバージョンにだけある名前が入っていれば、その2つは同じフィールドです。まとめられた名前は、added_*とremovedから外れます。

名前の変更は、スキーマだけを見れば、追加1つと削除1つです。エイリアスが、その2つを1つの出来事に戻す唯一の仕組みです。値のサンプルで推測しないでください。ここでは、私たちがエイリアスを書く側なので、推測する理由がありません。

拡大と縮小を分ける

diffの応答にwidenedとnarrowedを加えてください。項目は[새이름, 옛타입, 새타입](プレースホルダーは、新しい名前、古い型、新しい型です)で、古い型が新しい型にプロモーションされれば拡大、それ以外は縮小です。名前が変わりながら型も変わったフィールドまで見ます。

プロモーション表を辞書1つで書いておくと、判定が1行になります。intはlong・float・doubleへ、longはfloat・doubleへ、floatはdoubleへ進みます。同じ型もプロモーションと見なすと、あとで互換性の判定が簡単になります。

後方と前方を別々に判定する

compat <옛 스키마> <새 스키마>(プレースホルダーは、古いスキーマと新しいスキーマです)を追加して、backward・forward・reasonsを出力させてください。理由は、参考の節の4つのコードだけを使い、ソートして出力します。

読み取る側が、書き込む側のデータを読めるかを見る関数を1つ作り、引数を入れ替えて2つの方向を作ってください。後方互換は、読み取る側が新しいバージョンで、前方互換は、読み取る側が古いバージョンです。エイリアスは、読み取る側のスキーマのものだけを使うという点が、ここで結果を分けます。

移行期間を読み取りスキーマ1つで乗り越える

read <읽기 스키마> <스키마폴더> <파일>(プレースホルダーは、読み取りスキーマ、スキーマフォルダ、ファイルです)を追加し、5つのバージョンをすべて読める寛容な読み取りスキーマ/root/evolve/reader.jsonを作ってください。schemas/v5.jsonで読んだ結果と、reader.jsonで読んだ結果を、/root/evolve/window.jsonにstrict・tolerant・amount_totalとして書いてください。

厳格な読み取りスキーマは、デフォルト値のないフィールドのために、古いバージョンを丸ごと捨てます。そのフィールドにデフォルト値を付けると、何件が生き返るかが、このステップの答えです。エイリアスも、読み取る側にあって初めて、古い名前を追いかけます。amount_totalは、寛容なほうの値を書いてください。

バージョンの履歴を1枚に残す

隣り合うバージョンごとにcompatを実行して、/root/evolve/evolve_report.jsonにversions・steps・full・brokenを書き、/root/evolve/evolve_report.mdに、## 어떤 판이 있나、## 어느 방향이 깨지나、## 전환 기간을 어떻게 넘기나、## 다음 판에 지킬 것の4つの節で書いてください(韓国語の見出しで、順に「どんなバージョンがあるか」「どの方向が壊れるか」「移行期間をどう乗り越えるか」「次のバージョンで守ること」を意味します)。

stepsは、{"from": 정수, "to": 정수, "backward": 참거짓, "forward": 참거짓, "reasons": [...]}(プレースホルダーは、整数、真偽値です)の一覧です。fullは両方向が成り立つペア、brokenは片方向でも壊れるペアを、[옛판, 새판](プレースホルダーは、古いバージョンと新しいバージョンです)で入れます。レポートには、移行期間に生き返った件数を、数字で書いてください。