プロンプトと適用範囲
ある注文レイクテーブルが、バッチジョブ、ストリーミングジョブ、アドホックなアナリストによって読み取られています。ビジネス要件として、ネストされたフィールドの追加、カラムの名前変更、および月単位から日単位のパーティショニングへの段階的な変更が求められています。古いジョブを一度にアップグレードすることはできず、すべての履歴ファイルを書き換えるのはコストが高すぎます。Iceberg がこれらの変更をどのように記録し、新旧のレイアウトが共存する中でリーダーの正確性を維持するかを説明してください。
Iceberg Catalog と、対象の Iceberg バージョンをサポートするエンジンを前提とします。面接では、特定の Spark SQL の構文ではなく、テーブルフォーマットのスキーマおよびパーティション進化が問われます。
面接官が評価している点
- フィールドID、Schema ID、Partition Spec ID、スナップショットを区別できているか。
- 追加、削除、名前変更、および特定の型プロモーションが古いファイルを書き換える必要がない理由と、その限界を説明できるか。
- パーティションレイアウトの共存と、複数の仕様にまたがるプランニング(書き換えが依然として有用な場合を含む)を説明できるか。
- 互換性、パフォーマンス、同時コミット、およびロールバックの検証方法を提案できるか。
最初に明確にすべき質問
- リーダーは Iceberg テーブルフォーマットを使用していますか、それとも単にディレクトリを Hive テーブルとして扱っていますか?後者はフィールドIDセマンティクスを自動的に提供しません。
- 変更はトップレベル、ネスト、またはパーティション変換のいずれですか?ネストされたフィールドやパーティションフィールドには追加の制約があります。
- 古いリーダーは位置によってカラムをバインドしていますか、それとも古いスキーマをキャッシュしていますか?エンジンが Iceberg のフィールドマッピングを尊重していることを確認してください。
- 目的はスキャン数の削減、ホットスポットの解消、あるいは論理的なスキーマ変更のみですか?パーティションの利点にはクエリによる裏付けが必要です。
30秒の回答フレームワーク
「Iceberg はテーブルの状態をバージョン管理されたメタデータに保存し、位置や使い回された名前ではなく、再利用されないフィールドIDでカラムをマッピングします。スキーマの変更は Schema ID を作成し、パーティションの変更は Partition Spec ID を作成します。古いファイルはそのレイアウトを保持し、新しい書き込みは新しい仕様を使用し、リーダーは隠蔽パーティションプルーニング(hidden partition pruning)を使用しながら各仕様をプランニングします。私は互換性チェックを実行し、メタデータをアトミックに公開した上で、新旧のリーダー、名前変更の正確性、スキャンされたファイル、失敗したリトライ、スナップショットのロールバックをテストします。『ファイルの再書き込みなし』は移行上の特性であり、パフォーマンスコストがゼロであることの保証ではありません。」
ステップごとの分析
1. カラムのアイデンティティにフィールドIDを使用する
Iceberg は各フィールドに、テーブル内で決して再利用されない ID を割り当てます。名前変更によって名前が変わっても、リーダーは ID によって元のフィールドを見つけることができます。削除された名前を再追加すると新しい ID が取得されるため、古いファイルの値が暗黙のうちに復活することはありません。位置ベースのフォーマットでは削除や順序変更を安全に処理できず、名前の再利用もデータの誤マッピングを引き起こす可能性があります。
Old schema: id=17, name="customer_id"
New schema: id=17, name="account_id"
Added field: id=42, name="region"2. Schema ID とフィールドIDを分離する
フィールドIDは「これはどのカラムか?」に答えます。Schema ID は「これはテーブル構造のどのバージョンか?」に答えます。進化によって新しいスキーマオブジェクトが作成され、その Schema ID がカレントになります。スナップショットは書き込み時に使用されたスキーマを記録します。リーダーはキャッシュされたカラム名の配列に安全に依存することはできません。
3. どのスキーマ変更が安全かを判断する
追加、削除、名前変更、順序変更、および特定の拡張操作はサポートされていますが、すべての型変更が安全なわけではありません。フォーマットバージョン、値の範囲、およびパーティション変換を確認してください。バケット変換で使用されているフィールドは、変換結果が変わる場合、型プロモーションできない可能性があります。マップキーの構造変更にも等価性の制約があります。
Safe candidate: add an optional field, rename a non-partition field, int -> long when transform output is unchanged
Block or redesign: narrowing a type, changing bucket input semantics, dropping a field required by critical readers4. 新旧のパーティション仕様を共存させる
パーティションの進化により、新しい Partition Spec ID が作成されます。古いファイルは古い仕様を保持し、新しいファイルは新しいデフォルトを使用します。リーダーは各ファイルをその仕様を使用して解釈し、結果を結合する必要があります。隠蔽パーティショニングにより、日付ディレクトリをハードコードする代わりに、クエリでデータ値に対する述語を表現できます。
5. クエリパフォーマンスに対する「書き換えなし」の評価
メタデータの進化によりデータファイルの再書き込みが回避され、移行コストが削減されますが、古いファイルには依然として古い物理レイアウトが残ります。複数の仕様が存在すると、プルーニングの品質が異なる複数のスプリットプランが生成される可能性があります。スキャンされたファイル、プランニング時間、読み取りバイト数、タスクの偏り(スキュー)、小さなファイルの数を比較します。古いレイアウトがホットスポットのままである場合は、論理的な進化が物理データを再編成したかのように装うのではなく、範囲を限定した書き換えをスケジュールします。
6. アトミックコミットとスナップショットで公開を保護する
テーブルの状態はメタデータファイルとスナップショットによって表され、更新は現在のメタデータポインタをアトミックに置き換えます。現在のバージョンを読み取り、そこから新しいメタデータを構築してコミットします。競合が発生した場合は、リフレッシュして再試行します。変更レコードをスナップショット ID に紐付けることで、問題のあるロールアウトが発生しても、古いリーダーの互換性ウィンドウを維持しながら、検証済みのスナップショットに戻ることができます。
質の高い模範解答
カラムのアイデンティティ、構造バージョン、物理レイアウトを分離して考えます。フィールドIDは、名前の変更や順序の変更によって古いファイルの値が誤ってマッピングされるのを防ぎます。Schema ID は構造バージョンを記録し、Partition Spec ID はパーティション変換を記録します。region の追加や customer_id の名前変更はメタデータの作業であり、ファイルの書き換えではありませんが、まずすべてのエンジンがフィールドIDによって読み取っていることを確認します。
月単位から日単位へのパーティショニング変更では、新しい仕様を作成して新しい書き込みに使用しつつ、既存のファイルには古い仕様を保持します。プランナーは各仕様のパーティション式を適用し、スプリットをマージする必要があります。公開する前に、旧リーダー/新リーダーのマトリクスを実行し、名前変更前後で値を比較し、スキャンされたファイルとバイト数を測定し、分離されたブランチまたはカタログトランザクションからコミットします。競合やクエリのパフォーマンス低下が発生した場合はスナップショットでロールバックし、古いレイアウトが遅いままの場合は、後で予算を組んで書き換えを実行します。
よくある間違いと改善策
- 間違い → カラムの名前変更をファイル位置の変更として扱う → 失敗する理由 → 位置バインディングは誤った値を紐付ける可能性がある → 改善策 → 安定したフィールドアイデンティティを説明し、エンジンのマッピングを検証する。
- 間違い → パーティション進化後に新しいディレクトリのみをスキャンする → 失敗する理由 → 古いファイルもテーブルの一部として残るため、結果が不完全になる可能性がある → 改善策 → すべての Partition Spec を保持し、仕様を認識したプランニングを使用する。
- 間違い → 「書き換えなし」を「パフォーマンスコストなし」として扱う → 失敗する理由 → 複数のスプリットや古いレイアウトにより、依然としてスキャンが増加する可能性がある → 改善策 → プランニング、ファイル、バイト、スキューを測定し、必要に応じて制御されたバッチで書き換える。
- 間違い → 現在のメタデータを直接上書きする → 失敗する理由 → 同時コミットやリトライによって更新が失われる可能性がある → 改善策 → 読み取ったバージョンからアトミックにコミットし、競合時にリフレッシュし、ロールバックポイントを保持する。
フォローアップの質問と回答
フィールドを削除した後に同じ名前を再利用できないのはなぜですか?
名前を再度追加することはできますが、新しいフィールドIDを受け取る必要があります。古い ID を再利用すると、古いファイルの値が新しいフィールドとして表示されてしまい、削除のセマンティクスに違反する可能性があります。古いファイル、新しいファイル、および再作成された名前に割り当てられた ID をテストしてください。
int から long へのプロモーションは常に安全ですか?
いいえ。フォーマットバージョン、値の範囲、ダウンストリームの型、およびそのフィールドがパーティション変換に使用されているかどうかを確認してください。バケットなどの変換が出力を変更する場合、新旧のパーティションセマンティクスが乖離する可能性があるため、変更をブロックするか、まず新しい仕様を設計してください。
月単位の古いファイルと日単位の新しいファイルによって行が見落とされることはありますか?
正しく実装されていればありません。リーダーは各ファイルをその Partition Spec で解釈し、その仕様内でプルーニングを行い、結果を結合します。進化の境界をまたぐ範囲クエリをテストし、行セットをフルスキャンのリファレンスと比較してください。
どのような場合にデータファイルを書き換えますか?
古いレイアウトによってスキャン、スキュー、小さなファイル、またはストレージコストが継続的に発生している場合に書き換えます。スキーマやパーティションのメタデータ進化自体はそれを必要としません。物理的なクリーンアップを可逆的に保つために、スナップショット、並行性制御、および予算を設定して書き換えを実行します。