{"record":{"id":"eebe19001c844ae2","repo":"toeverything/AFFiNE","slug":"failed-to-save-updates","errorCode":"failed_to_save_updates","errorMessage":"Failed to store doc updates.","messagePattern":"Failed to store doc updates\\.","errorType":"exception","errorClass":"FailedToSaveUpdates","httpStatus":500,"severity":"error","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":125,"sourceCode":"              priority: 100,\n            }\n          );\n          turn++;\n          done += batch.length;\n        }\n      });\n\n      if (isNewDoc) {\n        this.event.emitDetached('doc.created', {\n          workspaceId,\n          docId,\n          editor: editorId,\n        });\n      }\n    } catch (e) {\n      this.logger.error('Failed to insert doc updates', e);\n      metrics.doc.counter('doc_update_insert_failed').add(1);\n      throw new FailedToSaveUpdates();\n    }\n    return timestamp;\n  }\n\n  protected async getDocUpdates(workspaceId: string, docId: string) {\n    const rows = await this.models.doc.findUpdates(workspaceId, docId);\n\n    return rows.map(row => ({\n      bin: row.blob,\n      timestamp: row.timestamp,\n      editor: row.editorId,\n    }));\n  }\n\n  async deleteDoc(_workspaceId: string, _docId: string) {\n    return;\n  }\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/doc/adapters/workspace.ts#L107-L143","documentation":"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.","triggerScenarios":"The Postgres `doc.createUpdates` insert repeatedly fails: connection loss, deadlock, constraint violation, disk full, or a transient DB restart that outlasts the `retryable` budget.","commonSituations":"Database connectivity blips during heavy sync; deadlocks from concurrent writers; storage exhaustion; schema/data drift causing constraint errors; DB maintenance windows.","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)."],"exampleFix":"// before\nawait adapter.pushDocUpdates(workspaceId, docId, updates, editorId);\n\n// after\nasync function pushWithRetry(updates, attempt = 0) {\n  try {\n    return await adapter.pushDocUpdates(workspaceId, docId, updates, editorId);\n  } catch (e) {\n    if (e instanceof FailedToSaveUpdates && attempt < 3) {\n      await sleep(2 ** attempt * 200);\n      return pushWithRetry(updates, attempt + 1);\n    }\n    throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"// Coalesce and dedupe updates to reduce DB insert pressure\nasync function coalesceAndPush(workspaceId: string, docId: string, updates: Uint8Array[]) {\n  const deduped = await filterValidDocUpdates(workspaceId, docId, updates);\n  if (!deduped.length) return 0;\n  return adapter.pushDocUpdates(workspaceId, docId, deduped, editorId);\n}","typeGuard":"function isFailedToSaveUpdates(e: unknown): boolean {\n  return (\n    typeof e === 'object' && e !== null &&\n    (e as { code?: string }).code === 'failed_to_save_updates'\n  );\n}","tryCatchPattern":"async function pushWithRetry(updates: Uint8Array[], attempt = 0) {\n  try {\n    return await adapter.pushDocUpdates(workspaceId, docId, updates, editorId);\n  } catch (e) {\n    if (isFailedToSaveUpdates(e) && attempt < 3) {\n      await sleep(2 ** attempt * 200);\n      return pushWithRetry(updates, attempt + 1);\n    }\n    throw e;\n  }\n}","preventionTips":["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."],"tags":["doc-storage","workspace","postgres","persistence","retry","internal-server-error"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}