1. 問題
同一オリジンの複数のタブが、同時にIndexedDBキャッシュを更新してサーバーと同期する可能性があります。他のコンテキストが待機、スキップ、または解放後に再確認する一方で、最大1つのコンテキストのみが同期を実行できるようにするコーディネーターを設計してください。タブの終了、ネットワークエラー、およびWeb Locks非対応ブラウザへの対応も含めてください。
2. 制約と明確化事項
- ロック名は同一オリジンのウィンドウおよびワーカー間で共有されます。
- ロックは非同期コールバックの調整スコープのみを保護します。データベーストランザクションやサーバー側の並行性制御ではありません。
- 同期には時間がかかる場合があるため、キャンセル、リトライ、およびバージョンや結果のブロードキャストが必要です。
- ロックの待機、即時プロービング、およびリクエストの破棄を明確に区別してください。
3. コアアプローチ
名前付きロックを要求するには navigator.locks.request(name, options, callback) を呼び出します。ロックはコールバックのPromiseが解決(settle)した後に解放されるため、コールバックには読み取り、同期、状態書き込みの一連のフロー全体を含める必要があります。デフォルトのリクエストはキューに入ります。ifAvailable: true はビジー状態のときに null でコールバックを呼び出します。signal は待機中のリクエストをキャンセルできます。
ロックマネージャーは、ロックを保持しているコンテキストが終了したときにロックを解放しますが、正当性をタブのクラッシュ時のクリーンアップのみに依存してはなりません。同期レコードにはバージョン、リース、または冪等性キーを持たせる必要があり、サーバー側でも書き込みの検証を継続して行う必要があります。他のタブは、BroadcastChannel経由で、またはIndexedDBを再読み取りすることで新しいバージョンを検知できます。
4. 参照実装
const LOCK = "shared-cache-sync";
async function runSync(signal) {
return navigator.locks.request(LOCK, { signal }, async (lock) => {
if (!lock) return { status: "busy" };
const current = await readSyncVersion();
if (await isFresh(current)) return { status: "fresh" };
const result = await fetchAndWriteCache({ signal, baseVersion: current });
await publishVersion(result.version);
return { status: "updated", version: result.version };
});
}
async function trySyncWithoutWaiting() {
return navigator.locks.request(
LOCK,
{ ifAvailable: true },
(lock) => lock ? runOneSync() : { status: "busy" },
);
}5. 正当性と障害処理
ロックは、指定されたリソースに対して同一オリジンのコンテキスト間で相互排他を提供しますが、ネットワークリクエストやデータベース書き込みをロールバックするわけではありません。条件付き更新を行う前にバージョンを再読み取りしてください。ネットワーク障害が発生した場合は例外をスローし、ロックが解放されて後続のリクエストがリトライできるようにします。コールバックの外部に「ロックを保持している」というブール値を保持しないでください。
AbortSignal が待機中のリクエストをキャンセルした場合、呼び出し元はキャンセルとビジネスロジック上の障害を区別する必要があります。待機時間が長い場合は、別のタブが同期中であることを表示するか、作業をスキップします。解放後は、重複した更新を避けるためにバージョンを再読み取りします。BroadcastChannelによる通知は最適化のための手段であり、正データとしての権威はIndexedDBまたはサーバーにあります。
6. 発展事項と注意点
- Web Locksは同一オリジンのコンテキスト間のみを調整します。別のサイト、サーバープロセス、またはデータベースの行をロックすることはできません。
ifAvailableはキューに入りません。ビジー状態のリクエストはnullを受け取りますが、これは正常な結果です。steal: trueは既存のキューセマンティクスを中断するため、バージョンチェックを伴う明示的に破棄可能な作業に限定して使用する必要があります。- このAPIがない場合、BroadcastChannelとIndexedDBを組み合わせることでベストエフォート型の調整を提供できますが、同等の相互排他保証は得られません。サーバー側の重複排除が引き続き必要です。
7. 発展資料
Web Locks、IndexedDBトランザクション、Service Workerメッセージ、およびBroadcastChannelを比較してください。ロックはコンテキスト間の排他を提供し、トランザクションは単一データベースの原子性を提供し、チャネルは通知を提供します。デバイス間またはユーザー間の排他は、サーバーリース、冪等なAPI、またはデータベース制約で処理すべき領域です。
8. 面接の評価ポイント
ロックのスコープを述べることができるか
候補者は、名前付きロックが同一オリジンのウィンドウおよびワーカー間を調整し、コールバックのPromiseが解決したときに解放されること、またサーバーやデータベースの並行性制御を代替することはできないと説明できる必要があります。
リクエストモードを区別できるか
デフォルトのキューイング、ifAvailable による即時プロービング、および AbortSignal による待機の破棄について説明できる必要があります。
クラッシュと重複を処理できるか
バージョン、冪等性キー、および条件付き書き込みを使用し、タブのクラッシュによるクリーンアップがビジネス書き込みをロールバックするわけではないことを説明できる必要があります。
適切なフォールバックを提示できるか
BroadcastChannel/IndexedDBによるベストエフォートのパスを提示し、相互排他が弱まる一方でサーバー側のセーフガードが依然として必須であることを説明できる必要があります。