fix(historian): incremental batch-append bookkeeping; genuine group-commit durability pin

This commit is contained in:
Joseph Doherty
2026-08-16 04:18:38 -04:00
parent fbe8aaad9d
commit 7320586512
3 changed files with 98 additions and 14 deletions
@@ -25,6 +25,25 @@ public interface IHistorizationOutbox : IDisposable
/// per-entry fsyncs; under Periodic it is indistinguishable from N AppendAsync calls.
/// An empty list is a no-op.
/// </summary>
/// <remarks>
/// <para>
/// <strong>Failure semantics — prefix, never orphan.</strong> The call is not atomic. If it
/// throws part-way through (cancellation, an I/O fault), the entries appended before the
/// failure remain in the buffer and are fully tracked: they are visible to
/// <see cref="PeekBatchAsync"/>, ackable via <see cref="RemoveAsync"/>, counted by
/// <see cref="CountAsync"/>, and subject to the capacity bound — exactly as if the caller
/// had passed only that prefix. Nothing is left buffered-but-unackable, which would be
/// redelivered on every drain pass and block the buffer's head from advancing. Entries
/// after the failure point are not appended at all; the caller may retry them.
/// </para>
/// <para>
/// <strong>Accepted residual.</strong> The batch's single durability commit runs after the
/// last append, so a throw before it leaves that prefix tracked but not yet committed —
/// served correctly for the life of the process, but absent after an unclean restart. This
/// is the same enqueue-then-commit window <see cref="AppendAsync"/> has always had, widened
/// from one entry to the batch.
/// </para>
/// </remarks>
/// <param name="entries">The value records to buffer, in append (FIFO) order.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>A task that represents the asynchronous operation.</returns>