Skip to content

querier: release LSS query result buffers deterministically - #447

Merged
vporoshok merged 5 commits into
ppfrom
fix/lss-query-result-explicit-close
Jul 29, 2026
Merged

querier: release LSS query result buffers deterministically#447
vporoshok merged 5 commits into
ppfrom
fix/lss-query-result-explicit-close

Conversation

@vporoshok

Copy link
Copy Markdown
Collaborator

Summary

The C-allocated series-id result buffer returned by Querier::query (Go::Slice<uint32>) was kept alive by the series sets and freed only lazily by a Go GC finalizer. Because the real bytes live off the Go heap (jemalloc), the GC sees almost no pressure and finalization lags, so thousands of over-allocated buffers (capacity = max matcher cardinality, not the deduplicated result size) accumulate. In heap profiles this path (prompp_primitives_snapshot_queryQuerier::queryrealloc) was the dominant consumer (~10+ GB, ~14k live buffers, avg ~690 KB each).

This makes every series set independent of LSSQueryResult so the result can be closed deterministically right after the series set is built.

Changes

  • LSSQueryResult.Close() — idempotent explicit free of the C buffers; cancels the finalizer, which remains a harmless fallback.
  • InstantSeries carries the series id inline next to its sample. Because this struct is shared with C++ by pointer (entrypoint::types::SampleWithGoLabels), the field is mirrored as series_id_ in the C++ struct so the ABI/stride still matches (both become 40 bytes; C++ only ever writes the Sample base).
  • StaleNaNSeries carries the series id inline (Go-only struct).
  • ChunkSeriesSet takes the series id straight from the recoded chunks and no longer references the result at all.
  • selectInstant / selectRange / ChunkQuerier.Select close the query results (via closeLSSQueryResults) once all in-flight C queries have completed.

Net effect: the query result buffers are released immediately instead of waiting for a lagging GC finalizer — no GC hinting needed.

Test plan

  • Rebuild C++ bridge: cd pp && make build-entrypoint (ARM devcontainer)
  • go test -tags stringlabels ./pp/go/storage/... ./pp/go/cppbridge/... — pass (instant tests would fail on any stride mismatch)
  • Double-GC use-after-free stress: go test -tags "stringlabels,testgcdouble" ./pp/go/storage/querier/... ./pp/go/cppbridge/... — pass
  • CI (clang lint / golang lint / tests)

Made with Cursor

@vporoshok vporoshok self-assigned this Jul 24, 2026
@vporoshok
vporoshok marked this pull request as draft July 24, 2026 05:25
@vporoshok vporoshok added this to the v0.8.6 milestone Jul 24, 2026
vporoshok and others added 3 commits July 24, 2026 09:31
The C-allocated series-id result buffer returned by Querier::query was kept
alive by the series sets and only freed lazily by a Go GC finalizer. Because
the real bytes live off the Go heap (jemalloc), the GC sees almost no pressure
and finalization lags, so thousands of over-allocated buffers (capacity = max
matcher cardinality) pile up — the dominant on-heap consumer in heap profiles.

Make every series set independent of LSSQueryResult so it can be closed right
after construction:
- Add idempotent LSSQueryResult.Close() (frees the C buffers, cancels the
  finalizer which stays a harmless fallback).
- InstantSeries carries the series id inline next to its sample; mirror the
  field in the C++ SampleWithGoLabels struct so the shared ABI still matches.
- StaleNaNSeries carries the series id inline.
- ChunkSeriesSet takes the series id straight from the recoded chunks and no
  longer references the result at all.
- Close the query results in selectInstant/selectRange and ChunkQuerier.Select
  once all in-flight C queries have completed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Instead of copying the series ids on the Go side after the query, the instant
querier now writes the label-set id into SampleWithGoLabels.series_id in the
same loop that fills the sample. store_series_id is a no-op for plain
encoder::Sample (used in C++ tests) via an if-constexpr guard, so the generic
querier is unaffected. NewInstantSeriesSet no longer needs the query result.

Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror the instant-query optimization for the stalenan path: the C++ data
storage query now writes both the first-sample timestamp and the series id
directly into a C-shared StaleNaNSeries slice, so the LSS query result buffer
is no longer referenced after construction and can be released immediately.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vporoshok
vporoshok force-pushed the fix/lss-query-result-explicit-close branch from dcfcff2 to f56334c Compare July 24, 2026 05:38
@vporoshok
vporoshok requested review from cherep58 and u-veles-a July 24, 2026 09:18
Comment thread pp/series_data/querier/instant_querier.h Outdated
@vporoshok
vporoshok marked this pull request as ready for review July 29, 2026 12:58
@vporoshok
vporoshok enabled auto-merge (squash) July 29, 2026 13:46
@vporoshok
vporoshok merged commit 8cf4bc2 into pp Jul 29, 2026
23 checks passed
@vporoshok
vporoshok deleted the fix/lss-query-result-explicit-close branch July 29, 2026 17:37
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.

2 participants