Skip to content

refactor: unify sync and async prolly engine#24

Merged
forhappy merged 72 commits into
mainfrom
codex/async-first-prolly-engine
Jul 20, 2026
Merged

refactor: unify sync and async prolly engine#24
forhappy merged 72 commits into
mainfrom
codex/async-first-prolly-engine

Conversation

@forhappy

@forhappy forhappy commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make ProllyEngine<S: AsyncStore> the sole production owner of ordered-tree algorithms
  • expose the complete native async surface through AsyncProlly<S> and a ready-only synchronous adapter through Prolly<S>
  • remove the full logical-map async mutation rebuild and retire duplicate production sync traversal/mutation paths
  • add adaptive scan-resistant node-cache admission so large bounded scans preserve point-read working sets without raising memory limits
  • add a universal NodePublication contract across sync, async, remote, wrapper, transaction, and foreign-store boundaries
  • treat publication origins as advisory optimization evidence while keeping validation, atomicity, durability, and error behavior mandatory
  • add the native local Turso adapter, with explicit cloud push/pull behind the optional turso-cloud-sync Cargo feature
  • add reproducible local-only SQLite-sync, Turso-async, in-memory, and all-local-adapter revision gates

Architecture outcome

ProllyEngine<S: AsyncStore> is now the canonical core. AsyncProlly<S> uses it directly. Prolly<S> uses the same engine through SyncStoreAsAsync; ready-only means its internal futures must complete on their first poll, not that the facade is read-only.

Reads, range traversal, mutations, builders, diff/merge, proofs, order statistics, sessions, manifests/hints, snapshots, garbage collection, transactions, and versioned maps share the async-first services. Stored nodes are validated for CID, format, and structure before cache admission.

Bounded-cache scans now use adaptive admission in that shared engine. A cold scan still admits validated nodes and warms the cache. A scan opened over existing decoded read nodes, or following a non-empty single/batch point read in the same borrowed or owned session, reuses cache hits but does not admit one-pass continuation misses after the seek path. Disabled and unbounded caches retain their existing semantics.

NodePublication supplies stable semantic context to every store family. Default implementations preserve the existing batch or combined node-plus-hint behavior. Unknown origins use the conservative general path. The first measured override is Turso: only PointUpsert uses deferred transaction acquisition; general publications, named-root compare-and-swap, and coordinated commits remain immediate.

Correctness

  • sync and async writers remain canonical-root equivalent across append, random, and clustered workloads
  • sync, native async, and owned-session scans return the same ordered data while preserving bounded-cache point locality
  • cold and unbounded scans continue admitting nodes for reuse; bounded warm scans cannot displace the primed point path with continuation misses
  • every publication origin and transparent wrapper is covered by conformance tests
  • publication waits for backend acknowledgment; cancellation cannot expose a returned tree or partially published named root
  • CID mismatches and structurally corrupt nodes fail before cache admission or remote backend invocation
  • node-plus-hint writes remain atomic where supported
  • transaction overlays absorb publication metadata and retain their existing commit semantics
  • foreign bindings receive the same origin, entries, and optional hint, with safe general fallback behavior

The publication design and plan are in docs/superpowers/specs/2026-07-19-point-publication-intent-design.md and docs/superpowers/plans/2026-07-19-point-publication-intent.md. The cache design and plan are in docs/superpowers/specs/2026-07-19-scan-resistant-node-cache-design.md and docs/superpowers/plans/2026-07-19-scan-resistant-node-cache.md.

Latest local performance evidence

The universal-publication comparison uses baseline a2f4e7a3066d2259783c8d815b45c0461c77b06b and engine candidate 81357948363fdb51dbc201ae5ba1a7377bb297e0. Later commits contain benchmark tooling, generated evidence, inventory, and documentation.

  • foundation: 21/21 groups passed, 840 raw revision rows, 20 alternating pairs
  • focused native SQLite-sync/Turso-async: 24/24 groups passed, 960 raw revision rows, 20 alternating pairs
  • all local stores: 108/108 groups passed, 5,490 raw rows across memory, file, SQLite, RocksDB, SlateDB, and Turso
  • PGlite is explicitly recorded as unavailable because @electric-sql/pglite was not installed; it is not reported as a pass

Focused Turso point-publication paired medians:

Pattern Latency Throughput p50 p95
append -64.90% +184.94% -67.40% -65.25%
clustered -69.93% +232.61% -74.23% -65.46%
random -57.13% +133.27% -62.33% -49.29%

The worst focused non-target paired median latency change was +4.07%; the worst non-target p95 change was +4.82%.

The broad screen uses five alternating pairs with three samples per revision/pair. Every flagged group was preselected for one complete 20-pair, three-sample confirmation with no early stopping. The authoritative composed result replaces all 25 flagged groups and retains the other 83 screen groups. Its 5 us median and 10 us p95 resolution floors apply only to the broad local-adapter gate, not to focused or foundation acceptance.

Full publication method and artifacts: performance-results/node-publication-local-adapters-2026-07-19/findings.md.

Five-million-record cache re-evaluation

The final rebuilt prolly_compare --records 5000000 --phase fresh --workload random run validated every operation. Compared with the pre-cache-change run:

Operation Before After Change
write 639.916 ns/op 662.367 ns/op +3.5%
point read 9,987.898 ns/op 9,876.401 ns/op -1.1%
range scan 66.468 ns/entry 42.263 ns/entry -36.4%

The write result remains inside observed run-to-run variance, point reads remain stable, and the large scan improves materially. Small-cache regression tests directly verify zero scan-driven eviction of primed point paths, including same-session single reads, batch reads, owned sessions, and the native async path.

Scale-run boundary

The earlier async-first scale matrix in performance-results/sqlite-turso-local-async-first-final-r2-2026-07-18/ completed all 10K-through-2M sizes, 432 measurement cells, and 36 fixtures. It remains earlier scale evidence.

A new full-scale follow-up for the universal-publication candidate was stopped at the requester's direction after 504 data rows. That partial output is outside the repository and is not used by any completed-result claim or acceptance gate.

Fresh verification

  • cargo fmt --all -- --check
  • no-feature, default, all-feature, and wasm32-unknown-unknown checks
  • cargo test --workspace --all-features: 497 core unit tests plus every integration binary passed; one explicitly ignored long stress test
  • cargo test --doc --workspace --all-features: 74 passed
  • strict all-target, all-feature Clippy and rustdoc warnings
  • SQLite adapter: 18 tests passed plus strict Clippy
  • Turso adapter with all features: 15 tests passed plus strict Clippy; the credential-gated cloud test performed no network sync without credentials
  • both local benchmark crates passed all-target checks
  • 45 benchmark-tool and binding-inventory unit tests passed
  • evidence recomposition byte-matches the checked-in authoritative raw dataset
  • normal binding inventory completeness passes for 2,939 public Rust operations with no missing or stale symbols
  • scan-resistant cache regressions pass through sync, native async, same-session point/batch reads, owned sessions, and unbounded-cache reuse

Strict binding check --release is not green: the repository-wide contract still contains 2,589 planned mappings. This PR does not claim that broader binding-release backlog is complete.

Benchmark boundaries

  • every reported measurement uses local files only
  • Turso Cloud sync was disabled; no credentials, push(), or pull() were used
  • SQLite and Turso are measured through their preferred production contracts: native synchronous SQLite and native asynchronous Turso
  • local durability defaults are recorded but are not asserted to have identical fsync semantics

Scope and integration status

This PR now contains 71 commits through 7a77cbbe against main. The latest cache commits are:

  • 200df59b — design adaptive scan-resistant cache admission
  • 7a77cbbe — implement and verify bounded scan resistance

The final publication-evidence commits remain:

  • bbfcc6fa — harden paired sampling and fixed confirmations
  • 98c075fa — refresh the public binding inventory
  • 97236b89 — record normalized local performance evidence

The PR is open and currently conflicts with main. Resolve that integration conflict before review/merge without weakening the correctness gates or reintroducing duplicate algorithm paths.

forhappy added 30 commits July 18, 2026 17:21
@forhappy
forhappy marked this pull request as ready for review July 20, 2026 01:21
Resolve the async-first architecture against current main, retain shared lineage optimizations, and add evidence-based ready paths for native sync reads and version operations. Remove the replay round cap in favor of a progress invariant so large async workloads preserve correctness.
@forhappy

Copy link
Copy Markdown
Contributor Author

Updated PR #24 with current main and resolved all merge conflicts in 2dd0ee7.

Verification:

  • cargo test --all-features: passed, including 497 library tests, all integration targets, and 74 doc tests
  • cargo clippy --all-targets --all-features -- -D warnings: passed
  • cargo fmt --all -- --check: passed
  • no unmerged paths or conflict markers; staged diff checks passed

Rust performance regression verification, lower elapsed time is better:

  • Interleaved same-session 1M full matrix, 6 fresh/mutation x append/random/clustered scenarios x 3 repetitions, current vs exact pre-async binary: write -37.24%, point read -0.13%, range scan -14.33%
  • Interleaved same-session 10M fresh-append, 3 repetitions, 131072-node / 2 GiB cache, current vs exact pre-async binary: write -44.75%, point read -1.16%, range scan -19.30%
  • Final 10M full matrix, 18 validated runs: median write 2.232 s, point read 97.480 ms, range scan 58.234 ms; median RSS 2.952 GB, max RSS 7.225 GB, zero swaps
  • Every workload digest and result count validated; stderr was empty for all 18 runs

Version operation verification:

  • 100K records at 1% and 30% density across append, random, and clustered locality
  • 42/42 rows validated for full/range diff, patch generate/apply, and convergent/disjoint/conflict merge
  • Median throughput included 3.13M diff edits/s and 6.97M disjoint merge edits/s

The version workload also exposed a fixed 256-round async replay correctness limit for sparse multi-level edits. The cap is now replaced by a no-progress invariant, and native sync lineage batch/merge uses the canonical ready writer while retaining lineage in the shared engine.

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