| name | metadata-storage-source-of-truth-desync |
| description | Fix content serving failures caused by metadata (KV store, database) being out of sync
with actual storage (GCS, S3, filesystem). Use when: (1) Content exists in storage but
API returns "processing" or "not ready", (2) Webhook callbacks that update metadata
failed silently, (3) Backfill scripts generated content but didn't update status records,
(4) HLS/transcoded content exists but transcode_status is not "Complete", (5) Handler
checks metadata status BEFORE checking if content actually exists in storage.
|
| author | Claude Code |
| version | 1.0.0 |
| date | "2026-02-27T00:00:00.000Z" |
Metadata-Storage Source of Truth Desync
Problem
When a system has both metadata (KV store, database status field) and actual content
in object storage, the metadata can become stale. If the serving handler gates on
metadata status before checking storage, content that exists becomes inaccessible.
Common causes:
- Webhook callbacks (transcode complete, migration done) failed silently
- Backfill scripts created content but didn't update metadata
- Race conditions between metadata write and storage write
- Manual operations that bypassed the normal status update flow
Context / Trigger Conditions
- API returns "processing" or "202 Accepted" for content that actually exists in storage
transcode_status is Processing/Pending/None but HLS files exist in GCS
- Content works when accessed directly via storage URL but not through the API
- Backfill script ran successfully (content in bucket) but metadata wasn't updated
- Webhook endpoint had downtime or returned errors during batch processing
Solution
Pattern: Storage-First with Metadata Auto-Repair
Check storage (source of truth) BEFORE checking metadata status. If content exists,
serve it and fix metadata as a side effect.
match metadata.transcode_status {
Some(Complete) => { }
Some(Processing) => { return 202; }
_ => { trigger_transcoding(); return 202; }
}
match download_from_storage(&path) {
Ok(content) => {
if metadata.transcode_status != Some(Complete) {
eprintln!("[FIX] {} has content but status was {:?}", id, metadata.transcode_status);
update_status(id, Complete);
}
serve(content)
}
Err(NotFound) => {
match metadata.transcode_status {
Some(Processing) => return 202,
_ => { trigger_processing(); return 202; }
}
}
}
Key Principles
- Storage is the source of truth — if the file exists, serve it regardless of metadata
- Auto-repair metadata — when you find a desync, fix it inline (best-effort, don't fail if update fails)
- Log desyncs — track how often metadata is wrong to find the root cause
- Don't block on metadata — metadata status should be advisory, not a gate
Apply to HEAD requests too
HEAD handlers often have the same bug. Apply the same storage-first pattern:
match check_storage_exists(&path) {
Ok(true) => {
if metadata.status != Complete {
update_status(id, Complete);
}
return 200;
}
Ok(false) | Err(_) => {
}
}
Verification
- Request content that previously returned 202 — should now return 200 with content
- Check logs for "[FIX]" messages showing auto-repair in action
- After warming traffic, verify metadata has been corrected
Example
In Divine Blossom, the HLS handler checked transcode_status in KV before GCS:
- ~57,000 objects in GCS bucket, most with HLS transcodes
- Many had
transcode_status: None or Processing in KV (webhooks failed during backfill)
- Handler returned "202 transcoding in progress" for content that was already in GCS
- Fix: Check GCS first, serve if exists, auto-repair KV metadata as side effect
Notes
- The auto-repair is best-effort — if the metadata update fails, don't fail the request
- This pattern works well with caching layers (CDN, VCL) because the repair only runs
on cache misses, and subsequent requests hit the cache
- Consider running a batch metadata-repair script to fix all desynced records at once,
rather than relying solely on on-demand repair
- Root cause should still be investigated (why did webhooks fail?) to prevent future desyncs