Skip to content

DNM: upsert v2 hydration optimizations and payload-separated state - #38719

Draft
DAlperin wants to merge 14 commits into
MaterializeInc:mainfrom
DAlperin:dov/upsert-perf-overnight
Draft

DNM: upsert v2 hydration optimizations and payload-separated state#38719
DAlperin wants to merge 14 commits into
MaterializeInc:mainfrom
DAlperin:dov/upsert-perf-overnight

Conversation

@DAlperin

@DAlperin DAlperin commented Sep 8, 2026

Copy link
Copy Markdown
Member

Motivation

DNM. Draft benchmark branch only. Upsert v2 hydration spends substantial work allocating and moving wide payloads through its spill and merge path. This branch combines the optimization experiments and payload-separated upsert prototype so CI publishes images for controlled comparisons. Optimization commits remain separate for review.

Description

  • Bound serialized column chunks to 2 MiB, except indivisible oversized rows. Size merge reservations and output checks from observed row width.
  • Bound pool insertion debt under concurrent enforcement. Encode admission-denied inserts directly to extents.
  • Add enable_upsert_async_reads: offload upsert drain reads to the blocking executor, with at most eight queued or running reads per pool and cancellation-safe permit ownership. This flag controls drains and probes independently of merge scheduling.
  • Add enable_upsert_async_merges: use Differential's pollable chunk merge pipeline and batcher with a vendored fueled spine for source-stash insertion and sealing, feedback batching, and feedback trace compaction. Pending reads preserve merge phases, remaining fuel, and published input batches. Materialize continues to use its existing Differential trace interfaces. A private timestamp/frontier adapter connects maintenance to the newer DD/Timely dependency without changing stored timestamps or Timely operators. The DD polling APIs are pinned to 3659671246e0b5635a8128cc27d65f12ecf7dda5 in the fork. Only the spine scheduling implementation remains vendored. DNM-only dependency-policy exceptions allow the two pinned Git sources and duplicate DD/Timely versions. Merge reads yield to Timely and share a 256 MiB decoded-input budget across workers and sources. Trace replacement publishes atomically after completion and respects aggregated reader compaction holds. This path requires chunked upsert with payload separation disabled.
  • Add enable_column_chunk_direct_compressed_output: serialize compressed generations directly to extents, avoiding resident admission and the intermediate serialized allocation. Younger identity-coded generations keep the existing path.
  • Add enable_upsert_payload_stash: keep row bytes in shared pool-backed blocks and merge columnar keys, times, differences, and payload handles. Manifests retain referenced blocks through compaction and restashing. Feedback compares keys and fingerprints before resolving exact payload bytes, so equal updates cancel across physical allocations without a persistent interner. The bounded comparison cache reuses blocks across argument positions and within-block comparisons. Source encoding publishes only winners after offset consolidation, and output decoding groups reads by block within bounded probe windows.

The flags default off in production and on in the test configuration. The payload path uses Materialize's existing Timely and Differential dependencies. The standalone local proxy-join harness remains separate and is not required to build these images.

This is a prototype, not a complete out-of-core runtime. Manifests and chunk summaries remain resident. Payload merge comparisons use synchronous reads into a two-block cache outside shared read admission. Probe histories and other temporary metadata still need bounds, payload decoded-read budgets are per dataflow, and partially live blocks retain dead bytes. Oversized rows retain an inline fallback. The design document describes the shared operator model and remaining boundaries.

Verification

Regression coverage exercises byte bounds, oversized rows, concurrent admission with stalled enforcement, resident and spilled round trips, async cancellation and concurrency limits, and direct extent accounting and codec equivalence. Shared payload tests cover ownership, admission, exact equality across independent locators and forced fingerprint collisions, timestamp compaction, block reclamation, and cache reuse across swapped arguments. A deterministic local microbenchmark compares unique keys, cancellation, replacement churn, forced collisions, and timestamp advancement against a canonical-handle control. It verifies exact output and block retirement, and records merge/advance timing and payload decompression counts separately.

The upsert operator comparison exercises paged, chunked, resumable, and payload-separated execution under forced spilling. Resumable batching is also compared against synchronous sealing across retractions, times, and re-stashing. Trace tests verify reader holds and that pending compaction keeps both input batches visible. Spine tests pause irregular fueled rollups to check visibility and continuation state. DD owns the chunk and merger suspension tests. Timestamp-adapter tests cover incomparable frontiers, lattice operations, and path-summary composition and overflow. It covers inserts, highest-offset selection, deletes, persisted feedback, resume filtering, ineligible restashing, errors, multiple payload blocks, and oversized rows.

Hydration comparisons include the original 12 million key seed and a skewed 192.36 million row seed across 105 sources on eight workers, with 1,900 byte payloads and a 0.2 pool budget fraction. The replacement resumable profile pins commit 09ed476925278025aed3aa35df08fa07e5d5fd10, keeps drain offload enabled, and compares the vendored Differential merge driver on/off twice in reversed order using the retained large seed. This third queue entry replaces the earlier fully vendored driver and uses the pinned DD polling APIs. The on/off comparison includes async admission and resumable maintenance as a unit. The existing runs retain their pinned images. Measurements retain hydration time, CPU, memory, extent traffic, image identity, and replica restart evidence. Resumable merge performance gains are not yet established.

Release note: None (draft performance experiment).

Reserve resident bytes before filling pool slots. If enforcement cannot make room within the bounded allowance, write the insertion directly to an extent and expose that fallback in pool metrics. This prevents an occupied single-flight enforcer from allowing an unbounded queue of resident insertions.

Add deterministic tests for a stalled enforcer and concurrent fills, including payload round trips and accounting after drops.
Add enable_upsert_async_reads for chunked stash drains and feedback probes. Read pooled bodies on Tokio's blocking executor with at most eight queued or running reads per pool. Submitted jobs retain their handles and permits through cancellation. Resident columns stay on the timely worker, and merge/seal reads remain synchronous.

Default the flag off in production and on in mzcompose defaults, register it for stress-test flag flips, and expose submitted/in-flight read metrics. This is an offload experiment: it adds scheduling and read-buffer allocation overhead, and does not pipeline reads within one drain.

Validate cancellation and admission bounds, copy-out without residency admission, straddled probe keys, and skipped chunks. Compare paged, synchronous chunked, and asynchronous chunked output in the upsert operator scenarios, including forced pool eviction.

Validation: 302 pool/timely utility tests passed; 14 focused async and upsert operator tests passed after the final harness changes. bin/fmt, flag-registration lint, storage cargo check, and clippy for all targets in mz-ore, mz-timely-util, and mz-storage passed. Full bin/lint remains blocked by macOS Bash 3 in check-protobuf and environment stripping in check-mzcompose-files under the jj workspace. No performance results yet.
Add enable_column_chunk_direct_compressed_output, off in production and on in mzcompose test defaults. Eligible spilled chunks encode directly into an actual-size extent without entering the resident slot pool. Preserve the compression depth floor and existing empty/oversize fallbacks. Typed columns use bounded retained serialization scratch; aligned columns encode from their existing words.

Expose Pool::insert_cold and a separate cold-insertion metric. Share extent registration and reclamation accounting with admission-denied insertions. Encoding remains synchronous and bypassing resident slots gives up write elision for short-lived chunks.

Test cold insertion, read admission, extent reuse, cleanup, empty/oversize handling, byte-identical typed/aligned serialization, and the compression floor. Exercise the upsert drain with direct output disabled/enabled and synchronous/asynchronous reads against the paged reference.

Validation: 331 selected utility and upsert tests passed. Clippy passed for all targets in mz-ore, mz-timely-util, mz-storage, and mz-compute. Formatting and flag-registration checks passed. Full lint has local tooling failures in check-protobuf (macOS Bash 3) and check-mzcompose-files (Git environment stripped under jj). Performance remains to be measured.
@DAlperin DAlperin changed the title DNM: Measure upsert v2 hydration optimizations DNM: upsert v2 hydration optimizations and payload-separated state Sep 9, 2026
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