v5.2.9
Write stalls
The RocksDB WriteBufferManager no longer stalls writes by default (#2490). The process-wide WriteBufferManager is on by default at 1/3 of the block cache, and it was configured as a hard cap: when the budget filled, RocksDB parked every writer across every database in WriteBufferManagerStallWrites(), and nothing guaranteed the flush that would release them. ShouldFlush() only fires when mutable memory alone reaches half the budget, so a budget held by memory that is not mutable — the shape you get when it is spread across many column families — never dropped, and the only way out was a process restart.
The budget is now a soft cap: RocksDB schedules flushes more aggressively instead of blocking writers. Explicitly configured writeBufferManagerAllowStall values still win in both directions.
The trade is memory. With stalling off, each column family keeps its full derived conflict-check history (maxWriteBufferNumber * writeBufferSize) rather than having it derived to zero, so expect a higher memory floor on instances with many column families. rocksdb-js#821 is the other half of the fix.
Query planning
Table sizing for query planning is now O(1) (#2478). estimatedEntryCount() iterated the entire key space natively, on the main thread, once per store per 10-second memo window — and the memo never took effect, because the window was sampled before the call and had already expired by the time it returned. On a 28.6M-row table that is roughly 11 seconds per call, and the planner calls it once per additional condition, so a 10-condition operation measured around 100 seconds of main-thread stall with the Operations API unresponsive throughout.
It now reads rocksdb.estimate-num-keys, which is O(1) and returned an identical count on the affected table. The estimate skews high on overwrite- and delete-heavy data until compaction; every consumer is a relative-ordering or explicitly-estimated path, so that trade is deliberate. Two arithmetic sites that a zero estimate would have broken — the AND-group divisor, which yielded Infinity and silently disabled the adaptive filter/index switch, and the ne null subtraction, which could go negative — are guarded.
Replication configuration
The blob-gap escalation bounds are registered — replication.blobGapEscalationCycles and replication.blobGapEscalationMs now exist in CONFIG_PARAMS, the config validator, and the root config schema. env.get resolves only registered names, so without this the keys would sit in harper-config.yaml doing nothing while the compiled-in defaults stayed in force. The consumer is the Harper Pro escalation budget described in that release's notes; see the harper-pro v5.2.9 notes for the operator-facing behavior.
Also in this release
CI workflow sync for the cancellation migration (#2387), and test-only cleanups alongside the changes above.