Skip to content

Data plane: anticipatory segment rolling to remove the roll-boundary stall #155

Description

@Migorithm

Problem

Segment rolls are currently reactive: the data plane seals when size_bytes >= limit, checked on commit. The roll then travels the metadata log — seal → RollSegment Raft commit → new-segment assignment delivered to the data plane — before the range can serve the next offset.

That opens a write-availability gap at every roll boundary. While the roll is in flight the range's write head is in limbo: the old segment is full/sealing and the successor isn't ready yet (producers hit the retriable "segment not ready / not write leader" window — see d6). A caught-up tail consumer inherits the gap: it drains the sealing segment to its end, then waits out the roll.

To be precise about what this is not: it is not a disk cold read — the data on both sides of the boundary is hot in cache, nothing loads from a segment file. The stall is purely the wait for the roll to materialize (a Raft round-trip to the shard group's majority + assignment propagation). It shows up as periodic latency jitter at each ~1 GB roll, which matters for latency-sensitive tailing.

Root cause / tradeoff

EastGuard places each segment independently (the successor can land on different nodes) — the property that buys flexible scaling and the D5 repair "lottery." That independence is exactly what forces coordination on a roll. Same situation as Pulsar (per-ledger ensemble) and Northguard (segments assigned to potentially new brokers). Kafka avoids the gap entirely by not placing segments independently: a partition's segments share its replica set, so a roll is a local file open with no coordination.

Proposed fix — anticipatory rolling

Decouple the roll trigger from the write-head switch:

  • Trigger the roll at a soft high-water mark (e.g. ~90% of the limit), not at the hard cap.
  • Keep the current segment writable while the roll is in flight. The RollSegment commit + assignment delivery overlap with continued writes to the current segment; by the time it would reach the hard cap, the successor's tracker, placement, and connections are already live.
  • The seal still fixes the old segment's end at its real committed end, and the switch is gated on "successor ready" — which has already happened. The tail consumer flows into the successor with no gap.

Size the slack between the soft mark and the hard cap to cover the roll latency at the segment's write rate.

Offset-continuity wrinkle (vs Pulsar)

EastGuard keeps continuous offsets across a range's segment chain (segment[N].end_offset + 1 == segment[N+1].start_offset), and the old segment's end isn't known until it actually seals. So we can't fully pre-bake the successor's start offset the way Pulsar can (per-ledger entry IDs restart at 0). Hence the early-trigger + write-headroom shape rather than pure pre-creation: the expensive coordination (placement decision, assignment, data-plane readiness) is hidden under the tail of the current segment, while the start-offset binding still happens at the real seal.

Scope

  • Server-side, data-plane concern. The client / consumer prefetch path cannot fix this — it's the roll latency itself, not a client resolution cost.
  • Relevant docs: diagrams/data-plane/d3_segment_roll_integration.md, and the size-based-seal note in CLAUDE.md.

Prior art

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions