Blob Upload Stuck
.mutate("attach", { attachment: file })は成功して返った。- 行は
useLiveQueryでFileRefを表示している。 - outbox に保留中エントリがある。
- しかし mutation はサーバーへ push されない。Devtools パネルは
outboxDepth: 1+を表示し、それが減らない。
連続プレフィックス push ルール
Section titled “連続プレフィックス push ルール”plasma の push ループは 連続プレフィックス配信 を保証します。すなわち、
< N のすべての mutation が確認されるか (blob 依存があれば) その blob が
アップロードされるまで、mutation N はサーバーに送られません。
mutation 1 に blob-upload-failed があると、mutation 2 は — たとえ
mutation 2 自体に blob 依存がなくても — 永遠に outbox に留まります。
Devtools パネルを開くか、outbox を直接読みます。
// In devtools console:const idb = await plasma.__internal.idb // via useDevtoolsSnapshot's schemaconst tx = idb.transaction("_plasma_blob_uploads", "readonly")const uploads = await tx.objectStore("_plasma_blob_uploads").getAll()console.log(uploads)各 upload レコードには state があります。
"queued"— 実行待ち"uploading"— 現在 PUT 中"present"— アップロード成功 (レコードはまもなくクリーンアップされる)"failed"— リトライ枯渇、対応が必要
いずれかの upload が "failed" なら、それが原因です。
対処 1 — リトライ
Section titled “対処 1 — リトライ”失敗が一時的だった場合 (ネットワークの瞬断、R2 が一瞬不調だった等):
await client.retryBlobUpload(mutationID)upload レコードは新しい試行予算とともに queued に戻ります。
対処 2 — 破棄
Section titled “対処 2 — 破棄”mutation が回復不可能な場合 (ユーザーがタブを閉じた、やはりその添付は 不要だと判断した等):
await client.discardMutation(mutationID)outbox エントリと upload レコードの両方を削除します。optimistic ビューは
次の rebuildOptimistic で revert します。
対処 3 — UI に hook する
Section titled “対処 3 — UI に hook する”onError を配線して mutation ID を公開します。
createPlasmaClient({ ..., onError: (err) => { if (err.kind === "blob-upload-failed") { setStuck((prev) => [...prev, err]) // { kind, mutationID, hash, cause } } },})スタックした各 upload を retry/discard ボタンとともにレンダリングします。
なぜ単にスキップして続行しないのか
Section titled “なぜ単にスキップして続行しないのか”push の順序が保たれているのには理由があります。mutation 2 が mutation 1 の
効果に依存しているかもしれないからです。もし 1 がノートを insert し、2 が
そのノートに画像を添付する場合、2 を先に配信すると auth.write に失敗します
(ノートがまだ存在しないため)。
連続プレフィックスルールは、サーバーのビューをクライアントの mutation タイムラインと一貫させ続けます。アップロードされるまで blob をブロックするのは、 その帰結です。
なぜ upload は失敗したのか
Section titled “なぜ upload は失敗したのか”よくある理由:
- Worker に間違ったバケットが設定されている。
wrangler.jsoncの binding が、 実際に作成したバケットと一致しているか確認してください。 maxSize上限を超えた。 カラムがfile({ maxSize: 5 * 1024 * 1024 })と 宣言されているのに、ユーザーが 20MB の画像を選んだ。クライアント側でdesugarFileArgsがこれを捕捉するはずです — 捕捉しなかった場合、サーバー側のPUTが拒否します。mimeAllowListの制約。 カラムがfile({ mimeAllowList: ["image/*"] })と 宣言されているのに、ユーザーが PDF を選んだ。- R2 のレート制限。 まれですが、高速にアップロードして R2 のクォータに 当たっている場合、リトライがいずれ解決します。
- サーバー 500。
sync-handler.ts:handleBlobPutの何かが throw しました。 実際のエラーはwrangler tailで確認してください。
次に読むもの
Section titled “次に読むもの”- ファイルと blob — アップロードのステートマシン全体
- Sync errors —
エラー kind 表の中の
blob-upload-failed