代表的な面接トピック

バックエンド面接:チェックポイントを備えたバルクデータエクスポートAPIの設計

バックエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

テナントが数億件のレコードをエクスポートできるようにするAPIを設計してください。エクスポートには数時間かかる場合があり、クライアントはポーリングや切断後のリトライを行い、実行中にもデータが変更されます。オンラインシステムを保護しながら、重複や欠損のない再現可能な結果を提供する必要があります。

プロンプトとスコープ

あるB2B製品では、フルエクスポートと増分エクスポートが必要です。1回のエクスポートがリクエストのタイムアウトや単一ファイルの制限を超える可能性があり、クライアントはダウンロードの中断後にリトライする場合があります。面接では、エクスポートの境界、非同期状態、チェックポイント、ファイルの可視性、認可、およびリソースクォータがテストされます。

面接官がテストしていること

面接官は、長時間実行されるジョブをオブザーバブルで再開可能なプロトコルに落とし込めるかどうかを確認したいと考えています。GoogleのData Portability APIはエクスポートを専用の機能として扱い、機密スコープに対する審査を要求します。Zendeskの増分エクスポートは次の開始点のためにカーソルまたは時間境界を保存し、直近の書き込みとの競合(recent-write race)を明示的に回避します。Oracle FHIR Bulk Exportはポーリング用のジョブロケーションを公開します。優れた回答では、スナップショットの一貫性、削除セマンティクス、テナントの分離についても言及します。

最初に明確にすべき質問

対象リソース、フルモードか増分モードか、最大行数と保持期間、テーブル間の一貫性が必要かどうか、出力がJSONL、CSV、圧縮シャードのいずれか、クライアントがキャンセルや範囲ダウンロードを必要とするかどうか、誰が作成およびダウンロードできるか、各テナントの同時実行数・帯域幅・ストレージのバジェットを確認します。「エクスポート完了」と「ファイルダウンロード済み」を同一の状態として扱わないでください。

30秒の回答フレームワーク

冒頭は次のように始めます。「作成、ステータス、マニフェスト/ダウンロード、キャンセルの各操作を公開します。作成時に認可スコープとスナップショット境界を確定し、ジョブIDを返します。ワーカーがシャードを読み取ってチェックポイントを永続化し、その後イミュータブルなファイルマニフェストを公開します。クライアントはジョブロケーションをポーリングし、完了後にのみ短時間有効なダウンロード認証情報を取得します。チェックポイントと冪等性キーにより、ワーカーのリトライが安全になります。有効期限が切れるとファイルが削除され、クォータが解放されます。増分エクスポートでは、動的に変化する時計境界の代わりに、カーソルまたは確定した高水位標(high-water mark)を使用します。」

ステップごとの詳細解説

ステップ 1: ジョブの作成と境界の凍結

POST /exports でテナントの認可、リソーススコープ、フィルタ、クォータを検証し、推測不可能な exportId を作成します。ジョブには認可スナップショット、フォーマット、圧縮、作成時刻、有効期限、および snapshotWatermark を保存します。データベースが一貫性のあるスナップショットをサポートしている場合は、1つのトランザクションでウォーターマークを読み取ります。サポートしていない場合は、厳密なテーブル間の一貫性ではなく、リソースごとの概略スナップショットであることを明記します。

ステップ 2: ステータスと作成の冪等化

Idempotency-Key を受け入れ、同じテナントと同じキーに対しては同一のジョブを返します。GET /exports/{id} は、QUEUEDRUNNINGSUCCEEDEDFAILEDCANCELLED、または EXPIRED に加え、処理済みシャードと次のアクションをレポートします。ステートマシンは前方にのみ遷移します。条件付き更新によってキャンセル対完了の競合を判定し、リトライによってクォータが二重に消費されないようにします。

ステップ 3: シャードの読み取りとチェックポイントの永続化

ワーカーは安定したプライマリキーまたはデータベーススナップショットカーソルによって固定バッチを読み取り、一時オブジェクトを書き込みます。各シャードは (exportId, partition, cursor, rowCount, checksum, objectKey) を記録します。チェックポイントおよびシャードメタデータのコミットは冪等です。クラッシュ後、ワーカーは最後にコミットされたカーソルから再実行します。決定論的なシャードバージョンまたは条件付きオブジェクト書き込みによって重複が上書きされ、最終マニフェストは各シャードを1回だけ参照します。

ステップ 4: フルエクスポートと増分エクスポートの境界定義

フルエクスポートは snapshotWatermark 以前のデータを読み取ります。それ以降の挿入や更新は、後続の増分実行に属します。増分実行は「現在時刻の1秒前」からではなく、保存されたカーソルまたは高水位標から開始します。Zendeskのドキュメントでは、直近の書き込みとの競合を避けるための安全遅延(safety delay)を設けた上で、カーソルを次のページおよび次のエクスポートに再利用できるとされています。コントラクトには、遅延、重複処理、修復ウィンドウを含めます。

ステップ 5: マニフェストの公開と安全なダウンロード

すべてのシャードが完了して検証された後、ジョブを RUNNING から SUCCEEDED へアトミックに移行し、ファイル、サイズ、チェックサム、範囲を含むイミュータブルなマニフェストを作成します。ダウンロードエンドポイントは、短期間のみ有効でテナントおよびオブジェクトにスコープを限定した認証情報を返します。これらは任意のパスにはアクセスできません。ダウンロードに失敗した場合は、エクスポートを再実行するのではなく、同じマニフェストを取得します。ファイルは有効期限が切れるまで読み取り専用のまま維持されます。

ステップ 6: リソース保護、キャンセル、およびクリーンアップ

作成およびスケジューリング時に、同時実行ジョブ数、スキャン行数、CPU、オブジェクトストレージ、下り転送量(egress)に関するテナントごとの制限を適用します。大規模ジョブにはキューとグローバルな同時実行上限を使用し、オンライントラフィックには別個のリソースプールを使用します。キャンセルは新規シャードの処理を停止し、ジョブにマークを付け、一時オブジェクトを非同期に削除します。リトライ可能なクリーナーが EXPIRED のジョブを回収し、孤立したバイト数、失敗理由、クリーンアップ遅延を記録します。監査イベントには実行者、スコープ、ダウンロードを記録します。

模範的な高クオリティの回答

「私は POST /exports でジョブを作成し、テナントの認可とクォータを検証した上で、スコープ、フォーマット、有効期限、スナップショットウォーターマーク、冪等性キーを永続化します。レスポンスは exportId のみを返し、クライアントは GET /exports/{id} をポーリングします。ワーカーはスナップショットカーソルからバッチを読み取り、各シャードのカーソル、行数、チェックサムを永続化します。リトライ時は最後にコミットされたチェックポイントから再開し、(exportId, partition, cursor) によって重複書き込みが冪等になります。フルエクスポートは1つの固定ウォーターマークを使用し、増分エクスポートはカーソルまたは高水位標に加えて安全遅延を保存します。すべてのシャードが検証された後、イミュータブルなマニフェストが公開され、ジョブはアトミックに SUCCEEDED になります。ダウンロードにはテナントに紐づく短期間有効な認証情報が使用されます。キャンセルと有効期限切れにより新規処理が停止され、一時オブジェクトが削除されてクォータが解放されます。レート制限、分離されたリソース、監査ログによってオンライントラフィックが保護されます。」

よくある間違いと改善策

  • 1回のHTTPリクエストでデータベース全体をスキャンする: 接続がタイムアウトしないよう、キューイングされたジョブとポーリング可能な状態を使用します。
  • 現在時刻を増分の開始点として使用する: カーソルまたは確定した高水位標を保存し、安全遅延と重複ポリシーを定義します。
  • リトライごとに新しいファイルを作成する: シャードとマニフェストを冪等にし、リトライでは不足している処理のみを補填するようにします。
  • 無期限のダウンロードリンクを発行する: 認証情報をテナント、オブジェクト、有効期限にバインドします。ダウンロードに失敗した場合は同じファイルを取得するようにします。

フォローアップの質問と回答

エクスポート中にレコードが削除された場合はどうなりますか?

まずプロダクトのセマンティクスを定義します。スナップショットエクスポートには、ウォーターマーク以前に可視だったレコードが含まれます。増分ストリームにはトゥームストーン(削除マーカー)または変更タイプを含める必要があります。すべてのコンシューマーの修復ウィンドウが経過するまで、トゥームストーンを保持します。

ワーカーがファイルを書き込み、チェックポイントをコミットする前にクラッシュした場合はどうなりますか?

再起動時に、データベースのチェックポイントを信頼してバッチを再実行します。条件付き書き込みまたは決定論的なシャードバージョンによって安全にオブジェクトが上書きされ、マニフェストにはコミットおよび検証済みのシャードのみが追加されるため、不完全な処理結果がダウンロード可能になることはありません。

1つのテナントがオンラインデータベースに悪影響を与えるのを防ぐにはどうすればよいですか?

リードレプリカ、スナップショット、または専用のクエリプールを使用し、スキャンの同時実行数と秒あたりの行数を制限します。テナントごとに公平にスケジューリングを行い、レプリカの遅延、ロック待機、下り転送量を監視して、閾値を超えた場合はエクスポートを低速化または一時停止します。

クライアントは中断されたダウンロードをどのように再開できますか?

エクスポートをイミュータブルに保ち、HTTP Rangeまたはシャード単位のダウンロードをサポートします。クライアントはマニフェスト、チェックサム、完了した範囲を保持します。認証情報をリフレッシュしても、ファイルのバージョンが変わったりエクスポートが再実行されたりすることはありません。

公開情報ソース

関連する質問