toeverything/AFFiNE · error · FailedToUpsertSnapshot
failed_to_upsert_snapshot
failed_to_upsert_snapshot
Error message
Failed to store doc snapshot.
What it means
Thrown by `setDocSnapshot` when the `models.doc.upsert(...)` throws — the snapshot row could not be written. The original error is logged, the metric `snapshot_upsert_failed` is incremented, and `FailedToUpsertSnapshot` (category `internal_server_error`) is thrown so the DB error is not exposed. Because snapshot writes happen under `lockDocForUpdate`, contention is not the cause — a DB-level failure is.
Source
Thrown at packages/backend/server/src/core/doc/adapters/workspace.ts:351
docId: snapshot.docId,
blob,
timestamp: snapshot.timestamp,
editorId: snapshot.editor,
});
if (updatedSnapshot) {
this.event.emitDetached('doc.snapshot.updated', {
workspaceId: snapshot.spaceId,
docId: snapshot.docId,
blob,
});
}
return !!updatedSnapshot;
} catch (e) {
metrics.doc.counter('snapshot_upsert_failed').add(1);
this.logger.error('Failed to upsert snapshot', e);
throw new FailedToUpsertSnapshot();
}
}
protected override async lockDocForUpdate(
workspaceId: string,
docId: string
) {
const lock = await this.mutex.acquire(`doc:update:${workspaceId}:${docId}`);
if (!lock) {
throw new Error('Too many concurrent writings');
}
return lock;
}
protected async lastDocHistory(workspaceId: string, id: string) {
return this.models.history.getLatest(workspaceId, id);View on GitHub (pinned to 26c515e050)
Solutions
- Retry the snapshot write after backoff (snapshots are deterministic from updates and safe to rewrite).
- Inspect server logs for the original `e` under 'Failed to upsert snapshot' to identify the DB cause.
- Check DB disk space, connectivity, and connection-pool health.
- Reduce overlapping writers per doc to avoid deadlocks (the per-doc mutex should already serialize; confirm lock acquisition is not being bypassed).
Example fix
// before
await adapter.setDocSnapshot(snapshot);
// after
async function setSnapshotWithRetry(snapshot, attempt = 0) {
try {
return await adapter.setDocSnapshot(snapshot);
} catch (e) {
if (e instanceof FailedToUpsertSnapshot && attempt < 3) {
await sleep(2 ** attempt * 200);
return setSnapshotWithRetry(snapshot, attempt + 1);
}
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
// Snapshots are written under a per-doc lock; confirm the input is well-formed
function isWellFormedSnapshot(s: unknown): boolean {
return typeof s === 'object' && s !== null &&
typeof (s as any).spaceId === 'string' &&
typeof (s as any).docId === 'string' &&
(s as any).bin instanceof Uint8Array;
}
if (!isWellFormedSnapshot(snapshot)) {
throw new Error('Malformed snapshot — refusing to upsert');
} Type guard
function isFailedToUpsertSnapshot(e: unknown): boolean {
return typeof e === 'object' && e !== null &&
(e as { code?: string }).code === 'failed_to_upsert_snapshot';
} Try / catch
async function setSnapshotWithRetry(snapshot: DocRecord, attempt = 0) {
try {
return await adapter.setDocSnapshot(snapshot);
} catch (e) {
if (isFailedToUpsertSnapshot(e) && attempt < 3) {
await sleep(2 ** attempt * 200);
return setSnapshotWithRetry(snapshot, attempt + 1);
}
throw e;
}
} Prevention
- Retry snapshot writes — they are deterministic from updates and safe to rewrite.
- Inspect logs ('Failed to upsert snapshot') for the underlying DB cause.
- Check DB disk space, connectivity, and pool health.
- Confirm the per-doc mutex is honored so snapshot writers don't overlap and deadlock.
When it happens
Trigger: The Postgres `doc.upsert` fails: connection loss, deadlock, disk full, a constraint/type error on the blob, or a schema mismatch.
Common situations: DB connectivity blips; storage exhaustion; deadlocks from overlapping snapshot writers; schema drift after a partial migration; oversized blob payloads.
Related errors
- failed_to_save_updates
- Too many concurrent writings
- doc_history_not_found
- doc_not_found
- doc_update_blocked
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/d62b93b7922d2812.
Report an issue: GitHub.