プロンプトと適用されるコンテキスト
Apache Iceberg のイベントテーブルには約 1 PiB のデータがあり、1 日あたり 6 TiB が追加されます。保持期間は 180 日です。すべてのクエリに event_time の範囲指定(通常は 1 〜 7 日間)が含まれており、35% のクエリには 12,000 個ある tenant_id の値の 1 つに対する等価フィルターも含まれています。イベントは最大 48 時間遅れて到着する可能性があり、過去データのバックフィルも発生し得ます。現在パーティション分割されていないテーブルは、スキャンするデータ量が多すぎます。
初期のパーティション仕様を選択し、明らかな代替案がなぜ失敗するのかを示し、プルーニングを単に仮定するのではなくどのように証明するかを説明してください。述語の形式、スキュー、ファイルサイズ、遅延データ、パーティションエボリューション、ロールアウト、およびロールバックについて網羅してください。
すべてのサイズ、パーセンテージ、カーディナリティ、保持期間は面接用の前提条件です。この質問は、データエンジニア、アナリティクスプラットフォームエンジニア、レイクハウスエンジニアを対象としています。コアコンピテンシーは物理データレイアウトとクエリプランニングであるため、カテゴリは data です。
面接官が評価しているポイント
第 1 のシグナルは、ワークロード優先の思考(workload-first reasoning)です。パーティション列は、一般的な述語によってエンジンが物理データをスキップできる場合に有用となります。カーディナリティの高さだけでは優れたパーティションキーにはなりません。
第 2 のシグナルは、粒度の規律(granularity discipline)です。粒度が粗すぎるパーティションは不要なバイトを読み取ります。一方、粒度が細かすぎる、または高カーディナリティなパーティションは、メタデータの肥大化、小さなファイル、書き込みコストの上昇を招きます。優れた回答では、仕様を選択する前にバイト数とパーティション数を概算します。
第 3 のシグナルは、実証(proof)です。日付条件を含むクエリであっても、プルーニングが確実に機能するとは限りません。候補者は、物理プランやドライランの推定値、計画されたファイル数とパーティション数、スキャンされたバイト数、および結果の同一性を確認します。
第 4 のシグナルは、ライフサイクルの認識です。イベント時刻(event time)と取り込み時刻(ingestion time)は異なる問いに対応します。遅延データは過去のイベント時刻パーティションを変更します。また、パーティションエボリューションでは、書き換えが行われるまで、古いファイルは古い仕様のまま残ります。
回答前に確認すべき明確化のための質問
- どの述語が必須かつ選択的(selective)ですか? ほとんどのクエリで時間条件が省略される場合、時間パーティショニングでスキャン範囲を制限することはできません。ほぼすべてのクエリが 1 つのテナントを選択する場合、テナントによるバケッティングの価値が高くなります。
- ビジネス要件はイベント時刻と到着時刻のどちらでフィルタリングしますか? イベント時刻によるパーティショニングはレポート要件と一致しますが、遅延書き込みが発生します。取り込み時刻によるパーティショニングは追記処理を簡素化しますが、1 つの業務日データが複数の到着パーティションに分散する可能性があります。
- テナントの分布はどうなっていますか? 固定ハッシュバケットは高カーディナリティに対応できますが、支配的なテナントが存在するとスキューが発生する可能性があります。専用パスの用意は、実測を行って初めて正当化されます。
- エンジンにはどのようなファイルおよびメタデータの制限がありますか? 目標はパーティション数を最大化することではありません。各アクティブパーティションが有用なファイルを生成するのに十分なバイト数を受け取る必要があります。
- テーブルフォーマットは変換を隠蔽し、仕様を進化(evolve)させることができますか? できない場合、クエリとライターは明示的な派生パーティション列と移行計画を必要とする可能性があります。
30 秒の回答フレームワーク
「列のカーディナリティからではなく、述語ログから検討を始めます。すべてのクエリが event_time でフィルタリングするため、まず day(event_time) をカナリア検証します。1 日あたり 6 TiB というデータ量は大きいため、テナント等価条件を持つ 35% のクエリに対しては、固定の bucket(64, tenant_id) 変換を追加するベンチマークを実施します。これにより、12,000 個のテナントパーティションではなく、1 日あたり最大 64 個の物理グループが作成されます。
半開区間のイベント時間範囲を使用し、プランと計画されたファイルを確認し、同一の結果を検証しながら、パーティション分割されていない対照群とスキャンバイト数を比較します。tenant_id/hour は 1 日あたり数十万のグループを作成しライターのリソースを圧迫する可能性があるため不採用とします。遅延イベントは該当するイベント日のパーティションを更新します。ワークロードクラスごとにロールアウトし、プルーニング効果、ファイルサイズ、書き込みコスト、メタデータが許容基準内に収まることを確認した上でのみ Iceberg 仕様を進化させます。」
ステップバイステップの詳細解説
ステップ 1:クエリログを述語マトリクスに変換する
物理レイアウトを選択する前に、本番クエリをサンプリングします。ワークロードクラスごとに、頻度、レイテンシやコストの重み、時間範囲の幅、テナントの選択性、結合、およびファクトテーブルをスキャンする前に述語が利用可能かどうかを記録します。頻度だけで重み付けを行うと、安価なダッシュボードが最適化される一方で、テーブルの大部分を読み取る稀な日次ジョブが見過ごされる可能性があります。
このシナリオには強力な不変条件があります。すべてのクエリにイベント時間の範囲指定があることです。これにより、イベント時間が第 1 のプルーニング次元となります。テナント等価条件はクエリの 35% にしか現れないため、テナントを唯一のパーティション次元にすることはできません。自由形式の検索フィールド、メトリクス値、または高カーディナリティなイベント ID は、主要なアクセスパスと一致せず、安定した物理グループを生成しないため不適切です。
ステップ 2:候補となるパーティションサイズと数を概算する
日ごとのイベント時間パーティションは約 6 TiB を受け取ります。したがって、7 日間のクエリは、ファイルおよび行グループ(row-group)レベルのスキップを行う前の段階で約 42 TiB から開始できます。時間単位のパーティションは平均して約 256 GiB を受け取ります。
6 TiB / 24 = 0.25 TiB = 256 GiB per hourどちらも通常のカラムナーファイルを満たすのに十分な大きさです。日次パーティションは約 180 個のアクティブな時間グループを作成し、時間単位のパーティションは約 4,320 個を作成します。より細かい粒度の選択が正当化されるのは、多くのクエリが数時間を対象としており、メタデータの増加や書き込みのファンアウトを考慮しても測定コストが改善される場合のみです。
tenant_id と時間で直接パーティショニングすることには、危険な上限があります。
12,000 tenants * 24 hours = 288,000 tenant-hour groups per dayまばらな(sparse)グループやスキューにより実際の数は少なくなりますが、この上限は障害モードを示しています。ライターはバッチごとに多くのグループにアクセスし、極小のファイルを閉じる可能性があります。固定の bucket(64, tenant_id) 変換を使用すると、時間パーティションあたりのテナント次元が 64 グループに制限されます。均一なデータの場合、各日次バケットは約 96 GiB を受け取ります。実際のスキューは測定する必要があります。
ステップ 3:ワークロードを満たす最もシンプルな仕様を選択する
まずは day(event_time) から始めます。これはすべてのクエリに対応し、メタデータ面が最も小さくなります。次に、テナント主体のワークロードに対してこの代替案をベンチマークします。
PARTITIONED BY (day(event_time), bucket(64, tenant_id))バケット数は面接用の候補値であり、普遍的なデフォルト値ではありません。実際のテナント分布、目標ファイルサイズ、ライターの並列処理度、クエリの同時実行性を用いて、16、32、64、128 を比較します。バケットの追加が有効なのは、エンジンがテナント等価述語からバケットを導出でき、計画されるファイルの削減効果がメタデータと書き込みファンアウトのコストを上回る場合のみです。
テーブルフォーマットが隠蔽された変換(hidden transforms)をサポートしている場合、パーティションレイアウトをビジネス SQL にハードコードしないでください。クエリは論理的な event_time および tenant_id をフィルタリングすべきであり、テーブルのメタデータがそれらの述語を物理パーティションにマッピングします。これにより、暗黙的な派生列の不整合が回避され、すべてのコンシューマークエリを書き換えることなく仕様を進化させることができます。
ステップ 4:プランナーがプルーニング可能な述語を記述する
ビジネスタイムゾーンで明確な半開区間を使用し、UTC 境界に変換します。
SELECT tenant_id, event_type, COUNT(*)
FROM analytics.events
WHERE event_time >= TIMESTAMP '2026-07-01 00:00:00 UTC'
AND event_time < TIMESTAMP '2026-07-08 00:00:00 UTC'
AND tenant_id = 'tenant-42'
GROUP BY tenant_id, event_type;半開区間を使用することで、隣接するウィンドウが実行されたときの境界値の二重カウントを防止できます。パーティションソースをサポートされていない関数でラップしたり、別の行依存列と比較したり、OR の背後に隠したり、一貫性のないキャストを行ったりすると、静的な除外(static elimination)が行われない可能性があります。サポートされる正確な変換はエンジンに依存するため、1 つのオプティマイザのルールを暗記するのではなく、プランを検証してください。
ローカル日付のレポートでは、クエリ実行前に要求されたタイムゾーンから UTC の開始および終了の瞬間を計算します。夏時間の移行期において、固定の 24 時間減算を行うのは誤りです。保存されたイベントタイムスタンプ、パーティション変換、およびレポートの境界値も、文書化されたタイムスタンプ規則に従う必要があります。
ステップ 5:プルーニングと正確性を証明する
1 時間、1 日、7 日の範囲、テナントあり/なしのバリアント、空の範囲、遅延イベント、および夏時間の境界を含むテストマトリクスを構築します。各クエリについて、以下を取得します。
- パーティションまたはファイルフィルターを含む論理プランおよび物理プラン
- 計画されたパーティション数およびファイル数
- 推定および実際のスキャンバイト数
- プランニング時間、実行 p50/p95、およびタスク数
- 出力行数、集計結果、および対照群に対するチェックサム
パーティション分割されていないスナップショットを対照群(control)とします。サイズが均一な 180 日間に対する 1 日のクエリの場合、メタデータ、スキュー、ファイル統計を考慮する前において、時間プルーニングによってアクティブバイトの約 1/180 が対象になると大まかに期待されます。これは桁数レベルの確認であり、保証された比率ではありません。フィルターが指定されていても全ファイルをスキャンしているプランは合格とは言えません。
ネガティブコントロール(陰性対照)もテストします。時間述語を削除したり、サポートされていない式に書き換えたり、等価ではなくテナントの範囲指定を使用したりします。スキャン範囲が説明可能な形で拡大するはずです。ネガティブコントロールは、プルーニングの欠落を測定によって検出できることを証明します。
ステップ 6:書き込み、遅延データ、およびメンテナンスを考慮する
パーティショニングは書き込みパスを変化させます。タスクごとのオープンライター数、コミットごとに作成されるファイル数、p50/p90 ファイルサイズ、コミットレイテンシ、メタデータの増加、およびリトライの衝突を追跡します。書き込み前にパーティション変換によって行を分散させ、グループあたりのバイト数からライター数を決定します。パーティションプルーニングであっても、何百万もの小さなファイルによるオーバーヘッドを補うことはできません。
レポートは event_time を使用するため、48 時間遅れて到着したイベントは過去の該当イベント日パーティションに属します。ライターとコンパクション(compaction)はこれらの更新を許可する必要があります。パーティションがいつコールド(cold)になるかを定義し、積極的な書き換え処理は遅延ウィンドウが閉じるまで延期します。バックフィルには、制限されたパーティション範囲、べき等な書き込み、およびストリーミング取り込みと同じファイルサイズチェックが必要です。
ステップ 7:一括書き換え(Big-Bang)を行わずに進化させる
Iceberg の隠蔽されたパーティショニングを使用すると、古いファイルが以前の仕様を維持したまま、新しい仕様が新しいファイルに適用されます。プランナーは両方を読み取ることができますが、頻繁にクエリされるホットな古いデータが書き換えられるまでは、得られるメリットは限定的です。ベースラインのスナップショットを記録し、直近の範囲をカナリア検証し、古い仕様と新しい仕様のクエリ双方が正確である場合にのみ対象を拡大します。
ロールバックとは、新しい仕様での新規ライターを停止し、サポートされている場合は以前の書き込み構成またはテーブルスナップショットを復元することを意味します。すでに削除されたファイルが自動的に元に戻るわけではありません。スナップショットの保持と物理的なクリーンアップはカナリアウィンドウの対象外としてください。過去データの書き換えは、測定されたコスト削減が I/O および競合リスクに見合う場合にのみ行います。
受け入れ基準には、同一のビジネス結果、ワークロードクラスごとの期待されるパーティション除外、スキャンバイト数またはレイテンシの削減、健全なファイルサイズパーセンタイル、制限されたプランニング時間、取り込み SLA の低下がないこと、および安定したメタデータ増加が含まれる必要があります。
高品質な回答サンプル
「まず実際の述語分布を抽出します。ここでは、すべてのクエリにイベント時間範囲があり、単一のテナントを選択するものは 35% にとどまるため、時間が主要な次元となります。日次パーティションは約 6 TiB を保持し、180 個のアクティブな時間グループを作成します。時間単位のパーティションは約 256 GiB を保持しますが、4,320 個のアクティブな時間グループを作成します。短時間のクエリが多く、追加のメタデータが正当化される場合にのみ時間単位を採用します。
私のベースラインカナリアは day(event_time) です。テナント条件を多用するクエリに対しては、day(event_time), bucket(64, tenant_id) をベンチマークします。64 はあくまで候補値であり、テナントのファンアウトを抑制し、均一分布下で日次バケットあたり約 96 GiB を提供します。テナントと時間による直接のパーティショニングは、1 日あたり最大 288,000 グループに達し、ファイルサイズの極小化やライターファンアウトのリスクがあるため不採用とします。
クエリは UTC の半開区間とテナントの等価条件を使用します。物理プラン、計画されたファイル、スキャンバイト数を検査し、結果とチェックサムをパーティション分割されていないスナップショットと比較します。テストマトリクスには、1 時間、1 日、7 日、空の範囲、遅延イベント、テナント指定あり/なし、および夏時間のケースが含まれます。また、時間述語を削除または難読化したネガティブコントロールも実行します。
遅延イベントは該当するイベント日パーティションに書き込まれるため、コンパクションは 48 時間の遅延ウィンドウを過ぎるまで待機します。カナリア検証中は、ファイルパーセンタイル、タスクあたりのライター数、プランニングの p95、メタデータ、書き込みラグ、競合を監視します。Iceberg のパーティションエボリューションが利用可能な場合、新しいファイルは新しい仕様を採用し、古いファイルも読み取り可能なまま維持されます。測定されたクエリ削減効果が書き換えコストを上回る場合にのみ、過去データを書き換えます。正確性の不一致、全ファイルスキャン、極小ファイルの急増、または取り込み SLA の低下が発生した場合は、ロールアウトを停止します。」
よくある間違い
- 最もカーディナリティの高い列を選択する → 主要な述語に対応することなく、まばらなグループと小さなファイルを生成する → 重み付けされたクエリ述語から検討を開始し、物理グループ数を制限する。
- テナントと時間でパーティショニングする → このシナリオでは 1 日あたり 288,000 グループが発生し得る → 時間を優先し、固定バケット変換をベンチマークする。
- 日付フィルターを見てプルーニングされていると思い込む → サポートされていない式では全ファイルスキャンが発生する可能性がある → 物理プラン、計画されたファイル、およびバイト数を検査する。
- レイテンシのみを検証する → キャッシュ、同時実行性、または追加の計算リソースによって見かけ上の改善が起きる可能性がある → スキャンバイト数、プランのエビデンス、ネガティブコントロール、および同一リソースを使用する。
- 分析なしにイベント時間レポートに取り込み時間を使用する → 1 つの業務日の遅延イベントが複数の到着パーティションに散らばる → 物理次元をクエリの時間セマンティクスと一致させる。
- ファイルサイズを無視する → 細かすぎるパーティションはライターのリソースを圧迫し、コストをスキャンからプランニングへとシフトさせる → グループあたりのバイト数、ライターのファンアウト、およびファイルパーセンタイルを追跡する。
- 1 PiB すべてを直ちに書き換える → コスト、競合、およびロールバックのリスクが過大になる → 直近の範囲をカナリア検証し、正当性が確認された過去データのみを書き換える。
- エボリューションによって古いファイルも書き換えられると思い込む → 複数の仕様が共存する → 混合仕様でのプランニングをテストし、選択的な書き換えをスケジュールする。
フォローアップの質問と回答
フォローアップ 1:クエリの 95% が単一テナントをフィルタリングする場合はどう変わりますか?
テナントのバケッティングの重要性が高くなります。テナント分布とクエリの同時実行性からバケット数を再計算し、時間のみ、時間+バケット、そして支配的なテナント向けにテナントを分離したレイアウトを比較します。12,000 通りの直接パーティショニングを採用する場合でも、各グループが健全なファイルと管理可能なメタデータを生成することの証明が必要です。
フォローアップ 2:1 時間あたり 256 GiB がまだ十分大きい場合、なぜ時間単位でパーティショニングしないのですか?
大部分のクエリが数分から数時間の範囲を対象とし、7 つの日次パーティションをプルーニングするのでは粒度が粗すぎる場合には、時間単位が正しい選択肢になり得ます。ただし、時間グループが 24 倍になり、書き込みファンアウトが増加します。そのトレードオフを受け入れる前に、実際の範囲分布に基づいてプランニング、バイト数、ファイルサイズ、取り込みコストをベンチマークしてください。
フォローアップ 3:クエリがローカルのカレンダー日をフィルタリングする場合はどうなりますか?
要求されたローカル日の開始と翌日の開始を UTC の瞬間に変換し、半開区間を使用します。これにより、夏時間による 23 時間または 25 時間の日が適切に処理されます。パーティション変換と保存されたタイムスタンプ規則により、エンジンが該当する物理日を導出できることを検証してください。
フォローアップ 4:プランにプルーニングが示されているのにスキャンバイト数が減少しない場合はどうしますか?
スキューによって選択されたパーティションにテーブルの大部分が含まれていないか、古いファイルが別の仕様を使用していないか、残りのフィルターがファイル統計を利用できていないか、報告されたバイト数に関係のないステージが含まれていないかを確認します。計画されたファイル ID とパーティション分割されていない対照群を比較してください。プラン上の表示ラベルだけでは十分な証拠になりません。
フォローアップ 5:日次パーティションから時間単位のパーティションへ安全に変更するにはどうすればよいですか?
新しいファイルに対して新しい仕様をカナリア検証し、以前のスナップショットを保持した上で、古いレイアウトと新しいレイアウトにまたがる範囲をクエリします。正確性、プランニング、ファイルサイズ、書き込みコストを測定します。測定されたメリットが I/O コストに見合う過去の範囲のみを書き換え、物理的なクリーンアップはロールバックおよび保持ウィンドウが閉じるまで延期します。