Sync / Editing server data
Failure and recovery
- Offline. The edit stays pending in the outbox, through a reload, and is sent on reconnect.
- The table write fails or lags. The edit is committed and the row's
revision older, so it keeps showing. The next exchange's
settletries again; nothing absorbs past it.settleruns on exchanges, not on a timer. - A write runs twice. Harmless: it never moves a row back.
- A refused operation. Rejected by id, dropped by the replica, its cells marked; the outbox behind it still goes.
- A forged stamp, or a forged
AbsorbedEdits. Refused byvalidateandauthorize. - Two devices edit one cell. The later commit wins everywhere; the loser is told.
- A server restarted over a compacted journal.
settlestarts at the journal's floor, not at 0, so it does not ask for history that is gone. - A server reset. A new epoch: replicas rebuild and resend what they had
not sent; committed edits of the old history are gone.
onReinstallis told (reset), lifts what it held (clear), and reads its rows again, since their revisions counted the old history. The table must be reset with the journal: a table that outlived it holds revisions past every new sequence, soapplywould skip each new edit and the replicas would count it absorbed.settlerefuses to start whiletableRevisionis past the journal's cursor (TableAheadOfJournalError), on every exchange, until the two agree. - An edit to a row the table has not got. Refuse it at commit, in the
journal's
validate; its sender is told and its cell marked. Recovery cannot refuse what has committed: let through, the edit would change no row and vanish once absorbed. The registry'svalidatedoes this. - A sorted page. An unabsorbed edit shows its new value in its row's old position until the page is read again.
- The recovery cursor in a persistent deployment lives in the process; a restarted server recovers again from the floor, and the ledger keeps each intent from writing twice within an epoch.
Limits and when not to use it
- A cell is last-writer-wins. Text is not merged; for that, see CRDT-backed documents.
- The journal and the table must survive, or be rebuilt, together.
- If every replica can hold the whole state, a plain Sync slice is simpler.
- If edits need no offline life, a Remote mutation is simpler.
Terms you may know
| Here | Elsewhere |
|---|---|
a row's revision | a read model's checkpoint or position (Marten's projection progress, EventStoreDB's subscription checkpoint, Axon's tracking token), kept per row |
| an edit shows while the row's revision is older | EventStoreDB's stale-read check: the client's position is past the read model's |
| committed, not yet in the row | Linear's completed transactions waiting for their lastSyncId |
| dropping edits the base has | Replicache discarding mutations up to lastMutationID; Electric discarding optimistic state once its txid arrives |
| journal and table | Kleppmann's system of record and derived data |