プロンプトと適用範囲
あなたはローカルでの分析タスクを担当しています。DuckDB は Parquet ファイルを読み込み、複数テーブルの JOIN、GROUP BY 操作、およびウィンドウ関数を実行します。データが増加するにつれて、タスクは Out of Memory を報告するか、多数の一時ファイルを作成してタイムアウトします。診断の順序、パラメータの変更、SQL の書き換え、および検証計画について説明してください。
このシナリオは、データエンジニアリング、アナリティクスエンジニアリング、および組み込み OLAP の面接に適しています。回答は証拠に基づくべきであり、「メモリを追加する」や「より大きなマシンを購入する」は診断とは言えません。
面接官が評価するポイント
- ストリーミング実行と大きな状態を保持するオペレータを区別できているか。
- 推測ではなく、実行プラン、ランタイムプロファイル、およびメモリシグナルを使用しているか。
- スレッド、メモリ制限、スピルディレクトリ、および挿入順序の保持を理解しているか。
- 一時ディスクの容量、パーミッション、型、インデックス、および JOIN 結果の爆発を確認しているか。
- 正確性、再現性、およびリグレッションパフォーマンスによってチューニングループを完結させているか。
回答前の確認質問
- 障害はスキャン、JOIN、集約、ソート、ウィンドウのどこで発生していますか?それは DuckDB のエラーですか、それとも OS による強制終了(kill)ですか?
- DuckDB のバージョン、スレッド数、
memory_limit、一時ディレクトリのパス、および利用可能なディスク容量はどれくらいですか? - 入力は Parquet/CSV ですか、カラムの型とパーティション構成はどうなっていますか、また述語プッシュダウンは可能ですか?
- クエリに高カーディナリティの GROUP BY、厳密な DISTINCT、ワイドな JOIN、ORDER BY、ウィンドウ、
list/string_agg、または PIVOT が含まれていますか? - 結果をバッチ処理、事前集約、近似処理、または入力順序を保持せずに返すことは可能ですか?
30秒の回答フレームワーク
まず枯渇しているオペレータとリソースを特定し、次に EXPLAIN ANALYZE、メモリのスナップショット、および一時ディレクトリのメトリクスを用いてベースラインを確立します。高カーディナリティの集約、JOIN、ソート、またはウィンドウがブロッキング状態を作り出している場合は、スキャン対象の行と列を削減し、フィルタや結合条件を修正した上で、並行性を下げ、安全なメモリ制限を設定し、スピルディレクトリを検証します。最後に、固定入力と完全入力の両方で行数、キーの一意性、集約値、およびレイテンシを比較し、最適化が高速でありながらセマンティクス上も安全であることを証明します。
ステップバイステップの詳細解説
1. メモリ障害と一時ディスク障害を切り分ける
エラーテキスト、プロセスの終了理由、ピーク RSS、DuckDB バージョン、スレッド数、およびクエリフィンガープリントを記録します。DuckDB は利用可能なメモリの一部を制限として確保しますが、OS の OOM、コンテナの上限、書き込み不可の一時ディレクトリ、またはディスクフルも同様の挙動を示すことがあります。SQL を変更する前に、cgroup/コンテナの制限、容量、およびパーミッションを確認してください。
2. 実行プラン内のブロッキングオペレータを特定する
EXPLAIN を使用して結合順序と述語プッシュダウンを調査し、次に EXPLAIN ANALYZE を使用して実際の行数、処理時間、および実行時状態を確認します。スキャンは通常チャンクごとに処理されますが、GROUP BY、JOIN、ORDER BY、ウィンドウ、および厳密な DISTINCT はハッシュテーブル、ソートバッファ、またはフレームを保持します。誤った結合キーによって行数が爆発している場合は、メモリについて議論する前にそのセマンティクスを修正します。
3. リソースをチューニングする前にワーキングセットを削減する
必要なカラムのみを読み込み、パーティションフィルタや時間フィルタを早期に適用し、サブクエリ内で完全なワイドリレーションを実体化することを避けます。再利用可能な高コストのファクトをパーティションごとに事前集約し、明らかな多対多の JOIN は一意性チェックを伴うステップに分割します。明確なエラーバジェットがない限り、正確な高カーディナリティの統計を近似値に置き換えてはなりません。
4. スレッド、メモリ、およびスピルを設定・検証する
スレッド数が増えると、複数のオペレータが同時に状態を保持する可能性があるため、リソース制約のあるホストでは threads を減らします。システムのヘッドルームを残すために、memory_limit をコンテナの割り当て未満に維持します。これを単純に増やすと、DuckDB のエラーが OS による強制終了に変わるだけになる可能性があります。スピルには、容量が明確で書き込み可能な高速ローカルディスクを使用します。制御された実験では以下を使用できます:
SET threads = 4;
SET memory_limit = '4GB';
SET temp_directory = '/var/tmp/duckdb_swap';
SET preserve_insertion_order = false;
EXPLAIN ANALYZE
SELECT customer_id, date_trunc('day', event_time) AS day, sum(amount) AS total
FROM read_parquet('events/*.parquet')
WHERE event_time >= DATE '2026-01-01'
GROUP BY customer_id, day;ビジネス上の結果が入力順序に依存しない場合にのみ preserve_insertion_order を無効化します。インデックスや一部の中間状態は必ずしもバッファマネージャによって管理されるわけではないため、memory_limit は万能のハードガードではありません。
5. スピル動作とオペレータの限界を特定する
スピルは多くの大規模な GROUP BY、JOIN、ソート、およびウィンドウワークロードをサポートしますが、I/O が増加します。連鎖したブロッキングオペレータ、巨大なリスト集約、string_agg、一部の全体的(holistic)な集約、および PIVOT は、依然として分割不可能な大きな状態を必要とする場合があります。一時ディレクトリが予期せず肥大化した場合は、temp_directory、max_temp_directory_size、ディスクスループット、およびクリーンアップ状況を調査します。スピルで対処できない場合は、バッチ処理に戻るか、SQL 構造を書き直します。
6. 結果とパフォーマンスのリグレッションテストで完了する
固定された入力スナップショットを使用して、変更前後の総行数、プライマリキーのセット、NULL の分布、グループ数、チェックサム、およびサンプルの詳細を比較します。ピークメモリ、一時バイト数、スキャンバイト数、実行時間、および失敗率を記録します。境界日付、空のパーティション、重複キー、および極端なカーディナリティを個別にテストします。
質の高い模範回答
私はこのインシデントを、オペレータの状態、設定されたキャパシティ、または外部環境のいずれかに分類します。まず、バージョン、クエリ、入力スナップショット、コンテナメモリ、および一時ディスクの証拠を保全し、次に EXPLAIN ANALYZE を使用してピーク箇所を特定します。高カーディナリティの GROUP BY、誤った多対多の JOIN、ソート、またはウィンドウについては、カーディナリティと述語プッシュダウンを確認し、列と行を削減し、適切な場合は事前集約を行います。単に memory_limit を増やして結合の爆発を隠すようなことはしません。
次に、スレッドの並行性を測定済みの安全なレベルまで下げ、memory_limit 未満にシステムのヘッドルームを残し、明確な容量とパーミッションを持つディスク上に temp_directory を設定します。順序が契約上必須でない場合にのみ preserve_insertion_order を無効化します。ピークメモリ、スピルバイト数、実行時間を測定し、一時領域の使用量がクォータ内に収まっていることを確認します。効果的に分割できないリスト、非常に大きな文字列、または PIVOT の状態については、段階的な結果を使用するかクエリの構造を再検討します。
最後に、リリース前に固定データおよび完全なデータで行数、キーの一意性、集約チェックサム、境界パーティション、および NULL の挙動を比較します。これにより、OOM が解消されたことと、結果のセマンティクスが保持されていることの両方を証明します。
よくある間違い
memory_limit を増やすだけにとどまる
コンテナの上限、システムのヘッドルーム、バッファ管理外のメモリを確認しないと、障害が DuckDB からオペレーティングシステムに移るだけになる可能性があります。
すべてのオペレータがスピル可能であると思い込む
特定のオペレータとバージョンを確認してください。一部のリスト、文字列、全体的(holistic)な集約、および PIVOT の状態は、依然として分割不可能なメモリを必要とします。
JOIN のカーディナリティと述語プッシュダウンを無視する
非一意のキーや遅延したフィルタは、中間データを桁違いに増加させる可能性があります。設定だけで誤ったクエリ構造を修復することはできません。
結果のリグレッションを確認せずに成功とみなす
順序保持の変更、集約の分割、または近似関数の使用は、セマンティクスを変更する可能性があります。固定入力と比較し、ビジネスチェックを行ってください。
フォローアップ質問と回答
フォローアップ 1:なぜスレッド数を減らすことが役立つのですか?
並行オペレータは同時に状態とバッファを保持する可能性があります。スレッド数を減らすとピークは下がりますが通常スループットも低下するため、実測したメモリと完了時間のカーブから選択します。
フォローアップ 2:一時ディスクに空きがあるのになぜ OOM が持続することがあるのですか?
すべての状態がパーティション化されてスピルできるわけではありません。分割不可能な集約、過大な結合状態、またはディレクトリのパーミッションやクォータが原因で依然として失敗する可能性があります。プラン、制限、およびログを組み合わせて判断してください。
フォローアップ 3:挿入順序の保持はいつ無効化できますか?
結果および後続のコンシューマが入力順序を契約として扱わない場合にのみ無効化できます。その後、重複キー、順序付け、および LIMIT の動作をリグレッションテストします。
フォローアップ 4:チューニングによって結果が保持されたことをどのように証明しますか?
同一の入力スナップショットを実行し、行数、プライマリキーのセット、グループ数、数値チェックサム、NULL の分布、および境界パーティションを比較します。近似集約の場合は、許容エラーバジェットを明記し、ビジネス側の承認を得ます。