fix(data_plane): bound warm SketchStore memory with retention horizon - #327
Merged
Merged
Conversation
The warm sketch store grew unbounded (~0.74 GiB/min) under steady OTLP ingest, freezing a 251 GiB node. Root cause: SidStoreData is always built with epoch_capacity=None, so maybe_rotate_epoch returns early and nothing ever seals; the persistence flusher only evicts SEALED epochs, so current_epoch accumulated one ~19 KB pane per 30s tumbling window per sid forever. Memory was O(active_series x total_elapsed_time). Add a time-based retention horizon on SidStoreData: on each insert, evict windows whose END is older than newest_end - horizon from current_epoch (and any sealed_epochs), making memory O(active_series x horizon). Configurable via ASAP_SKETCH_RETENTION_MS (0 disables), default 2h. Does not regress the #323-#326 sketch read path: the horizon (2h) comfortably exceeds the ~30m max range-query window plus the delta-stitching carry-in's Full-base reach, and eviction keys on window-END so a straddling pane survives until fully behind the horizon. Recent unsealed windows stay queryable via the overlap scan (no force-seal). Regression tests prove (a) long-elapsed ingest keeps per-sid window count bounded and (b) a 30m range query within the horizon still resolves with its Full carry-in base intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
zzylol
added a commit
that referenced
this pull request
May 25, 2026
Compose hot (current_epoch) -> sealed (in-mem, pending flush) -> disk parts into a single tiered store so warm-sketch memory is bounded by flush-then-evict rather than #327's age-based drop. Reads union all three tiers across the requested range. - Sealing now fires under persistence: SidStoreData gains a seal_window_count cadence (default 20 windows ~= 10 min of 30s panes) so current_epoch rotates into sealed_epochs for the flusher to persist. max_epochs drop is disabled under persistence (the flusher owns sealed-epoch lifecycle). In-memory-only deploys keep #327. - query_range/union_disk_parts_into consult PartCache+Manifest for the evicted portion of the range, rebuilding the full label key->value map from the sid's group_by_keys and preserving the #323-#326 read contract (half-open overlap + delta-stitching carry-in) across the in-mem/on-disk boundary -- incl. a carry-in Full base that now lives on disk. Part format round-trips SketchEncoding via a repurposed v1 pad byte (legacy 0 decodes as Full). - enforce_retention is a no-op under persistence so retention never drops un-flushed sealed/current data; the disk-tier TTL bounds the durable copy. In-memory-only path is unchanged. - start_persistence installs a read handle + seal cadence and recovers the manifest+parts so a restart immediately serves recovered data. - New CLI flag --persistence-seal-window-count plumbs the cadence. Tests: seal-fires, flush+evict bounds memory, query-from-disk incl. disk carry-in base, restart recovery, and persistence-disabled non-regression. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause (the leak)
asap-data-planeRSS grew ~0.74 GiB/min unbounded under steady OTLP sketch ingest, freezing a 251 GiB node. The[MEMORY_DIAG]showed a flat ~20218 sid count yet climbing RSS and0.00 KB sealed— so the growth is per-instance, incurrent_epoch.SketchStore::append_sample/append_precomputealways build the per-sid store viaSidStoreData::new(), which setsepoch_capacity: None(index/epoch_columnar.rs).maybe_rotate_epoch()therefore returns early on the very first match arm — nothing ever seals (hence0.00 KB sealed).persistence/flusher.rs) andapprox_memory_bytesonly ever operate onsealed_epochs(list_sealed_epochs->evict_sealed_epoch). With nothing sealed, the eviction/retention path never seescurrent_epochat all.lifecycle/eviction.rs(SchemaEvictionService) only drops whole Expired sids (schema retirement). In steady state sids stay Active forever, so it never fires.Net:
current_epoch's columnar Vecs accumulate one ~19 KB pane per 30s tumbling window per sid forever — memory wasO(active_series × total_elapsed_time). Two unbounded sources: (1) nothing seals, (2) even sealed epochs only evict by count (max_epochs), never by age forcurrent_epoch.Fix
A time-based retention horizon on
SidStoreData:newest_end − horizonfromcurrent_epoch(and drop fully-behindsealed_epochs). Memory becomesO(active_series × horizon).ASAP_SKETCH_RETENTION_MS(0disables), resolved once viaOnceLockoff the hot path. Default 2h (DEFAULT_SKETCH_RETENTION_MS).Why it can't regress #323–#326
The 2h horizon comfortably exceeds the ~30m max range-query window plus the delta-stitching carry-in's Full-base reach. Eviction keys on window-END, so a pane straddling the cutoff survives until fully behind the horizon — the overlap scan (
range_query_overlap_into) and carry-in (collect_ending_at_or_before) still see everything within the horizon. Older data lives in the cold/raw tier.Tests
cargo test -p data_plane— 739 lib tests pass (0 fail). New regression tests:evict_window_ends_before_*— drops old / keeps straddling+recent, recomputes bounds, no-op fast path.retention_bounds_window_count_over_long_elapsed_time— 4h of 30s panes stays bounded to ~horizon/window (not 480).retention_disabled_keeps_full_history—Nonehorizon = legacy behavior.retention_keeps_windows_within_horizon_queryable+ e2eretention_bounds_memory_yet_keeps_recent_windows_queryable— 30m range query within horizon still resolves with its Full carry-in base intact.cargo clippy -p data_plane— no new errors/warnings in the changed files.🤖 Generated with Claude Code