toeverything/AFFiNE · error · FailedToSaveUpdates
failed_to_save_updates
failed_to_save_updates
Error message
Failed to store doc updates.
What it means
Thrown by `PgWorkspaceDocStorageAdapter.pushDocUpdates` when the `retryable` batch insert of doc updates throws — every retriable attempt inside the try block failed. The original DB error is logged and a metric `doc_update_insert_failed` is incremented, then `FailedToSaveUpdates` (category `internal_server_error`) is thrown so the raw DB error is not leaked.
Source
Thrown at packages/backend/server/src/core/doc/adapters/workspace.ts:125
priority: 100,
}
);
turn++;
done += batch.length;
}
});
if (isNewDoc) {
this.event.emitDetached('doc.created', {
workspaceId,
docId,
editor: editorId,
});
}
} catch (e) {
this.logger.error('Failed to insert doc updates', e);
metrics.doc.counter('doc_update_insert_failed').add(1);
throw new FailedToSaveUpdates();
}
return timestamp;
}
protected async getDocUpdates(workspaceId: string, docId: string) {
const rows = await this.models.doc.findUpdates(workspaceId, docId);
return rows.map(row => ({
bin: row.blob,
timestamp: row.timestamp,
editor: row.editorId,
}));
}
async deleteDoc(_workspaceId: string, _docId: string) {
return;
}
View on GitHub (pinned to 26c515e050)
Solutions
- Retry the whole `pushDocUpdates` call after backoff — updates are CRDT-encoded and safe to resend.
- Inspect server logs for the original `e` logged under 'Failed to insert doc updates' to find the DB root cause.
- Verify DB connectivity, disk space, and connection-pool health.
- If caused by deadlocks, reduce concurrent writers per doc (serialize per docId).
Example fix
// before
await adapter.pushDocUpdates(workspaceId, docId, updates, editorId);
// after
async function pushWithRetry(updates, attempt = 0) {
try {
return await adapter.pushDocUpdates(workspaceId, docId, updates, editorId);
} catch (e) {
if (e instanceof FailedToSaveUpdates && attempt < 3) {
await sleep(2 ** attempt * 200);
return pushWithRetry(updates, attempt + 1);
}
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
// Coalesce and dedupe updates to reduce DB insert pressure
async function coalesceAndPush(workspaceId: string, docId: string, updates: Uint8Array[]) {
const deduped = await filterValidDocUpdates(workspaceId, docId, updates);
if (!deduped.length) return 0;
return adapter.pushDocUpdates(workspaceId, docId, deduped, editorId);
} Type guard
function isFailedToSaveUpdates(e: unknown): boolean {
return (
typeof e === 'object' && e !== null &&
(e as { code?: string }).code === 'failed_to_save_updates'
);
} Try / catch
async function pushWithRetry(updates: Uint8Array[], attempt = 0) {
try {
return await adapter.pushDocUpdates(workspaceId, docId, updates, editorId);
} catch (e) {
if (isFailedToSaveUpdates(e) && attempt < 3) {
await sleep(2 ** attempt * 200);
return pushWithRetry(updates, attempt + 1);
}
throw e;
}
} Prevention
- Retry the whole push — CRDT updates are safe to resend.
- Inspect server logs ('Failed to insert doc updates') for the underlying DB error.
- Verify DB connectivity, disk space, and pool health during incidents.
- Reduce concurrent writers per doc to avoid deadlocks.
When it happens
Trigger: The Postgres `doc.createUpdates` insert repeatedly fails: connection loss, deadlock, constraint violation, disk full, or a transient DB restart that outlasts the `retryable` budget.
Common situations: Database connectivity blips during heavy sync; deadlocks from concurrent writers; storage exhaustion; schema/data drift causing constraint errors; DB maintenance windows.
Related errors
- failed_to_upsert_snapshot
- 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/eebe19001c844ae2.
Report an issue: GitHub.