@@ -241,32 +241,38 @@ it writes the `DELTA TABLE` (so `mz_now()` there is A); `INTEGRATE`'s query read
241241the ` DELTA TABLE ` in B and reclocks B→A as it writes ` v ` (placing by
242242` mz_timestamp ` , driving the frontier via the reclock; ` mz_now() ` there is B).
243243
244- ** Representing the reclock (a choice that does not change the model).** The A→B
245- mapping can be a ** separate collection** , or live ** in-band** as progress markers
246- in the ` DELTA TABLE ` (` SUBSCRIBE ` 's ` mz_progressed ` rows — frontier advances with
247- no data), making the ` DELTA TABLE ` a persisted ` SUBSCRIBE ` stream. Either way the
248- semantics are identical; only the representation differs:
249-
250- - * In-band* gives a single-shard, single-CAS ` RECORD ` (data and reclock cannot
251- diverge), but burdens every consumer of the ` DELTA TABLE ` with progress-marker
252- noise — the same awkwardness as consuming ` SUBSCRIBE ` directly.
253- - * Separate collection* keeps the ` DELTA TABLE ` clean to query, at the cost of
254- committing the reclock atomically with the data — but that is a ** combined
255- CAS** the design already needs for the multi-output bundle
256- (` RECORD ` /` INTEGRATE ` /` DELETE ` at one ` T ` ), so it is nearly free.
257-
258- It is an open implementation choice (see Open Questions); the clean-data argument
259- leans toward a separate collection. (The conceptual symmetry is worth noting
260- regardless: ` CHANGES ` turns * changes* into data via ` mz_diff ` , the frontier can be
261- turned into data via ` mz_progressed ` , and ` INTEGRATE ` reads them back.)
244+ ** The reclock is a separate, engine-owned collection (decision).** The A→B
245+ mapping is kept as its own durable collection — a monotone mapping between the two
246+ domains, exactly the ** source-remap pattern** (` 20210714_reclocking.md ` ) — * not*
247+ as data in the ` DELTA TABLE ` . Two grounds:
248+
249+ - ** Clean reasoning.** The reclock is a separable object ` R ` , so ` v ` is provably
250+ ` INTEGRATE(DELTA TABLE) ` with its frontier driven by ` R ` — a function we can
251+ state and verify in isolation, rather than one entangled with parsing frontier
252+ information back out of the data stream.
253+ - ** No tampering, no validation.** It is ** engine-owned metadata, not user
254+ data** , so its invariants (monotone, well-formed) are * maintained* by the
255+ engine and * assumed* by every consumer — there is nothing for a user to corrupt
256+ (the frontier is not "just data" in a writable table) and nothing to
257+ defensively validate on read. It can also be retained independently of the
258+ data, long enough to interpret history.
259+
260+ This respects a boundary worth stating plainly: ** the ` DELTA TABLE ` is
261+ user-queryable data; the reclock is control-plane bookkeeping.** Folding the
262+ latter into the former is the root of both the tampering risk and the
263+ consumption noise that the in-band alternative incurs (see Alternatives).
264+
265+ The reclock is committed atomically with the recording — in the same multi-shard
266+ transaction the multi-output bundle (` RECORD ` /` INTEGRATE ` /` DELETE ` at one ` T ` )
267+ already requires — so the extra shard costs nothing in atomicity machinery.
262268
263269Replica races are an * exactly-once* concern — a delta must not be recorded twice
264270— not a correctness one (the data and frontiers each function sees are
265- deterministic); whichever representation, the guard is the CAS on the recording
266- commit. Non-determinism is confined to the recorded ** values** (frozen at
267- processing time), so ` v ` is a definite function of the recorded data . The
268- ` mz_timestamp ` remains queryable as data for "as of input-time" questions, within
269- ` RETAIN HISTORY` .
271+ deterministic); the guard is the CAS on the recording commit. Non-determinism is
272+ confined to the recorded ** values** (frozen at processing time), so ` v ` is a
273+ definite function of the recorded ` DELTA TABLE ` + reclock . The ` mz_timestamp `
274+ remains queryable as data for "as of input-time" questions, within `RETAIN
275+ HISTORY`.
270276
271277The write verbs differ by * shape* : ** ` RECORD ` → ` DELTA TABLE ` ** keeps the per-row
272278change log in domain B (with ` mz_timestamp ` as data); ** ` INTEGRATE ` → TVC**
@@ -507,22 +513,20 @@ implementation, PR #35967):
507513 operationally heavy; the point is to stay inside Materialize.
508514- ** Distributed locking instead of OCC commit.** Rejected (latency, brittleness,
509515 scalability), per the OCC read-then-write design.
510- - ** Reclock representation: in-band progress markers vs. a separate collection.**
511- A representation choice, not a model change (see Solution). In-band
512- (` mz_progressed ` , a persisted ` SUBSCRIBE ` ) gives a single-shard CAS but makes
513- the ` DELTA TABLE ` awkward to consume; a separate collection keeps the data clean
514- at the cost of a combined CAS the multi-output bundle already needs. Leaning
515- toward a separate collection for data cleanliness; left open.
516+ - ** In-band progress markers instead of a separate reclock.** Considered and
517+ rejected. Encoding the A→B mapping as ` mz_progressed ` rows in the ` DELTA TABLE `
518+ (a persisted ` SUBSCRIBE ` ) gives a single-shard CAS, but (a) makes the frontier
519+ * user data* — inviting tampering and forcing defensive validation on every read
520+ — and (b) burdens every ` DELTA TABLE ` consumer with progress-marker noise. Its
521+ single-CAS upside is moot since the multi-output bundle already needs a
522+ multi-shard transaction. (Conceptually neat — ` mz_diff ` is changes-as-data,
523+ ` mz_progressed ` is frontier-as-data — but not worth storing that way.)
516524
517525## Open questions
518526
519527- ** ` RECORDER ` object model & syntax.** Confirm `CREATE RECORDER WITH <rels > AS
520528 <actions >` . How is the trigger cadence expressed (vs ` COMMIT EVERY`)? Are the
521529 bundled actions always one atomic commit at ` T ` ?
522- - ** Reclock representation.** In-band progress markers (` mz_progressed ` , a
523- persisted ` SUBSCRIBE ` ) vs. a separate reclock collection — a representation
524- choice that does not change the model; trades single-shard CAS against
525- data-consumption cleanliness (the body leans toward a separate collection).
526530- ** Which domain does ` mz_now() ` / aging resolve in?** With ` INTEGRATE ` 's output
527531 reclocked onto the input timeline (domain A), time-based aging and ` mz_now() `
528532 in a body could mean ** domain A** (event-age — "last 30 days of * events* ";
0 commit comments