{"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":"critical","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":179,"sourceCode":"              : await this.runtime.appendWorkspaceDocUpdatesV1({\n                  ...input,\n                  ...contract,\n                });\n          done += batch.length;\n        }\n      });\n\n      if (isNewDoc) {\n        await this.event.emitDetachedAsync('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":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/doc/adapters/workspace.ts#L161-L197","documentation":"PgWorkspaceDocStorageAdapter.pushDocUpdates wraps batched inserts of Yjs binary updates (models.doc.createUpdates in batches of 10 inside a retryable() block) plus enqueueing of the doc.mergePendingDocUpdates job. Any database error that survives the retry budget is logged, increments the doc_update_insert_failed metric, and is rethrown as FailedToSaveUpdates (internal_server_error / failed_to_save_updates). This is a server-side persistence failure, not a client-input problem.","triggerScenarios":"Pushing doc updates while Postgres is unreachable or the connection pool is exhausted; a foreign-key/unique violation when the workspace was deleted (deleteSpace) while a client was still pushing updates; deadlocks or serialization failures exceeding the retry budget; schema drift after a missed migration.","commonSituations":"Self-hosted deployments with an under-provisioned database, disk-full events, managed DB failovers, or a deployment where prisma migrations were not applied; dev environments running an old branch against a new schema.","solutions":["Read the preceding server log line 'Failed to insert doc updates' — it contains the underlying database error that this wrapper hides","Check database health, connectivity, and that all migrations are applied (prisma migrate deploy)","Retry the push after the DB issue is fixed — client-side Yjs updates are still buffered locally and can be re-pushed","If the FK points at a deleted workspace, discard the local updates for that workspace or recreate the workspace before pushing"],"exampleFix":"// before\nawait docSync.push(workspaceId, docId, updates);\n\n// after\ntry {\n  await docSync.push(workspaceId, docId, updates);\n} catch (e) {\n  if (e.extensions?.code === 'failed_to_save_updates') {\n    // server logged root cause; keep local updates and retry later\n    return scheduleRetry(updates);\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"// pre-flight: confirm the workspace still exists so the FK target is valid\nconst exists = await db.workspace.findUnique({ where: { id: workspaceId } });\nif (!exists) return discardLocalUpdates(workspaceId, docId);","typeGuard":"function isFailedToSaveUpdates(e: unknown): boolean {\n  return (e as { extensions?: { code?: string } }).extensions?.code === 'failed_to_save_updates';\n}","tryCatchPattern":"try {\n  await pushUpdates(workspaceId, docId, updates);\n} catch (e) {\n  if (isFailedToSaveUpdates(e)) {\n    keepLocalUpdatesForRetry(updates); // updates are still client-side; retry with backoff\n    return scheduleRetry();\n  }\n  throw e;\n}","preventionTips":["Monitor the doc_update_insert_failed metric and alert on it","Keep prisma migrations in lockstep with the deployed schema","Never delete a workspace while sync clients are still pushing — quiesce first"],"tags":["database","postgres","yjs","doc-updates","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"}