0015. Project per entry: a projection may land for some entries of a batch, and Committed says why¶
Status: Accepted. Builds the two things 0014 deferred.
Context¶
ProjectedWrite (0014) projects a record in one fenced write: the view is one
object per record, and the projection lands whole or fails. A store appeared whose view is one row per entry,
each row its own conditional write. There, one throttled row out of many leaves the record partially projected:
most entries are in the view, one is not. Reported all-or-nothing, every input of the call is answered
Committed although most are visibly projected, no reason is attached, and the sweeper re-projects the whole
record for one row.
Decision¶
project(key, state) answers with what it left pending, Projection[Id, E]: for each entry whose row is not
known to be in the view, why. Empty means the view includes the whole record and the marker is cleared. Each
input's outcome is its own: Projected if the verdict does not list it, Committed(provisional, cause) with
its own cause otherwise. When project fails and resume fails after its retries, every input is Committed
with that error. resume answers the same Projection, for sweepers to log.
The store's contract for a view of many rows: each row write is idempotent (a row already present is present,
never a duplicate and never a cause); the marker clears only when every row is present, and only if the store
still holds the record, version and marker. project derives rows and ids from the committed record alone,
because resume has no inputs: a state that cannot yield them is not a candidate for per-entry projection.
A verdict with entries pending is final for the call: the library does not retry it, because fold.retry is
driven by one error and a partial verdict has none that is honest. The store owns its per-row retries and
chooses between failing the effect (re-read and retry the round trip) and reporting what is pending (answer
now, the sweeper finishes). A store whose view is one object per record uses ProjectedWrite.wholeRecord,
whose project never leaves anything pending, and behaves as under 0014.
Consequences¶
Outcome[+O]becomesOutcome[+E, +O]andCommittedcarries its cause;project's result type andresume's change. The release is 0.3.0; the migration is mechanical (wholeRecord, a second pattern variable,.unitonresume).- The library still holds nothing between calls (0005): which rows are missing is the store's knowledge, read from the record and the view.
- A client of a
Committedoutcome can now tell a throttle from a bug; it must still treat the input as durable and the view as late, and not resubmit to hurry it. - Rows need no fence of their own, only idempotence: a row is a function of its immutable entry, so an older projection writes the same row a newer one would. The marker clear is what the fence protects.
ProjectedWriteSpec,PartialProjectionSimulationandformal/ProjectedWriterPartial.tlacheck the laws; the model'sUnconditionalRows,EarlyClear,UnfencedClearandNoResumevariants must fail.formal/ProjectedWriter.tlaremains the whole-record model.