{"record":{"id":"d62b93b7922d2812","repo":"toeverything/AFFiNE","slug":"failed-to-upsert-snapshot","errorCode":"failed_to_upsert_snapshot","errorMessage":"Failed to store doc snapshot.","messagePattern":"Failed to store doc snapshot\\.","errorType":"exception","errorClass":"FailedToUpsertSnapshot","httpStatus":500,"severity":"error","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":351,"sourceCode":"        docId: snapshot.docId,\n        blob,\n        timestamp: snapshot.timestamp,\n        editorId: snapshot.editor,\n      });\n\n      if (updatedSnapshot) {\n        this.event.emitDetached('doc.snapshot.updated', {\n          workspaceId: snapshot.spaceId,\n          docId: snapshot.docId,\n          blob,\n        });\n      }\n\n      return !!updatedSnapshot;\n    } catch (e) {\n      metrics.doc.counter('snapshot_upsert_failed').add(1);\n      this.logger.error('Failed to upsert snapshot', e);\n      throw new FailedToUpsertSnapshot();\n    }\n  }\n\n  protected override async lockDocForUpdate(\n    workspaceId: string,\n    docId: string\n  ) {\n    const lock = await this.mutex.acquire(`doc:update:${workspaceId}:${docId}`);\n\n    if (!lock) {\n      throw new Error('Too many concurrent writings');\n    }\n\n    return lock;\n  }\n\n  protected async lastDocHistory(workspaceId: string, id: string) {\n    return this.models.history.getLatest(workspaceId, id);","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/doc/adapters/workspace.ts#L333-L369","documentation":"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.","triggerScenarios":"The Postgres `doc.upsert` fails: connection loss, deadlock, disk full, a constraint/type error on the blob, or a schema mismatch.","commonSituations":"DB connectivity blips; storage exhaustion; deadlocks from overlapping snapshot writers; schema drift after a partial migration; oversized blob payloads.","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)."],"exampleFix":"// before\nawait adapter.setDocSnapshot(snapshot);\n\n// after\nasync function setSnapshotWithRetry(snapshot, attempt = 0) {\n  try {\n    return await adapter.setDocSnapshot(snapshot);\n  } catch (e) {\n    if (e instanceof FailedToUpsertSnapshot && attempt < 3) {\n      await sleep(2 ** attempt * 200);\n      return setSnapshotWithRetry(snapshot, attempt + 1);\n    }\n    throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"// Snapshots are written under a per-doc lock; confirm the input is well-formed\nfunction isWellFormedSnapshot(s: unknown): boolean {\n  return typeof s === 'object' && s !== null &&\n    typeof (s as any).spaceId === 'string' &&\n    typeof (s as any).docId === 'string' &&\n    (s as any).bin instanceof Uint8Array;\n}\n\nif (!isWellFormedSnapshot(snapshot)) {\n  throw new Error('Malformed snapshot — refusing to upsert');\n}","typeGuard":"function isFailedToUpsertSnapshot(e: unknown): boolean {\n  return typeof e === 'object' && e !== null &&\n    (e as { code?: string }).code === 'failed_to_upsert_snapshot';\n}","tryCatchPattern":"async function setSnapshotWithRetry(snapshot: DocRecord, attempt = 0) {\n  try {\n    return await adapter.setDocSnapshot(snapshot);\n  } catch (e) {\n    if (isFailedToUpsertSnapshot(e) && attempt < 3) {\n      await sleep(2 ** attempt * 200);\n      return setSnapshotWithRetry(snapshot, attempt + 1);\n    }\n    throw e;\n  }\n}","preventionTips":["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."],"tags":["doc-storage","workspace","postgres","persistence","retry","internal-server-error","snapshot"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}