Skip to content

#4685 PR 1 — ProjectionUpdateBatch insert-only flush-mode plumbing#4697

Merged
jeremydmiller merged 1 commit into
masterfrom
feature/4685-pr1-insert-only-flush-mode
Jun 9, 2026
Merged

#4685 PR 1 — ProjectionUpdateBatch insert-only flush-mode plumbing#4697
jeremydmiller merged 1 commit into
masterfrom
feature/4685-pr1-insert-only-flush-mode

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

First PR in the BulkWriter (binary COPY) rebuild optimization series tracked in #4685. Foundation only — no behavior change. Adds the seam where subsequent PRs plug in.

Series sketch

PR Scope
PR 1 (this) Classifier + BatchFlushMode property on ProjectionUpdateBatch. No behavior change.
PR 2 TRUNCATE step at the start of a per-(tenant, projection) rebuild. Partition-aware under UseTenantPartitionedEvents.
PR 3 BulkWriter (binary COPY) flush path that activates when FlushMode == InsertOnly. Reuses BulkInsertAsync's column binders.
PR 4 Composite-stage propagation review — verify the Upstream cache propagation handles stage 2 reading stage 1's un-flushed COPY data.
PR 5 ScaleTesting harness validation. Phase E (#4684) measurement confirms the ≥30% wall-clock reduction target on the write-side of a 20M-event Telehealth composite rebuild.

What ships in this PR

File Role
Marten.Events.Daemon.Internals.BatchFlushMode New enum -- Mixed (default; current flush path applies) and InsertOnly (eligible for the BulkWriter path in PR 3).
BatchFlushModeClassifier Pure, unit-testable transition rule. Initial = InsertOnly (empty batch is trivially insert-only -- BulkWriter is a no-op on zero rows); WithOperation(current, role) demotes to Mixed on any non-Insert role and is monotonic -- once Mixed, stays Mixed for the lifetime of the batch.
ProjectionUpdateBatch.FlushMode New public read-only property maintained incrementally in applyOperation. PR 3 dispatches on this signal.

Tests

10 cases in CoreTests/Events/Daemon/BatchFlushModeClassifier_pin.cs:

  • Initial state is InsertOnly
  • Insert keeps InsertOnly across multiple arrivals
  • Each of the non-Insert roles (Update, Upsert, Patch, Deletion, Events, Other) independently demotes to Mixed
  • Monotonic: once Mixed, subsequent Inserts can't bring it back
  • Interleaved sequence: InsertInsert (still InsertOnly) → Update (Mixed) → Insert (still Mixed)
Suite Result
New classifier tests 10/10 on net9.0
DaemonTests (rebuild + continuous catch-up exercise applyOperation on every role) 188/188 on net9.0

The new property maintenance is one increment per operation arrival -- the perf footprint on the hot path is unmeasurable. The plumbing for PR 3 has somewhere to plug in without any of this PR changing observable Marten behavior.

🤖 Generated with Claude Code

First PR in the BulkWriter (binary COPY) rebuild optimization series tracked in
#4685. Foundation only -- adds the seam where subsequent PRs plug in:

  * PR 2 will TRUNCATE the projection table at rebuild start, so every batch
    that follows is naturally INSERT-only.
  * PR 3 will dispatch the actual BeginBinaryImport flush path when the batch
    reports BatchFlushMode.InsertOnly.
  * Continuous catch-up keeps the existing per-row UPSERT path -- it mixes
    inserts, updates, upserts, patches, and the classifier reports Mixed for
    those batches.

**No behavior change in this PR.** The flush path is unchanged; only a new
public read-only property exposes how the batch classifies itself.

## What ships

* `Marten.Events.Daemon.Internals.BatchFlushMode` -- enum with `Mixed` (the
  default; current flush path applies) and `InsertOnly` (eligible for the
  BulkWriter path in PR 3).
* `BatchFlushModeClassifier` -- pure, unit-testable transition rule. Initial
  state is `InsertOnly` (empty batch is trivially insert-only; the BulkWriter
  path is a no-op on zero rows); `WithOperation(current, role)` demotes to
  `Mixed` on any non-Insert and is monotonic -- once Mixed, stays Mixed for
  the lifetime of the batch.
* `ProjectionUpdateBatch.FlushMode` -- public read-only property maintained
  incrementally in `applyOperation`. Subsequent PRs in the series dispatch on
  this signal.

## Tests

`CoreTests/Events/Daemon/BatchFlushModeClassifier_pin.cs` (10 cases):
  * Initial state is InsertOnly
  * Inserts keep InsertOnly across multiple arrivals
  * Each of the five non-Insert roles (Update, Upsert, Patch, Deletion, Events,
    Other) demotes to Mixed independently
  * Monotonic: once Mixed, subsequent Inserts can't bring it back
  * Interleaved sequence: insert -> insert (still InsertOnly) -> update (Mixed)
    -> insert (still Mixed)

DaemonTests pass 188/188 on net9.0 -- the projection rebuild + continuous
catch-up paths exercise applyOperation across every operation role on every
projection lifecycle, and the new property maintenance does not change any
existing assertion.

## Series sketch

  * PR 1 (this PR): classifier + property.
  * PR 2: TRUNCATE step at the start of a per-(tenant, projection) rebuild.
    Partition-aware under UseTenantPartitionedEvents.
  * PR 3: BulkWriter (binary COPY) flush path that activates when
    FlushMode == InsertOnly. Reuses BulkInsertAsync's column binders.
  * PR 4: Composite-stage propagation review -- verify the Upstream cache
    propagation handles stage 2 reading stage 1's un-flushed COPY data.
  * PR 5: ScaleTesting harness validation. Phase E (#4684) measurement
    confirms the ≥30% wall-clock reduction target on the write-side of a
    20M-event Telehealth composite rebuild.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 6a45fb0 into master Jun 9, 2026
8 checks passed
@jeremydmiller jeremydmiller deleted the feature/4685-pr1-insert-only-flush-mode branch June 9, 2026 00:47
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.

1 participant