{"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":"critical","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":397,"sourceCode":"    if (this.isEmptyBin(snapshot.bin)) {\n      return false;\n    }\n\n    try {\n      const blob = Buffer.from(snapshot.bin);\n      const updatedSnapshot = await this.models.doc.upsert({\n        spaceId: snapshot.spaceId,\n        docId: snapshot.docId,\n        blob,\n        timestamp: snapshot.timestamp,\n        editorId: snapshot.editor,\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":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/doc/adapters/workspace.ts#L379-L415","documentation":"The snapshot upsert path (createOrUpdate of the doc snapshot row, followed by emitting doc.snapshot.updated) is wrapped in a catch that increments the snapshot_upsert_failed metric, logs 'Failed to upsert snapshot', and rethrows FailedToUpsertSnapshot (internal_server_error / failed_to_upsert_snapshot). It means the database rejected the snapshot write after the attempt.","triggerScenarios":"Postgres outage or connection-pool exhaustion during snapshot merge; snapshot blob exceeding column/size limits; unique-constraint conflicts when two writers race despite the doc:update mutex; schema drift where the snapshot table columns no longer match the model.","commonSituations":"Long-running self-hosted servers hitting max_connections during bulk imports; failed migrations leaving the snapshot table in an old shape; disk-full or transient managed-DB restarts while the merge job runs.","solutions":["Read the paired server log line 'Failed to upsert snapshot' for the true database error","Verify DB health, disk space, and connection-pool configuration","Ensure migrations are applied and the prisma model matches the deployed schema","Retry is safe: the merge job re-runs and upserts are idempotent once the underlying issue is fixed"],"exampleFix":"// before\nawait docAdapter.upsert(docId, snapshot);\n\n// after\ntry {\n  await docAdapter.upsert(docId, snapshot);\n} catch (e) {\n  if (e.extensions?.code === 'failed_to_upsert_snapshot') {\n    metrics.increment('snapshot_retry_scheduled');\n    return requeueMergeJob(spaceId, docId); // idempotent retry\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"// health-check the DB before kicking off snapshot-heavy jobs\nawait db.$queryRaw`SELECT 1`;\nawait upsertSnapshot(spaceId, docId, snapshot);","typeGuard":"function isFailedToUpsertSnapshot(e: unknown): boolean {\n  return (e as { extensions?: { code?: string } }).extensions?.code === 'failed_to_upsert_snapshot';\n}","tryCatchPattern":"try {\n  await upsertSnapshot(spaceId, docId, snapshot);\n} catch (e) {\n  if (isFailedToUpsertSnapshot(e)) {\n    return requeueMergeJob(spaceId, docId); // upsert is idempotent; safe to retry\n  }\n  throw e;\n}","preventionTips":["Alert on the snapshot_upsert_failed metric","Cap snapshot merge job concurrency to avoid pool exhaustion","Apply migrations before deploying code that writes new snapshot columns"],"tags":["database","postgres","doc-snapshot","internal-server-error"],"backgroundTag":"database-write-failed","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}