Skip to content

Choosing a store

Ten sinks, and running more than one is the normal arrangement. Every one of them pushes: the tool is meant to run wherever it is convenient and reach its stores from there, not to be scraped. The Prometheus exporter is the one exception, and it exists because Prometheus insists.

StoreKeepsGood forConfig
InfluxDBthe dated history“how fast were we merging in July”url, token, org, bucket
PostgreSQLthe dated history, as SQL you pipe into psqla Grafana user with a Postgres and no InfluxDBdialect, path
Graphitethe dated historya Graphite that is already thereaddr, prefix
Elasticsearchthe dated history, as documentssearch across everything collectedurl, prefix, api_key
Prometheusthe current valuealerting, and a number on a walllisten, path
OpenTelemetryeither, depending on the backendan existing collector pipelineendpoint, raw
Lokithe events, as log lines“what happened, in order”url, labels, max_age
Telegrafwhatever its outputs keepreaching anything Telegraf can reachurl
File and stdoutline protocol or JSONa shipper you already run, and a bufferpath, format

There is no sink for a specific hosted vendor, and that is deliberate. A managed backend is reached through one of the two sinks that exist to route onward: Telegraf, whose own outputs cover Datadog, New Relic, Wavefront, Azure Monitor and a hundred more, or OpenTelemetry, which most of them now accept directly. A sink per vendor is a key to rotate, an API to track and a test that needs a paid account, for a hop those two already make.

A point carries the date the thing happened. A star is dated when it was given, a workflow run when it finished, a traffic day at that day’s own date.

InfluxDB keys a point by measurement, tag set and timestamp, so writing the same fourteen-day traffic window every six hours converges on the right answer rather than accumulating copies. That is what makes the whole backfill design work, and it is why InfluxDB is the sink that keeps history.

Prometheus cannot do that. It stamps a sample at scrape time and rejects anything meaningfully older: measured against Prometheus 3.14 with the OTLP receiver enabled and a thirty-minute out-of-order window, a sample dated two days back comes back as HTTP 400. So the exporter reduces the per-item rows to current values before serving them.

The full argument, and what the reduction does to each measurement, is on dating a point.

A sweep offers the same history every time: the fourteen day traffic window, every open pull request, the contribution calendar. Writing it all again is harmless to what the store holds, since a row is keyed by series and timestamp and simply overwrites, and it is how a collector that was down for a day repairs itself.

It is not harmless to the store’s files. InfluxDB 3 Core writes one Parquet file per partition per write request and never compacts them, and it refuses any query that would open more than its file limit. Measured before the fix below and with the limit then at ten thousand: gh_notification held barely more than one row per Parquet file, and a query over fourteen days came back with “Query would scan 10000 Parquet files, exceeding the file limit”.

Asking for a coarser interval does not help, because the limit counts the files the planner opens, before any aggregation. So the tool keeps a small ledger of what it has already written and sends only the points whose values have moved:

sinks:
dedupe_file: /var/lib/ghchronicle/state-written.bin # default: beside state_file
dedupe_horizon: 720h # forget a point nothing offers any more
influxdb:
dedupe: true # the default, here and for telegraf, graphite, sql and elasticsearch

The ledger stores two 64 bit hashes and a day per point, so a large account costs a few megabytes. It is keyed by sink, so a store that was unreachable still receives everything on its next write. Losing it, or setting dedupe_file: off, costs one sweep of rewriting and nothing else, which is exactly what a store that has been wiped and needs filling again wants. It is the second file worth putting on a persistent path, next to the state file.

A run that ends when its sweep does never opens the ledger at all. -once, -backfill and a card render write once and exit, so there is nothing to save and nothing to prune, and each of them offers the whole history again. That is what a backfill is for; it is also why a scheduled -once job, which is the shape the Action runs in, writes every point every time. Where that matters, run the loop instead.

The sweep log says what this saved:

level=INFO msg=written sink=influxdb family=events points=0 unchanged=300

If the files have already accumulated, the writer fix stops the growth but does not remove them: raise --query-file-limit on the server, rewrite the affected tables, or move to InfluxDB 3 Enterprise, which compacts on its own and is free for home use.

Normal, and cheap: the collection happens once and the points are handed to every configured sink. The usual arrangement is one history store plus one of the current-value ones.

sinks:
influxdb:
url: http://localhost:8181
token: ${INFLUX_TOKEN}
org: default
bucket: github
prometheus:
listen: 127.0.0.1:9605
path: /metrics

A sink that fails is logged and the sweep continues; a database being down does not stop collection, and with the file sink configured the data is still on disk when it comes back. Two failures are reported specially rather than as errors, because they are partial successes:

  • rejected lines, where everything parseable was written and the refused lines are logged individually with the store’s own reason.
  • dropped entries, which is Loki’s age horizon leaving out what its out-of-order window would have refused, rather than losing the whole push.