Tools Used in SRE · InfluxDB

InfluxDB

InfluxDB is a purpose-built time-series database: instead of Prometheus's model of pulling one value per label-combination on a scrape interval, InfluxDB is written to — pushed a compact text format called line protocol, from anything that wants to send it a point, whenever that thing wants to send it — and stores the result in a storage engine designed from the ground up for exactly one workload: huge volumes of timestamped, mostly-append-only writes, queried back later as ranges and aggregates. Most SRE teams meet it at a specific moment, not as a first choice. Prometheus is doing fine for alerting and short-term dashboards, but someone needs a year of capacity-trend data, or a fleet of ten thousand sensors that can't be scraped because they can't be discovered or held open on a pull interval — and the answer isn't "make Prometheus retain more," because Prometheus's local TSDB was never designed as a long-term store and its label-cardinality ceiling doesn't move just because you want it to. This page covers InfluxDB as that purpose-built alternative: how it actually stores a write, the query languages it speaks, and the retention-policy mechanics that replace Prometheus's single --storage.tsdb.retention.time flag with something considerably more capable.

☺ Explain it like I'm 10

Imagine two ways to track a busy warehouse's temperature. Method one: a security guard walks past the same ten thermometers every fifteen seconds, writes each number in a notebook, and only keeps two weeks of notebook before starting a fresh one — that's Prometheus, and it's great as long as you only ever need those two weeks and only ever care about those ten thermometers. Method two: every thermometer, sensor, and delivery scanner in the building — thousands of them — sends its own reading straight to a central warehouse computer whenever it feels like it, in one simple line of text, and that computer is specifically built to hold years of those readings and still answer "what was the average temperature every five minutes last March" in under a second. InfluxDB is method two: things push data to it instead of being walked past, and it's built to hold onto far more of it, for far longer, than a notebook made for two weeks was ever meant to.

🐘Your host for this topic: Ellie the Elephant — Ellie holds every metric, log, and trace this course produces and never forgets where she put one. A purpose-built time-series database is Ellie's own job description, turned into software.

What InfluxDB is, and when Prometheus stops being enough

☺ Like you're 10: Prometheus is superb up close and for a few weeks; InfluxDB is built to be pushed to by almost anything and to remember it for a long time.

InfluxDB comes from InfluxData, first released in 2013 and historically bundled with three companion tools into what was called the TICK stack — Telegraf for collection, InfluxDB for storage, Chronograf for visualization, Kapacitor for alerting and stream processing. Most shops today keep Telegraf as the collector but swap Chronograf and Kapacitor for Grafana and their existing alerting pipeline, so in practice "the TICK stack" usually just means Telegraf writing into InfluxDB with Grafana reading it back out. The reason it shows up in an SRE toolchain at all, next to Prometheus, is that the two were designed around opposite answers to the same question: who decides when a data point gets collected? Prometheus decides — it dials out to targets on a scrape_interval, which is why it's dominant for Kubernetes-native infrastructure metrics where targets are discoverable and few enough in number to enumerate. InfluxDB has no opinion about when you write to it; you dial in, whenever you have a point, from wherever you are. That makes it the natural home for anything that can't be scraped — a fleet of IoT sensors behind NAT, a mobile app's client-side telemetry, financial tick data arriving in an unpredictable stream — and for anyone who needs retention measured in months or years rather than the weeks Prometheus's local storage is tuned for.

◆ Key idea

Push vs. pull isn't a style preference — it's the axis both databases were architected around, and it explains most of the rest of this page. Prometheus's pull model gives you built-in target health (a target that stops responding is instantly visible as "down") at the cost of needing something scrapeable. InfluxDB's push model accepts a write from anything with an HTTP client at the cost of needing the writer itself to notice if it stops sending. The two show up together in real infrastructure at least as often as they show up in competition: Prometheus for the k8s-native alerting surface, InfluxDB as the long-retention or high-volume sink underneath it.

Architecture: the write path from line protocol to disk

☺ Like you're 10: A write is one line of plain text — what it's measuring, which tags describe it, the actual numbers, and when it happened.

Every point InfluxDB stores arrives as one line of line protocol, a compact text format with four parts: a measurement name, an optional comma-separated set of tags, a required comma-separated set of fields, and an optional timestamp.

weather,location=us-midwest,scenario=sunny temperature=82,humidity=54 1465839830100400200
^--------------------------- ^------------------------- ^----------------
   measurement + tag set              field set                 timestamp (ns)

weather is the measurement — roughly analogous to a table. location and scenario are tags: indexed, string-only key/value pairs used for filtering and grouping. temperature and humidity are fields: the actual values, typed (float, integer, string, or boolean), and — this is the detail that trips people up coming from Prometheus — not indexed. You can filter or group by a tag cheaply; filtering by a field's value means scanning it. The timestamp, when supplied, is nanosecond precision by default over the write API (configurable to coarser precision). Omit it and InfluxDB stamps the point with server-receipt time.

Telegraf / apps / sensors many independent writers, on their own clock Write API HTTP POST line protocol body WAL → TSM engine write-ahead log, then compacted time-structured merge-tree files sharded per retention policy / bucket Query engines InfluxQL · Flux · SQL (3.x) all read the same storage Consumers Grafana · Chronograf influx CLI · Tasks Contrast — Prometheus: pulls targets on a fixed scrape_interval, no push write API by default, local TSDB tuned for a short retention window (long-term storage is a separate remote-write target)

Two consequences fall out of that architecture directly. First, a single point can carry multiple fields — temperature and humidity together, in one line — where Prometheus would need two entirely separate metric names, each its own single-valued time series. Second, the write-ahead log means a crash between "accepted the write" and "compacted to a TSM file" loses nothing — WAL entries replay on restart — but it also means write throughput and query freshness both depend on how aggressively the background compaction process keeps up, which is the first thing to check when writes start backing up under load.

One database, three eras: 1.x, 2.x, and the 3.0/IOx rewrite

☺ Like you're 10: InfluxDB is really three different products wearing the same name — know which one a job posting, a doc page, or an existing cluster actually means before you touch it.

1.x is the version most existing production deployments still run: databases containing retention policies (RPs), InfluxQL as the only query language, a free single-node OSS binary with clustering paywalled behind Enterprise, and Continuous Queries for scheduled downsampling. 2.x restructured almost everything: databases and retention policies fused into a single object called a bucket, organizations became the multi-tenancy boundary, token-based auth replaced username/password, a new functional query language called Flux became primary (InfluxQL kept alive through a compatibility layer for existing dashboards), and Tasks — scheduled Flux scripts — absorbed the job Continuous Queries and the separate Kapacitor daemon used to do. 3.0, built on a storage engine InfluxData calls IOx, is a further rewrite: Apache Arrow and DataFusion for in-memory columnar processing, Parquet files on object storage (S3-compatible) for the actual data, and a pivot toward SQL as the primary query surface, with InfluxQL kept for compatibility and Flux positioned as legacy — InfluxData has been moving away from Flux in the 3.x line in favor of native SQL. The 3.x product line has also split into differently-licensed tiers (commonly named "Core" for a free single-node build and "Enterprise" or a managed cloud offering for clustering and HA); the exact naming, licensing terms, and how far the Flux sunset has actually gone have shifted release to release, so verify current specifics against InfluxData's own documentation before you plan a migration around any one of them.

EraStorage groupingPrimary languageDownsampling mechanism
1.xdatabase + retention policyInfluxQLContinuous Queries
2.xbucket (org-scoped)Flux (InfluxQL via compat layer)Tasks (scheduled Flux)
3.0 / IOxdatabase, on Arrow/Parquet/object storageSQL (InfluxQL via compat layer)scheduled queries / Tasks, product still evolving

The data model: measurements, tags, fields — and the cardinality wall

☺ Like you're 10: A "series" is one exact combination of tag values — the more combinations your tags can form, the more separate series you're asking the database to remember, and that number has a ceiling.

A series in InfluxDB is the combination of a measurement name and one exact tag set — weather,location=us-midwest,scenario=sunny is a different series from weather,location=us-east,scenario=sunny, and a different series again from itself with a third tag added. Series cardinality is the count of distinct series in the database, and it is InfluxDB's single most important operational number, in exactly the same way that active series count is Prometheus's. The mechanism differs — InfluxDB indexes tags to make that series set fast to enumerate and filter, historically an in-memory index that InfluxDB 1.5 moved to a disk-backed structure called TSI (Time Series Index) specifically to survive higher cardinality than memory alone could hold — but the failure mode rhymes with Prometheus's almost exactly: a tag whose value is effectively unbounded (a raw user ID, a request ID, a full URL with a query string) multiplies your series count by the number of distinct values that tag has ever taken, and a database that was healthy at ten thousand series can start missing SLOs on ingest and query latency well before it hits any hard memory wall.

⚠ Cardinality bombs look like fields — until they're tags

The instinct to make everything filterable pushes people to tag anything they might one day want to GROUP BY. Don't tag a request ID, a raw UUID, a full stack trace, or any field whose distinct-value count grows with traffic rather than with your fleet size. If you need to search on that value occasionally, keep it as a field and accept a slower scan for the rare query — it's a better trade than a cardinality explosion that degrades every write and every query, all the time, for everyone sharing that instance.

One more distinction is worth internalizing precisely because Prometheus doesn't have it: a field's type is fixed at first write for a given series. Write value=1 (an integer, note the trailing i line protocol requires for integers) to a series and then write value=1.5 (a float) to that same series later, and InfluxDB rejects the second write with a field type conflict rather than silently coercing it — a schema constraint that a naive line-protocol producer will hit the first time someone changes an emitter's numeric type.

Two query languages, going on three: InfluxQL, Flux, and SQL

☺ Like you're 10: Ask InfluxDB the same question three different ways depending on which version you're talking to — a SQL-flavored one, a pipe-chain one, and now, plain SQL.

InfluxQL is deliberately SQL-shaped so it reads familiarly, with one real limitation worth knowing before you reach for it: a single InfluxQL query can only select from one measurement — there's no join across measurements the way a relational database would let you correlate two tables.

SELECT mean("value") FROM "cpu_load"
WHERE time > now() - 1h
GROUP BY time(5m), "host"

Flux, introduced with 2.x, is a functional, pipe-forward language built to fix that: it can join across buckets, call out to other data sources (SQL databases, CSV, even other InfluxDB instances), and reads as a chain of transformations rather than a single declarative statement.

from(bucket: "telegraf")
  |> range(start: -1h)
  |> filter(fn: (r) => r._measurement == "cpu_load")
  |> filter(fn: (r) => r._field == "value")
  |> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
  |> yield(name: "mean")

Flux's power came at a real cost: it's a genuinely new language, with its own learning curve, and a large share of InfluxDB's user base never moved past InfluxQL habits. That reception is a documented part of why InfluxData pivoted 3.x toward plain SQL instead, running on the Arrow/DataFusion engine, which supports the functions you'd expect from a modern analytical SQL dialect:

SELECT date_bin('5 minutes', time) AS window, avg(usage_idle)
FROM cpu
WHERE time > now() - interval '1 hour'
GROUP BY window
ORDER BY window

Treat that last block as illustrative of the direction rather than a guaranteed-current reference — the exact function surface on 3.x has been actively evolving, so check InfluxData's current SQL documentation before depending on a specific function name.

Retention policies, shards, and downsampling

☺ Like you're 10: Data gets stored in time-boxed chunks, old chunks get dropped whole once they age out, and a scheduled job quietly saves a lower-resolution summary before that happens.

In 1.x, a retention policy (RP) is an explicit object: a duration, a replication factor, and a shard group duration, attached to a database. Every new database gets a default RP named autogen with infinite retention unless you say otherwise.

CREATE DATABASE mydb
CREATE RETENTION POLICY "one_year" ON "mydb" DURATION 52w REPLICATION 1 DEFAULT

Data physically lives in shards — one shard per RP per time range — and the shard group duration controls how wide each time range is: short RPs default to short shard groups (as fine as an hour) so an expiring window can be dropped in a small, cheap slice, while long RPs default to week-long shard groups so you're not managing thousands of tiny files. Expiry itself is just: drop the shards whose time range has fully aged past the RP's duration — no row-by-row deletion, no compaction pass required, which is why RP-based expiry is cheap at any scale.

2.x replaces the RP with a bucket — a single object combining what used to be a database and a retention policy — and replaces Continuous Queries with Tasks: scheduled Flux scripts, each with its own every interval, that read from one bucket and write into another. The standard downsampling pattern is a cascade of buckets at increasing retention and decreasing resolution:

option task = {name: "downsample-cpu-5m", every: 30m}

from(bucket: "telegraf")               // 7-day raw retention
  |> range(start: -task.every)
  |> filter(fn: (r) => r._measurement == "cpu_load")
  |> aggregateWindow(every: 5m, fn: mean)
  |> to(bucket: "telegraf_5m", org: "myorg")   // 90-day retention

A second Task chains off telegraf_5m into an hourly-resolution, multi-year bucket the same way — raw data answers "what happened five minutes ago," the 5-minute bucket answers "what happened last week," and the hourly bucket answers "what did last March look like," each at a storage cost proportional to how long you actually need to keep it. That cascading-bucket pattern is the direct mechanical answer to "we need a year of capacity-trend data" that Prometheus's flat, single-resolution local retention has no equivalent for; see capacity planning & performance for what that year of history actually gets used for once you have it.

Day-to-day: writing, querying, and Telegraf

☺ Like you're 10: Six things cover most of it: write a point, query it back, tell Telegraf where to send readings, and check what's using space.

# 1.x write — HTTP API, line protocol in the body
$ curl -i -XPOST 'http://localhost:8086/write?db=mydb' \
    --data-binary 'cpu_load,host=server01,region=uswest value=0.64 1434055562000000000'

# 2.x write — org/bucket instead of db, token auth instead of user/pass
$ curl --request POST "http://localhost:8086/api/v2/write?org=myorg&bucket=telegraf&precision=ns" \
    --header "Authorization: Token $INFLUX_TOKEN" \
    --data-raw "cpu_load,host=server01,region=uswest value=0.64"

# influx CLI (2.x/3.x) — the equivalent of kubectl for this database
$ influx bucket create -n telegraf_5m -o myorg -r 2160h   # 90-day retention, in hours
$ influx bucket list -o myorg
$ influx query 'from(bucket:"telegraf") |> range(start:-5m)'
$ influx task create -f downsample-cpu-5m.flux            # register a scheduled Task

Most InfluxDB writes in practice don't come from hand-rolled curl — they come from Telegraf, InfluxData's plugin-based collection agent, which is to InfluxDB roughly what node_exporter plus a scrape config is to Prometheus, except Telegraf pushes:

# telegraf.conf
[[inputs.cpu]]
  percpu = true
  totalcpu = true

[[inputs.disk]]
  ignore_fs = ["tmpfs", "devtmpfs"]

[[outputs.influxdb_v2]]
  urls = ["http://localhost:8086"]
  token = "$INFLUX_TOKEN"
  organization = "myorg"
  bucket = "telegraf"

Worth knowing for an environment that's standardized on Prometheus already: 2.x InfluxDB can also accept Prometheus remote_write directly as an ingestion path, which is how some teams introduce InfluxDB as a long-retention sink underneath an existing Prometheus without touching a single scrape config — Prometheus keeps scraping and alerting on its normal short local window, and a remote_write rule fans the same samples into InfluxDB for the retention Prometheus was never going to give you.

Gotchas and failure modes

☺ Like you're 10: The line format is unforgiving about types and timestamps, and writing the exact same timestamp twice doesn't add a second point — it silently replaces the first.

Duplicate-timestamp overwrite, not append. If two writes land on the same series (same measurement, same tag set) with the identical timestamp, the second write wins — it overwrites the first field-for-field, silently. This is by design (it's how idempotent re-sends and backfills work cleanly) but it's a footgun for any pipeline that batches points at second-level precision when two real events happened in the same second: without a higher-precision timestamp or a disambiguating tag, one of them simply vanishes.

Field type conflicts. A field's type is fixed by its first write, as covered above. An emitter that switches a metric from integer to float — or a Telegraf plugin upgrade that changes a field's type — produces a stream of rejected writes until the series is dropped and recreated, which is exactly the kind of failure that shows up as "some data is just missing" rather than an obvious error in a dashboard.

Cardinality growth is quiet until it isn't. Nothing stops a bad tag choice from shipping — the first few thousand series are indistinguishable from a healthy database. Query and write latency degrade gradually as cardinality climbs, well before any instance hits a hard memory ceiling, which means the warning sign is a slow trend on a dashboard nobody's watching rather than a crash. Budget cardinality the same deliberate way you'd budget a Prometheus label set, and see monitoring & observability for the general discipline of watching your own monitoring stack's resource use, not just the system it's monitoring.

Single-node OSS has no built-in horizontal clustering. The free, self-hosted line has historically shipped as a single binary — no built-in replication or automatic failover across nodes. Clustering and high availability live behind a commercial tier or a managed cloud offering, which is a real capacity-planning input: if the SLO for whatever you're storing in InfluxDB requires it to survive a node loss, that requirement has to be met by the deployment tier you choose, not assumed for free the way it might be with a horizontally-native store. See database reliability engineering for what running any stateful data store as a reliability-critical dependency actually demands.

🐘 Ellie's workshop · 20 min

On a throwaway InfluxDB 2.x container: create a bucket, then write ten points to the same series with curl, all sharing one timestamp but different field values — influx query that series back and count how many points survived. Then write one integer-typed field, followed by a float-typed write to the exact same series, and read the error. Finally, write a hundred points across a hundred distinct values of one tag, then a hundred more across a hundred distinct values of a different, effectively-unbounded tag like a fake request ID, and compare how influx bucket create's cardinality-adjacent stats (or a simple SHOW SERIES CARDINALITY on 1.x) look after each. All three lessons land faster once you've watched them happen once.

InfluxDB vs. the alternatives

☺ Like you're 10: Other tools also store numbers over time — they just trade off retention, cardinality tolerance, and how familiar the query language is.

The real decision is rarely "InfluxDB instead of Prometheus" — most shops run both, at different layers. The actual question is which long-retention or high-cardinality store sits underneath your short-term Prometheus, or which purpose-built store you reach for when the data was never scrape-shaped to begin with.

OptionModelBest whenCosts you
InfluxDBPurpose-built TSDB; push writes via line protocol; InfluxQL/Flux/SQLHigh-volume push workloads (sensors, ticks, client telemetry); multi-year retention with built-in downsampling; multi-field pointsA second query language (or three) to learn; OSS single-node has no built-in HA; still has its own cardinality ceiling
PrometheusPull-based scraping; single-value series per label set; PromQLKubernetes-native infra metrics, alerting, dashboards over the last few weeks; targets are discoverable and scrapeableLocal retention isn't built for the long term; label cardinality is a hard, well-known ceiling; no native push path
Prometheus + a remote-write long-term store (Thanos, Mimir, VictoriaMetrics)Prometheus stays the collector; a second system holds history, speaking PromQL over itYou want PromQL and Prometheus's scrape ecosystem and long retention, with no second query languageAnother distributed system to run; still fundamentally the Prometheus data model, so the cardinality ceiling doesn't move
TimescaleDBTime-series extension on top of real PostgreSQLYou already run Postgres and want real SQL, joins against relational tables, and standard toolingNot purpose-built the way InfluxDB or Prometheus are; scaling write throughput follows Postgres's own limits
GraphiteOlder, simpler flat-file time-series store with its own query languageLegacy environments already standardized on it; simple dotted-metric-name workloadsFeels dated next to the above; weaker on tags/labels, high-cardinality workloads, and modern query ergonomics

A practical rule: reach for InfluxDB when the workload is push-shaped (many independent, possibly unreliable writers) or the retention need is measured in months to years with built-in downsampling, and keep Prometheus as the alerting-and-dashboards layer regardless — it's still the better answer for "is this target up right now." Practice picking between them, and the vocabulary that goes with each, in Practice · Monitoring & SRE Tools.

🎬 At the Reliability Watch
🐘

Ellie the Elephant: Prometheus just dropped our capacity data from six months ago. I need it for next quarter's forecast.

🐿️

Nutty the Squirrel: That's not a Prometheus bug, that's Prometheus doing exactly what its retention flag told it to. You want a database built to remember, not one built to alert fast and forget.

🐘

Ellie the Elephant: So I push the same metrics into InfluxDB with a year-long bucket, downsample the old stuff to hourly, done.

🐢

Timmy the Turtle: Before you do — what's your tag set? If anyone tags a raw request ID or a customer ID on those points, you'll trade "missing six-month-old data" for "cardinality explosion melting the write path."

🦥

Sol the Sloth: I already worked it out. Host, region, and service as tags keeps us under a few hundred thousand series. Add a customer ID and we're past ten million by Thursday.

🐘

Ellie the Elephant: Host, region, service. Nothing that grows with traffic. Understood.

✓ Checkpoint

1. What are the two architectural design points — how a value gets collected, and how long it's meant to be kept — that separate InfluxDB from Prometheus? 2. In line protocol, what's the difference between a tag and a field, and which one is indexed? 3. What is series cardinality, and name one kind of tag value that reliably causes it to explode. 4. What actually happens if you write two points to the same series with the exact same timestamp? 5. Name the three query languages InfluxDB has spoken across its 1.x/2.x/3.x eras, and what replaced Continuous Queries in 2.x. 6. Why might a team run both Prometheus and InfluxDB rather than choosing one?

Check your answers
  1. Prometheus pulls (scrapes targets on an interval) and is designed for short local retention; InfluxDB accepts pushed writes from anything, whenever they arrive, and is designed for long retention with built-in downsampling.
  2. A tag is an indexed, string-only key/value pair used for filtering and grouping (and it's part of what defines a series); a field is the actual value — typed, but not indexed, so filtering on a field's value means scanning it.
  3. Series cardinality is the count of distinct measurement-plus-tag-set combinations the database has to track. An unbounded tag value — a raw request ID, a UUID, a full URL — reliably explodes it, because it multiplies series count by every distinct value that tag has ever taken.
  4. The second write silently overwrites the first, field for field — InfluxDB does not append a second point for a duplicate series-plus-timestamp; the earlier value is simply replaced with no error and no visible warning.
  5. InfluxQL (1.x, kept via compatibility layers later), Flux (2.x, functional and pipe-forward), and SQL (3.x/IOx, on the Arrow/DataFusion engine). In 2.x, scheduled Flux scripts called Tasks replaced Continuous Queries (and absorbed what the separate Kapacitor daemon used to do).
  6. Because they solve different problems well: Prometheus is strong for short-term, scrape-based, Kubernetes-native alerting and dashboards, while InfluxDB is strong for push-shaped, high-cardinality-tolerant, long-retention storage — many teams keep Prometheus as the alerting layer and feed the same or related metrics into InfluxDB (via Telegraf or Prometheus remote_write) for history Prometheus was never built to hold.