Skip to content

fix: harden WAL flush workers and merge-scan teardown under load - #111

Open
jamals86 wants to merge 25 commits into
mainfrom
codex/stabilize-wal-flush-workers
Open

fix: harden WAL flush workers and merge-scan teardown under load#111
jamals86 wants to merge 25 commits into
mainfrom
codex/stabilize-wal-flush-workers

Conversation

@jamals86

@jamals86 jamals86 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix KoldMergeScan teardown so portal ERROR / large unbounded counts no longer double-free scan memory and abort the backend.
  • Stabilize flush executors with keyset paging of pending jobs (avoids head-of-line starvation) and acknowledge previously committed WAL on flush fences without recording new apply progress in an uncommitted txn.
  • Harden autonomous flush / stress / passive-convergence coverage, and add teardown, truncate fail-closed, CMS journey/load, and first-time user e2e paths.

Test plan

  • scripts/run-all-tests.sh (fmt, clippy, unit, pg_test, e2e, examples, storage, SQL, memory, stress, benchmarks)
  • Focused: merge_scan_teardown_crash_safe, auto-flush scheduler e2e, chat-penetration stress smoke

Made with Cursor

Prevent backend abort after merge-scan portal ERROR, page pending flush
jobs with a stable keyset cursor, and keep recyclable WAL moving on flush
fences without claiming apply progress in an uncommitted txn. Expand
autonomous flush / teardown / journey coverage and retry slot-lock races
in stress and passive convergence.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Aug 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Storage bench (PG 16, 10k rows)

Comparison against base commit 8d96b0b1ff1bdbd7ef5aec808b0442615e2e6026 (previous successful CI on main).

Results for commit 499d6550cfc3363584f215ce82450dd89fc6542b. · 10000 rows · hot_limit=2000

Metric Base (PG) Current (PG) Δ Base (Async) Current (Async) Δ
foreground insert throughput 92875 ops/s 93890 ops/s +1.1% ✅ 94002 ops/s 79947 ops/s -15.0% ⚠️
insert p99 latency 107.67 ms 106.51 ms -1.1% ✅ 106.38 ms 125.08 ms +17.6% ⚠️
update p99 latency 13.77 ms 15.56 ms +13.0% ⚠️ 16.51 ms 22.96 ms +39.1% ⚠️
hot-query p99 latency 417 µs 439 µs +5.3% ⚠️ 414 µs 312 µs -24.6% ✅
hot-after-flush-query p99 latency 457 µs new 320 µs new
cold-query p99 latency 417 µs 448 µs +7.4% ⚠️ 2.48 ms 2.77 ms +11.7% ⚠️
hot-only query throughput (after flush) 2508 ops/s new 4967 ops/s new
hot+cold query throughput 2733 ops/s 3161 ops/s +15.7% ✅ 729 ops/s 782 ops/s +7.3% ✅
cold-only query throughput 2565 ops/s 2358 ops/s -8.1% ⚠️ 427 ops/s 437 ops/s +2.3% ✅
peak RSS during flush 57.70 MiB (before=57.70 MiB, after=55.22 MiB) 55.91 MiB (before=52.32 MiB, after=55.91 MiB) -3.1% ✅
flush duration 384.6 ms 412.3 ms +7.2% ⚠️
flush write throughput 20802 rows/s 19405 rows/s -6.7% ⚠️
flush write bandwidth 1.35 MiB/s 1.24 MiB/s -8.1% ⚠️
VACUUM duration 277.2 ms 283.2 ms +2.2% ⚠️ 63.7 ms 84.1 ms +32.0% ⚠️
local PostgreSQL storage 6.42 MiB 6.42 MiB ±0% 1.60 MiB 1.64 MiB +2.5% ⚠️
total hot+cold storage 6.42 MiB 6.42 MiB ±0% 2.12 MiB 2.15 MiB +1.4% ⚠️
insert speed† 92875 ops/s (11 µs/op) 93890 ops/s (11 µs/op) +1.1% ✅ 94002 ops/s (11 µs/op) 79947 ops/s (13 µs/op) -15.0% ⚠️
update speed† 72624 ops/s (14 µs/op) 64285 ops/s (16 µs/op) -11.5% ⚠️ 60551 ops/s (17 µs/op) 43553 ops/s (23 µs/op) -28.1% ⚠️
delete speed† 460077 ops/s (2 µs/op) 405823 ops/s (2 µs/op) -11.8% ⚠️ 242278 ops/s (4 µs/op) 223031 ops/s (4 µs/op) -7.9% ⚠️
query hot only (before flush) 2686 ops/s (372 µs/op) 2467 ops/s (405 µs/op) -8.2% ⚠️ 3418 ops/s (293 µs/op) 4596 ops/s (218 µs/op) +34.5% ✅
query hot only (after flush) 2508 ops/s (399 µs/op) new 4967 ops/s (201 µs/op) new
query with hot+cold (after flush) 2733 ops/s (366 µs/op) 3161 ops/s (316 µs/op) +15.7% ✅ 729 ops/s (1372 µs/op) 782 ops/s (1279 µs/op) +7.3% ✅
query cold only (after flush) 2565 ops/s (390 µs/op) 2358 ops/s (424 µs/op) -8.1% ⚠️ 427 ops/s (2341 µs/op) 437 ops/s (2287 µs/op) +2.3% ✅
index storage (hot + __cl) 856.0 KiB 856.0 KiB ±0% 384.0 KiB 384.0 KiB ±0%
table storage (hot + __cl) 5.59 MiB 5.59 MiB ±0% 1.23 MiB 1.27 MiB +3.3% ⚠️
└ cold Parquet 532.6 KiB 524.6 KiB -1.5% ✅

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

E2E (PG 16)

207 tests  +12   207 ✅ +12   14m 5s ⏱️ + 6m 0s
  1 suites ± 0     0 💤 ± 0 
  1 files   ± 0     0 ❌ ± 0 

Results for commit 87f0dfc. ± Comparison against base commit 43c2395.

This pull request removes 1 and adds 13 tests. Note that renamed tests count towards both.
e2e::e2e ‑ merge::merge_scan_outage::merge_scan_outage_requires_error_not_partial_hot_only_results
e2e::e2e ‑ dml::wal_only_seq_cursor::changes_since_last_rows_spans_multiple_cold_segments
e2e::e2e ‑ flush::flush_autonomous::auto_flush_converges_writes_arriving_during_active_flush
e2e::e2e ‑ flush::flush_autonomous::automatic_flush_does_not_trigger_itself_while_idle
e2e::e2e ‑ flush::flush_autonomous::large_transaction_with_savepoint_applies_atomically_then_auto_flushes
e2e::e2e ‑ flush::flush_autonomous::queue_generation_published_during_blocked_executor_is_not_lost
e2e::e2e ‑ flush::flush_contract::truncate_is_rejected_before_and_after_cold_publication
e2e::e2e ‑ flush::flush_queue_starvation::seventeenth_candidate_is_not_starved_by_busy_first_page
e2e::e2e ‑ flush::parquet_layout_options::manage_table_layout_options_control_future_parquet_flushes
e2e::e2e ‑ merge::merge_scan_teardown_crash_safe::merge_scan_followup_query_survives_seen_key_error_and_unbounded_count
e2e::e2e ‑ merge::order_column_cold_index::migration_order_defaults_to_segment_order_for_existing_table
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

pg_test (PG 17)

95 tests  +6   95 ✅ +6   2m 58s ⏱️ +13s
 2 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 87f0dfc. ± Comparison against base commit 43c2395.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

E2E (PG 17)

38 tests   37 ✅  46s ⏱️
 1 suites   0 💤
 1 files     1 ❌

For more details on these failures, see this check.

Results for commit 4b9ecc9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

pg_test (PG 15)

95 tests  +6   95 ✅ +6   2m 52s ⏱️ +20s
 2 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 87f0dfc. ± Comparison against base commit 43c2395.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

E2E (PG 18)

207 tests  +12   207 ✅ +12   13m 45s ⏱️ + 5m 30s
  1 suites ± 0     0 💤 ± 0 
  1 files   ± 0     0 ❌ ± 0 

Results for commit 7f58b84. ± Comparison against base commit 43c2395.

This pull request removes 1 and adds 13 tests. Note that renamed tests count towards both.
e2e::e2e ‑ merge::merge_scan_outage::merge_scan_outage_requires_error_not_partial_hot_only_results
e2e::e2e ‑ dml::wal_only_seq_cursor::changes_since_last_rows_spans_multiple_cold_segments
e2e::e2e ‑ flush::flush_autonomous::auto_flush_converges_writes_arriving_during_active_flush
e2e::e2e ‑ flush::flush_autonomous::automatic_flush_does_not_trigger_itself_while_idle
e2e::e2e ‑ flush::flush_autonomous::large_transaction_with_savepoint_applies_atomically_then_auto_flushes
e2e::e2e ‑ flush::flush_autonomous::queue_generation_published_during_blocked_executor_is_not_lost
e2e::e2e ‑ flush::flush_contract::truncate_is_rejected_before_and_after_cold_publication
e2e::e2e ‑ flush::flush_queue_starvation::seventeenth_candidate_is_not_starved_by_busy_first_page
e2e::e2e ‑ flush::parquet_layout_options::manage_table_layout_options_control_future_parquet_flushes
e2e::e2e ‑ merge::merge_scan_teardown_crash_safe::merge_scan_followup_query_survives_seen_key_error_and_unbounded_count
e2e::e2e ‑ merge::order_column_cold_index::migration_order_defaults_to_segment_order_for_existing_table
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

E2E (PG 15)

207 tests  +12   207 ✅ +12   13m 46s ⏱️ + 5m 29s
  1 suites ± 0     0 💤 ± 0 
  1 files   ± 0     0 ❌ ± 0 

Results for commit 87f0dfc. ± Comparison against base commit 43c2395.

This pull request removes 1 and adds 13 tests. Note that renamed tests count towards both.
e2e::e2e ‑ merge::merge_scan_outage::merge_scan_outage_requires_error_not_partial_hot_only_results
e2e::e2e ‑ dml::wal_only_seq_cursor::changes_since_last_rows_spans_multiple_cold_segments
e2e::e2e ‑ flush::flush_autonomous::auto_flush_converges_writes_arriving_during_active_flush
e2e::e2e ‑ flush::flush_autonomous::automatic_flush_does_not_trigger_itself_while_idle
e2e::e2e ‑ flush::flush_autonomous::large_transaction_with_savepoint_applies_atomically_then_auto_flushes
e2e::e2e ‑ flush::flush_autonomous::queue_generation_published_during_blocked_executor_is_not_lost
e2e::e2e ‑ flush::flush_contract::truncate_is_rejected_before_and_after_cold_publication
e2e::e2e ‑ flush::flush_queue_starvation::seventeenth_candidate_is_not_starved_by_busy_first_page
e2e::e2e ‑ flush::parquet_layout_options::manage_table_layout_options_control_future_parquet_flushes
e2e::e2e ‑ merge::merge_scan_teardown_crash_safe::merge_scan_followup_query_survives_seen_key_error_and_unbounded_count
e2e::e2e ‑ merge::order_column_cold_index::migration_order_defaults_to_segment_order_for_existing_table
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

pg_test (PG 18)

95 tests  +6   95 ✅ +6   2m 51s ⏱️ +10s
 2 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 87f0dfc. ± Comparison against base commit 43c2395.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

pg_test (PG 16)

95 tests  +6   95 ✅ +6   4m 19s ⏱️ + 1m 41s
 2 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 87f0dfc. ± Comparison against base commit 43c2395.

♻️ This comment has been updated with latest results.

jamals86 and others added 18 commits August 10, 2026 00:30
Prevent same-named tables in different schemas from sharing mirrors. Rehome mirror artifacts after source renames and refuse unsafe legacy shared-mirror teardown.
Move PG-free merge decisions into koldstore-merge, keep mirror/guard/index
names within PostgreSQL's 63-byte limit, and update dependent tests plus
release notice packaging so the full verification suite stays green.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… roots.

Ship dual Docker tags (PG18 multi-arch as latest, PG16 amd64, optional PG17 amd64) and fail closed when register_storage checks a non-empty filesystem base_path unless check => false.

Co-authored-by: Cursor <cursoragent@cursor.com>
CI Debian package builds failed when curling the signing key timed out and left gpg with empty input. Prefer the vendored ACCC4CF8 key (with HTTPS fallbacks) in build/Docker paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
force_stop the async worker before DROP so supervisor relaunch cannot deadlock AccessExclusiveLock, and retry CASCADE on transient deadlock.

Co-authored-by: Cursor <cursoragent@cursor.com>
Refresh the docs and SQL contract to distinguish a committed-WAL fence from read-your-own-uncommitted semantics, and to clarify that cold Parquet rows are not PostgreSQL heap tuples. The update documents current preview limits around DDL renames, backup/recovery expectations, security assumptions, and unsupported hot+cold behaviors. It also aligns the mirror apply comments and architecture docs with the current committed-change fence semantics.
Keep the WAL apply path off JSON: builtin int/bool keys stay native for in-batch identity, and order text is taken before PK cells so migration_order_by can reuse the same tuple.

Co-authored-by: Cursor <cursoragent@cursor.com>
jamals86 and others added 5 commits August 13, 2026 23:10
Switch rust-toolchain.toml to nightly and add .cargo/config.toml to use Cranelift codegen-backend and -Z threads=8 for faster local debug builds. Profile.dev/test use line-tables-only DWARF. CI and Docker set CARGO_ENCODED_RUSTFLAGS="" so stable rustc never receives -Z flags.

Add E2E footprint tests: wal_applier_footprint (idle RSS vs sibling backend, restart SLO) and flush_executor_footprint (startup SLO, peak RSS, concurrent hot PK latency during encode). Introduce WorkerFootprintBudget with env-var overrides. Include both suites in run_memory_checks.sh.

Document background worker process lifecycle in jobs-and-scheduler.md (what the 30-second intervals actually mean, fork model, dispatch path) and clarify that async_apply_watchdog_interval_ms is registered but not currently read by the applier loop.
Move the dev-only Cranelift setup out of committed Cargo config and document it as an optional local environment override, while pinning GitHub Actions and release jobs to `RUSTUP_TOOLCHAIN=1.96.0` so they do not pick up the repo's nightly toolchain or `-Z` flags. Also refresh a few shared Rust dependencies and apply small cleanup simplifications in flush planning, object-read stats, and pgoutput tag tests.
The WAL applier try-locks and yields instead of re-taking the advisory apply lock, so encode/upload stay concurrent with user commits. Also install clippy on the Examples + SQL CI job.

Co-authored-by: Cursor <cursoragent@cursor.com>
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