Table of Contents

Release 2026-08-17

This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at 2026-08-17.md, and llms.txt lists every page.

Part of the changelog.

A per-package patch advances Orleans.Lattice to 9.0.4 (see Added and Changed); all other packages remain at their 9.0.x/9.0.0 versions.

Added

  • Activation-time cold WAL replay is now resumable, bounding WAL retention for a leaf with a large un-snapshotted prefix. A leaf rebuilds its projection cache from the WAL on every activation, but the persisted ProjectionCheckpointOffset was previously advanced only by the post-pass-2 reconciliation at the end of the replay. A leaf whose un-snapshotted prefix could not be drained inside one activation window (a large WAL relative to Orleans' ~30s RuntimeRequested deactivation budget) therefore made no durable progress: a mid-replay deactivation discarded every applied entry, the next activation restarted from the same offset, and because the coverage-gated WAL GC (correctly) refuses to trim an un-snapshotted prefix, the retained WAL grew without bound while the leaf never converged. The replay now flushes the checkpoint incrementally at each slice boundary over the strictly contiguous, fully-applied prefix, which also drives the existing periodic snapshot capture. The advance is clamped below any deferred saga terminal / DeleteRange and below any unresolved saga prepare in the partition, so it can never license a checkpoint (or the materialiser pin) past a not-yet-durably-applied offset. A teardown then loses at most one flush interval, and the next activation rehydrates from the incremental snapshot and resumes from the last durable offset instead of replaying from zero; the now-covered prefix becomes trimmable, bounding retention. Steady-state activations are unaffected - a single-slice replay with no deferred terminals still flushes exactly once at the end of pass 1. (Orleans.Lattice 9.0.4, #1513)

Changed

  • GSet.Add commits a new element with a single hash probe. The grow-only-set insert (and the fold behind GSet.MergeDelta) previously probed the element's base64 key twice on a genuine insert - a Contains miss followed by an Add - and now commits through the span alternate-lookup's Add in one probe, still materialising the key string only when the element is new. Behaviour, iteration order, and wire format are unchanged and allocation is identical; a new GSet_Add microbenchmark shows the fresh-insert fold about 11% faster (3.87 us to 3.44 us for 64 inserts, N=100, non-overlapping 99.9% confidence intervals). (Orleans.Lattice 9.0.4, #1508)
  • A completed atomic-write saga now releases its staged batch payload on the terminal checkpoint, bounding the store footprint of a bulk re-projection. A finished AtomicWriteGrain saga previously retained its full staged batch - Entries, PreValues, and the optional per-entry delta/delete channels - in persistent state for the entire AtomicWriteRetention window (default 48h), until the retention reminder cleared the record. For a single guarded write this is negligible, but across a high-cardinality bulk re-projection (one saga per touched key) the retained value bytes dominated the grain-store footprint and stayed pinned in process memory long after the saga finished using them. The terminal Completed checkpoint now releases those heavy byte-array fields and keeps only the lightweight outcome a completed saga needs (Phase, FailureMessage, KeyFingerprint, TransactionId), so idempotent re-entry and reminder-driven reactivation still resolve correctly. The release is crash-safe - the fields are snapshotted and restored verbatim if the terminal persist throws, matching the existing Phase rollback guarantee at that site - and the AtomicWriteBatchSize histogram still captures the entry count before the release. The PreconditionFailed path is left untouched. (Orleans.Lattice 9.0.4, #1511)