Skip to content

fix: stop Prometheus rejecting the ingest counters, cut 0.3.1 - #28

Merged
simonvanlierde merged 8 commits into
mainfrom
fix/ingest-counter-rejections
Sep 7, 2026
Merged

fix: stop Prometheus rejecting the ingest counters, cut 0.3.1#28
simonvanlierde merged 8 commits into
mainfrom
fix/ingest-counter-rejections

Conversation

@simonvanlierde

@simonvanlierde simonvanlierde commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Closes the three loose ends in section 7 of the post-merge list. Cuts 0.3.1.

Ingest counters rejected every 5–6 minutes

Three faults in metrics/count, each found by dumping the rejected payload through a temporary debug exporter:

  1. batch after deltatocumulative reordered timestamps, ~1/min. Removed.
  2. The count connector emits one sample per incoming batch, so 50 log batches became 50 duplicate samples of one series. interval: 30s collapses them.
  3. The counters carried the source telemetry's timestamp, up to 11 hours old. Now stamped at ingest, after interval so the restamp cannot recreate duplicates.

telemetry_logs_total now reports 8 senders where it reported 3; the other five were rejected wholesale. Both coverage alerts read that counter. No application telemetry was lost, which the new otlp_http/prometheus_count exporter proved by separating the two failure counts. deltatocumulative is capped at 5000 streams.

Also

  • ProjectsUncovered exempts demo/demo; bootstrapping it would leave ProjectTelemetrySilent firing forever after teardown.
  • bootstrap.sh refuses malformed # COVERS: markers.
  • PrometheusCardinalityHigh lowered to 30k; new PrometheusCardinalitySpike on 5,000 series in 30m.
  • Runbook: Bearer is the only scheme the collector accepts.

Verification

Applied to the running hub for 16 minutes: zero rejections, otelcol_exporter_send_failed_metric_points absent, all 12 rules healthy, just check passes.

resets(telemetry_logs_total[6h])                # 0
count(group by (job) (telemetry_logs_total))    # 8, was 3

Tag v0.3.1 after merge.

Three faults stacked behind one HTTP 400 every five or six minutes, found
by dumping the rejected payload through a debug exporter on metrics/count.

- batch sat after deltatocumulative and handed it datapoints whose
  timestamps had regressed, ~1/min. Removing it took delta.ErrOutOfOrder
  from 55 per collector hour to none.
- The count connector emits one ResourceMetrics per incoming batch and
  merges none of them, so a spoke shipping 50 log batches at once made 50
  samples for one series microseconds apart. interval collapses a stream
  to one sample per 30s scrape.
- The counters inherited the source telemetry's own timestamp, so a log
  backlog minted counter samples up to 11 hours old, past the 30m
  out_of_order_time_window. They are stamped at ingest now, which is what
  an ingest counter means. It has to run after interval, or several
  datapoints of one stream would restamp into duplicate samples.
- deltatocumulative tracked an unbounded number of streams. The counters
  carry the sender's service.instance.id, so every service restart added
  one. Capped at 5000.
- The counters export through their own otlp_http/prometheus_count, so
  send_failed_metric_points is attributable by pipeline rather than
  pooling counter drops with a spoke's application metrics.

telemetry_logs_total now reports all eight senders, where five had never
reached Prometheus at all, and none of them reset. That counter is what
ProjectTelemetrySilent and ProjectsUncovered read.
`just demo` runs a throwaway stack under its own compose project and is
torn down again, so the rule fires against the demo's own hub. Bootstrapping
the pair instead would leave a ProjectTelemetrySilent rule firing forever
once the demo is removed.

- Exempt as a pair, not by project name, so a real project called demo in a
  real environment is still caught.
- Re-rendering through bootstrap.sh also picked up a coverage.yaml that had
  drifted from the template's `unknown` wording.
The project and env arguments are validated, but the pairs read back from
the rendered files' '# COVERS:' markers were not, and they land in a sed
replacement and a PromQL label value. Fail the run instead of rendering a
rule that silently never matches.
The ceiling was 67 spokes away at ~1,400 series each, and 100k would have put
Prometheus near its 2g mem_limit before the warning arrived. A single static
threshold also cannot separate the two things it was being asked to catch:
onboarding is slow and planned, an exploding label is fast and is the failure
that hurts.

- PrometheusCardinalityHigh drops to 30k, ~4x the ~7k baseline and ~18 spokes
  of room, firing at roughly a quarter of Prometheus's memory limit.
- New PrometheusCardinalitySpike on 5,000 new series in 30 minutes, which
  needs no re-tuning as the fleet grows. Calibrated on the hub: steady state
  moves under 100 series/hour, one spoke onboarding adds ~1,400. Measured over
  30m so the window fits inside the shared relativeTimeRange, and a head-block
  truncation only ever moves the delta negative, so it cannot false-fire.
- RUNBOOK quotes both numbers.
- extract valid_pair() so argv and on-disk COVERS markers share one check
- drop file_storage from the count exporter's queue; the counters resume
  after a restart, so in-flight samples are not worth a queue on disk
- note that the max_streams heap figure predates the interval processor
- condense the changelog and date the release
@simonvanlierde simonvanlierde changed the title fix: stop Prometheus rejecting the ingest counters, exempt the demo fix: stop Prometheus rejecting the ingest counters, cut 0.3.1 Sep 7, 2026
@simonvanlierde
simonvanlierde merged commit 88b0894 into main Sep 7, 2026
4 checks passed
@simonvanlierde
simonvanlierde deleted the fix/ingest-counter-rejections branch September 7, 2026 04:18
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