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 settle tries again; nothing absorbs past it. settle runs 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 by validate and authorize.
  • Two devices edit one cell. The later commit wins everywhere; the loser is told.
  • A server restarted over a compacted journal. settle starts 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. onReinstall is 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, so apply would skip each new edit and the replicas would count it absorbed. settle refuses to start while tableRevision is 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's validate does 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

HereElsewhere
a row's revisiona 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 olderEventStoreDB's stale-read check: the client's position is past the read model's
committed, not yet in the rowLinear's completed transactions waiting for their lastSyncId
dropping edits the base hasReplicache discarding mutations up to lastMutationID; Electric discarding optimistic state once its txid arrives
journal and tableKleppmann's system of record and derived data