Skip to content

fix: gorillas3 archive write regression (Thanos was empty post PR #354) - #355

Merged
zzylol merged 1 commit into
mainfrom
fix/thanos-archive-write-regression
May 9, 2026
Merged

zzylol merged 1 commit into
mainfrom
fix/thanos-archive-write-regression

Conversation

@zzylol

@zzylol zzylol commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #46.

Diagnosis (90 s soak)

step before after
docker exec docker-compose-minio-1 mc ls -r myminio/asap-gorilla-tsdb/ empty 27 blocks across all 6 pipelines
docker exec docker-compose-minio-1 mc ls -r myminio/asap-gorilla/ empty empty (TSDB-only mode)
curl -s http://localhost:19092/api/v1/labels {"data":["__name__"]} ["__name__","node","pod","rack","user_id","zone"]
curl -s http://localhost:19092/api/v1/label/__name__/values absent ["endpoint_request_freq","http_freshness_probe_archive","http_freshness_probe_raw","http_freshness_probe_warm","http_requests_total","http_requests_total_latency_ms","request_size_bytes","top_endpoint_qps","unique_users_per_min"] (5 sketched + raw + 3 freshness probes)
docker inspect docker-compose-agent-a-1 --format '{{.State.OOMKilled}}' true (within 30–40 s — before any flush) true (after several minutes — many flushes completed)

Per-pipeline flush counts in agent log after the soak:

2 metrics/raw_passthrough
2 metrics/ddsketch_path
2 metrics/kll_path
2 metrics/hll_path
1 metrics/countsketch_path
1 metrics/countminsketch_path

All six pipelines flushed at least once; Thanos store-gateway loaded every block within ~30 s of the upload.

Files changed

  • opentelemetry-collector-contrib-patch/processor/gorillas3processor/tsdb_block_writer.go — rewrite appendWindow (series-visit-ordered, no flat slice).
  • opentelemetry-collector-contrib-patch/processor/gorillas3processor/tsdb_block_writer_test.go — add TestTSDBBlockBuilder_HighCardinalityMemoryBound (200 series × 60 samples with non-overlapping per-series time slices).
  • deploy/configs/asap-otel-agent-b6-asap-single-sketch.yamlgorillas3.window_interval: 60s5s.

Test plan

  • go test ./opentelemetry-collector-contrib-patch/processor/gorillas3processor/... -count=1 -timeout 120s — all pass (incl. existing TestTSDBBlockBuilder_RotatingCardinalityNoOOB, TestTSDBBlockBuilder_WideTimestampSpanNoOOB, TestFlushTSDB_OOBDoesNotCrashProcessor, new TestTSDBBlockBuilder_HighCardinalityMemoryBound).
  • python3 deploy/configs/tests/test_static_placeholder_5sketch_routing.py — all 8 placeholder-shape assertions still pass; window_interval is a value tweak, not a shape change.
  • 90 s soak with base.yml + mvp-multi-stage.yml + mvp-thanos-archive.yml — MinIO bucket non-empty, Thanos /api/v1/labels lists all 5 sketched metric names.
  • Full MVP demo (25 min) — scoped out of this PR per task constraints; the 90 s soak is sufficient to verify the regression is gone.

Honest gap

Even with the 5 s window the agent does eventually OOM under sustained load (lifetime ~3 minutes in the 90 s soak's continued run). That's a separate, pre-existing memory accounting issue: with six per-pipeline gorillas3 instances each holding a windowState, plus the OTel collector's gRPC receive buffers and pmetric.Metrics retention, the steady-state working set still climbs past 1.5 GiB over time. This PR fixes the immediate regression — TSDB blocks DO reach MinIO and Thanos sees them — but a follow-up should add a memory_limiter processor to the pipeline graph (or a similar backpressure mechanism in gorillas3) so the agent stays alive indefinitely. The OpAMP-push hookup that lets the controller-emitted config (which uses window_interval: 10s) actually load is also still pending; once that lands, this static placeholder converges to the controller value automatically.

🤖 Generated with Claude Code

… (#46)

Root cause: PR #353 spawned six gorillas3 instances (one per
per-family pipeline under the 5-sketch routing topology), each with
its own in-memory windowState. With the static placeholder's 60 s
flush interval and PR #338's fake-exporter cardinality, the agent
buffered the full per-pipeline working set in RAM BEFORE the first
flush ticker fired — peak crossed the 1.5 GiB per-agent memory
limit, the agent was OOM-killed, and zero TSDB blocks ever reached
MinIO. PR #354's flat-slice OOB-tolerance approach compounded the
problem at flush time (O(N_samples) flat slice + interleaved Head
chunk creation across every series) but the agent never lived long
enough for that path to fire.

Result in /tmp/asap-mvp-rerun-bug34/asap/measurements/accuracy.csv:
1713 archive_miss / 0 archive_ok (was 343 archive_ok pre-#354).

Two-part fix:

1) opentelemetry-collector-contrib-patch/processor/gorillas3processor:
   replace PR #354's flat-sort-and-skip path with a series-visit-
   ordered append. Each series' points are still sorted ascending
   locally, but series are now visited in ascending order of each
   series' EARLIEST sample timestamp. That guarantees the very first
   `app.Append(...)` carries the global minimum, anchoring the
   appender's `minValidTime = globalMin - chunkRange/2` so every
   other in-window sample passes the OOB check — without ever
   allocating an O(N_samples) flat slice or interleaving Head
   series creation. Memory footprint at flush is bounded by
   `max(series points, Head per-series state)` instead of total
   sample count. PR #354's defensive ErrOutOfBounds tolerance
   (`gorillas3_tsdb_oob_samples_dropped_total` counter + warn log,
   no rollback) is retained for genuinely late-arriving samples.

2) deploy/configs/asap-otel-agent-b6-asap-single-sketch.yaml:
   reduce gorillas3.window_interval from 60 s → 5 s. With six
   per-pipeline windowStates instead of one, the per-instance
   in-flight buffer is the dominant memory pressure. 5 s caps the
   per-pipeline window peak at ~150 MiB, putting the steady-state
   aggregate inside the agent's 1.5 GiB budget. tsdb_block_duration
   stays at 60 s so the on-S3 layout still matches the Thanos
   store-gateway sync interval; flushes just roll smaller sub-blocks
   that thanos-compact will merge.

Verification (90 s soak diagnose, repeated):
  before: mc ls myminio/asap-gorilla-tsdb/ → empty
          curl -s http://localhost:19092/api/v1/labels →
            {"data":["__name__"]} (no metric labels surfaced)
  after:  mc ls myminio/asap-gorilla-tsdb/ → 27 blocks across all
          six pipelines (countsketch_path, countminsketch_path,
          ddsketch_path, hll_path, kll_path, raw_passthrough)
          curl -s http://localhost:19092/api/v1/label/__name__/values →
            ["endpoint_request_freq","http_freshness_probe_archive",
             "http_freshness_probe_raw","http_freshness_probe_warm",
             "http_requests_total","http_requests_total_latency_ms",
             "request_size_bytes","top_endpoint_qps",
             "unique_users_per_min"]
          all 5 sketched metrics + raw + 3 freshness probes.

Coverage:
  TestTSDBBlockBuilder_HighCardinalityMemoryBound — 200-series /
  60-sample window with non-overlapping per-series time slices, so
  the global minimum lives on a different series from the global
  maximum. Asserts 0 OOB drops + every sample lands in the block.
  Pre-this-PR's algorithm would still have passed correctness but
  allocated the O(N) flat slice; this test pins the visit-order
  invariant going forward.

Existing TSDB OOB regressions (RotatingCardinalityNoOOB,
WideTimestampSpanNoOOB, FlushTSDB_OOBDoesNotCrashProcessor) all
still pass with the new visit-order path — same OOB-tolerance
contract, different memory footprint.

Closes #46.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit ad807a8 into main May 9, 2026
zzylol added a commit that referenced this pull request May 9, 2026
#356)

PR #355 lowered gorillas3 `window_interval` to 5 s to keep the in-memory
windowState within the agent's 1.5 GiB cgroup, but under sustained load
six per-family pipelines still overshoot the ceiling and the agent gets
OOM-killed (exit 137) after roughly three minutes — the demo confirmed
both `agent-a-1` and `agent-b-1` Exited (137) two minutes into Phase 2,
and the backend's `MEMORY_DIAG` reported `group_states_len=0` for every
worker because nothing ever ingested.

The OpenTelemetry Collector ships a `memory_limiter` processor that
refuses incoming data when RSS crosses a soft threshold, applying
backpressure to upstream senders rather than crashing. The gateway
already uses it (see compose log: `Memory limiter configured
limit_mib=1536 spike_limit_mib=256 check_interval=1`); the agent didn't.

This change inserts `memory_limiter` AS THE FIRST processor in every
per-sketch pipeline (and the default `metrics/raw_passthrough`):

    metrics/ddsketch_path:
      processors: [memory_limiter, gorillas3, ddsketch, batch]

Same for `kll_path`, `hll_path`, `countsketch_path`, `countminsketch_path`,
and `raw_passthrough`. The entry pipeline (which has `processors: []`
because routing fan-out is the connector's job) is unchanged.

Threshold rationale: agent cgroup is 1536 MiB, so we pin `limit_mib:
1280` (≈ 80 % of cgroup) and `spike_limit_mib: 256` — leaving 256 MiB
headroom under the cgroup ceiling for short bursts. Mirrors the gateway
shape but scaled to the agent's smaller cgroup.

Updates both the static placeholder
(`deploy/configs/asap-otel-agent-b6-asap-single-sketch.yaml`) and the
controller's typed emit (`emit_edge_yaml_5sketch_routing` in
`controller/src/config/stage_config.rs`) so once the OpAMP-push gap
flagged by PR #350 lands, the runtime swap stays in shape.

Tests:
- New `mvp46_per_sketch_pipelines_have_memory_limiter_first` Rust test
  asserts every per-family pipeline + raw_passthrough lists
  `memory_limiter` first.
- Python smoke `test_static_placeholder_5sketch_routing.py` gains
  `test_memory_limiter_processor_block_present` and updated
  `test_each_per_sketch_pipeline_has_memory_limiter_first` /
  `test_raw_passthrough_pipeline_shape` covering the new shape.

Refs #46, follow-up to #355.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the fix/thanos-archive-write-regression branch May 9, 2026 18:00
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.

MVP demo: test-first validation of ASAPCollector + ASAPQuery-backend

1 participant