Skip to content

Pr extend coverage - #38157

Draft
def- wants to merge 3 commits into
MaterializeInc:mainfrom
def-:pr-extend-coverage
Draft

Pr extend coverage#38157
def- wants to merge 3 commits into
MaterializeInc:mainfrom
def-:pr-extend-coverage

Conversation

@def-

@def- def- commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Targeting #37924

DELETE, UPDATE and INSERT ... SELECT run on the coordinator, which holds a
write lock on the target table across the read and the write. Every such
statement therefore serializes against every other one on that table, and
the coordinator loop is occupied for the duration.

This sequences them from the session task instead, using optimistic
concurrency control. The selection is read through an internal subscribe,
which streams the mutation's diffs directly rather than peeking every
matched row and recomputing them. The write is submitted at the timestamp
the diffs were observed at, and the group committer refuses it if another
writer got there first. A refusal is a retry with a fresh snapshot, not a
lost update, and the retry budget is `max_occ_retries`.

Concurrency is bounded by a semaphore of `max_concurrent_occ_writes`
permits, acquired before the read holds so that queued operations do not
pin compaction on their read dependencies while they wait. That parameter
carries a domain constraint of at least 1, since zero permits would leave
every read-then-write waiting out its `statement_timeout`, which is what
the new `U32InRange` constraint expresses.
`statement_timeout` is enforced in one place, the `select!` that owns the
whole operation, because every phase can block: permit acquisition,
linearization against a far-future `as_of`, and the retry loop itself.

The path is off by default and gated by
`enable_adapter_frontend_occ_read_then_write`, which is read once at
startup and fixed for the life of the process. A mixed-mode window would
be unsound: the lock-based path excludes concurrent writers, the OCC path
detects them afterwards, and the two do not synchronize.

The `recursion_limit` sqllogictest golden drops from 418 to 388 UNION
branches, because sequencing a read-then-write from the session task adds
frames to the planning path that test measures.

Large mutations get faster because the subscribe streams diffs, small ones
get slower because each installs a dataflow where the old path used a
fast-path peek. That trade is deliberate and recorded in the design doc.
The OCC path linearizes its `as_of` before subscribing, but the subscribe
then follows Persist, and Persist runs ahead of the timestamp oracle. Group
commit appends to the txns shard first, which makes the write readable and
advances the table's upper, and calls `oracle.apply_write` only after.

A read-then-write whose selection consolidates to empty inside that window
concluded from state no oracle-timestamped read could reach yet, and
returned immediately because it had nothing to write. A
strict-serializable read issued after that response then still saw the
rows the response said were not there. Real time orders the read last and
no serial order explains the history.

Only the zero-row exits are exposed. A write is linearized for free,
because group commit answers the writer after applying the timestamp, and
the coordinator's path takes its read timestamp from the oracle and so
cannot observe state the oracle has not reached.

`OccOutcome` now separates the two cases rather than encoding "nothing to
write" as a `Committed` with no timestamp. `NoRowsMatched` carries the
timestamp emptiness was concluded at, and the caller waits for the oracle
to reach it before responding. The timestamp is absent only for a
selection that reads no persisted state, which has nothing to linearize
against. Waiting is what the existing `ensure_read_linearized` does, and
it is preferable to applying the write timestamp ourselves: forcing the
oracle forward off another writer's timestamp would stall subsequent reads
of unrelated collections until they caught up.

The `group_commit_before_apply_write` failpoint holds the window open, and
is the same one a second `environmentd` opens on its own with no ordering
against local Persist visibility.

Two mzcompose tests come with this.
`workflow_test_occ_zero_row_write_linearization` is the regression test.
It parks the winning DELETE inside the window, confirms from a
serializable read that the delete is visible in Persist and from a
strict-serializable read that the oracle cannot serve it yet, and only
then runs the UPDATE that has to report zero rows. Attempts where the
subscribe reported progress before the append, told apart by the write
conflict count, prove nothing and are retried.

`workflow_mixed_mode_read_then_write` is a guard rather than a regression
test. It runs as its own nightly step rather than from the composition's
default workflow, which iterates workloads rather than workflows, so the
file joins the linter's list of compositions whose extra workflows are run
separately.

The dyncfg is sampled once per process, so a rolling restart can
leave one instance sequencing on the coordinator behind in-process write
locks and another under OCC. Those locks never excluded a second process,
so this is not new, but what keeps the two from corrupting a row is the
catalog fence, and nothing states that. The test asserts the fence keeps
the commit windows disjoint, so it goes red if that stops holding.
@def-
def- force-pushed the pr-extend-coverage branch from a6ce43d to 538d19d Compare August 11, 2026 11:04
@def-
def- force-pushed the pr-extend-coverage branch from 538d19d to c1f3987 Compare August 11, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants