Skip to content

logstore: cut allocation churn on the incremental log hot path#6803

Open
alleneubank wants to merge 2 commits into
tilt-dev:masterfrom
alleneubank:pr/logstore-alloc
Open

logstore: cut allocation churn on the incremental log hot path#6803
alleneubank wants to merge 2 commits into
tilt-dev:masterfrom
alleneubank:pr/logstore-alloc

Conversation

@alleneubank

Copy link
Copy Markdown
Contributor

Summary

After reducing subscriber work on log-only notifications, logstore was still
a major source of steady-state GC under a multi-span log storm:

  • empty checkpoint windows still cloned span maps
  • incremental ToLogList re-sent every span
  • toLogLines allocated heavily per rendered line

This PR:

  • Returns early from ContinuingLinesWithOptions when the checkpoint window
    is empty.
  • Clones only spans referenced by the checkpoint window (drops full
    cloneSpanMap for that path).
  • Scopes incremental ToLogList span payloads to segments actually sent
    (safe for tilt logs and the web client; covered by
    TestLogIncrementalSpansScopedToSegments).
  • Preallocates segments from newline counts; reuses a single line builder
    and cuts per-line prefix/builder churn in toLogLines / prefix helpers.

Motivation

On a ~200-span session shape after the PodMonitor skip, span cloning and
line materialization dominated remaining engine alloc profiles during
steady log ingress.

Evidence

  • Unit tests for scoped incremental spans and prefix characterization.
  • New benches in logstore_bench_test.go (empty window, narrow window,
    full-store render, continuing lines).
  • Reported microbench deltas (same shapes as in commit messages), e.g.
    full-store render ≈ 950µs / 16k allocs → ≈ 305µs / 2k allocs.
  • Hermetic log-only soak floors green with log rate within the ≥80% baseline
    band while validating the firehose path.

Test plan

  • go test ./pkg/model/logstore/
  • CI on this PR
  • Optional: go test -bench=. ./pkg/model/logstore/ if reviewing numbers

Every subscriber notification through ContinuingLines cloned the entire
span map (one span per pod/build) even when there was nothing to print,
and every incremental websocket update through ToLogList re-sent every
span. After the PodMonitor fix this was the dominant remaining source
of steady-state GC churn under a log storm (200-span session shape).

- ContinuingLinesWithOptions returns early when the checkpoint window
  is empty: 13.3us/203 allocs -> 1.6ns/0 allocs per no-op notification.
- Temp stores clone only the spans referenced by the checkpoint window
  instead of the whole span map (5-line window: 17.5us/247 allocs ->
  2.8us/45 allocs); the now-unused cloneSpanMap is deleted.
- Incremental ToLogList updates carry only spans referenced by the sent
  segments: 7.9us/216 allocs -> 430ns/15 allocs. Safe for both clients:
  `tilt logs` resolves segments strictly against the update that
  carried them and the web UI merges spans additively; locked in by
  TestLogIncrementalSpansScopedToSegments.
- segmentsFromBytes preallocates segments via newline count (100-line
  append: 10 -> 3 allocs).

Benchmarks live in the new logstore_bench_test.go. Full-path k8s
harness verdict: pass, all floors green, log ingestion at 100.9% of
baseline.
Rendering a log line cost ~8 heap allocations: a fresh logLineBuilder
and segments slice per line, a per-line []LogLine{} returned by build,
two allocations in SourcePrefix (strings.Repeat + fmt.Sprintf), unsized
strings.Builder growth, and append-doubling of the result slice. After
the span-cloning fixes this was the dominant remaining allocation
source in the terminal-stream path (toLogLines at 8.8% of cumulative
allocations under a steady-state log storm).

- One logLineBuilder value is reused across all lines of a toLogLines
  call; start/active/reset recycle its segment buffer. newLogLineBuilder
  and build are deleted.
- appendTo appends built lines into the caller's result slice instead
  of allocating a per-line slice.
- appendSourcePrefix writes the padded manifest prefix directly into
  the line's builder with zero allocations. SourcePrefix output is
  unchanged, locked by a new characterization test.
- toLogLines pre-counts line-starting segments to size the result
  once; buildMainLine/buildSpaceLine/linesToString pre-size their
  builders so each line's text is a single allocation.
- ContinuingStringWithOptions deduplicated into linesToString.

Benchmarks (200-span shape, new BenchmarkStringFullStore baselined
before optimizing): full-store render 950us/16039 allocs/2.09MB ->
305us/2003 allocs/513KB; ContinuingLines with 5 new lines 3.1us/45
allocs -> 760ns/8 allocs. Full-path k8s harness verdict: pass, all
floors green, log rate at 93.6% of baseline (floor 80%); toLogLines
cumulative allocations 138.9MB -> 50.9MB in the steady-state profile.
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