TimescaleDB, InfluxDB and Prometheus all store timestamped numbers, so they get compared as if they were three brands of the same product. They are not. Prometheus is a monitoring system that happens to contain a time-series database. InfluxDB is a purpose-built time-series database whose third generation is a columnar engine over Parquet files. TimescaleDB is a PostgreSQL extension that teaches a relational database to partition and compress time-ordered tables. Each one makes a different promise about what you can ask, how long you can keep data, and what happens when a node dies.

This article compares them the way an engineer has to when choosing: by data model, storage engine, query language, cardinality behaviour, downsampling and licence, then works through a sizing example with the arithmetic shown. Product facts were checked against the vendors' documentation on 2026-10-02; where a figure depends on your data, such as compression ratio, the article tells you how to measure it instead of quoting a marketing number.

Advertisement

The data model each one forces on you

Prometheus identifies a series by a metric name plus a set of label pairs, for example http_requests_total{service="checkout",code="500"}. Every distinct label combination is a separate series, and each sample is a timestamp and a 64-bit float, with native histograms as a richer sample type. Data normally arrives by pull: Prometheus scrapes an HTTP endpoint on each target at a fixed interval, which gives it a built-in liveness signal, the up series, for free. Prometheus 3.0, released in November 2024, added UTF-8 metric and label names, a built-in OTLP receiver behind a server flag, and Remote-Write 2.0.

InfluxDB organises data into tables (called measurements in earlier versions) with tag columns that identify a series and field columns that hold values, written over HTTP in line protocol such as cpu,host=web-7,region=eu usage_user=12.5,usage_system=3.1 1727850000000000000. Fields can be floats, integers, strings or booleans, and one line can carry many fields, which makes it natural for wide device readings. InfluxDB 3 Core and Enterprise query with SQL and InfluxQL; Flux, the language of version 2, is not supported in any InfluxDB 3 product, which matters if you are migrating dashboards.

TimescaleDB has no special data model at all. You create an ordinary PostgreSQL table, convert it into a hypertable, and TimescaleDB transparently splits it into chunks by time range. Rows can carry any PostgreSQL type, foreign keys to other tables, JSONB and text. That generality is the whole point: device metadata, billing data and readings can be joined in one query, inside one transaction.

AspectPrometheusInfluxDB 3TimescaleDB
Series identityMetric name plus labelsTable plus tag columnsWhatever your columns say
Value typesFloat, native histogramFloat, int, string, bool fieldsAny PostgreSQL type
Ingest stylePull (scrape), plus remote write and OTLPPush (line protocol)Push (INSERT, COPY)
Joins with business dataNoWithin SQL over its tablesFull relational joins

Storage engines underneath

Same samples, three storage pathsPrometheuspull: scrapes /metricsInfluxDB 3push: line protocolTimescaleDBpush: SQL INSERT / COPYWAL + in-memory headone series per label setWAL (1 s flush) + bufferArrow in memoryPostgres WAL + heaprecent chunk, rowstoreevery 2 hgen1 filespolicy, by ageImmutable blockschunks + inverted indexParquet fileslocal disk or object storeColumnstore chunkssegmentby / orderbycompactioncompaction (Enterprise)continuous aggregatesLarger blocks, local onlyremote write for durabilityLarger, sorted fileslonger query reachRollup hypertablesretention drops chunksPromQLSQL and InfluxQLFull PostgreSQL SQL
How each system moves a sample from ingest to long-lived storage, and the query language at the end of each path.

Prometheus appends incoming samples to a write-ahead log and an in-memory head block. Every two hours the head is cut into an immutable block holding compressed chunks, an index from label pairs to series, and metadata. Chunks use the Gorilla-style encoding: delta-of-delta for timestamps and XOR for float values, which is why the documentation estimates only one to two bytes per sample. Background compaction merges blocks into larger ones covering up to 10% of the retention period or 31 days, whichever is smaller. Default retention is 15 days. Crucially, the documentation states that local storage is not clustered or replicated, so durability and long-term history come from remote write into another system.

InfluxDB 3 buffers writes in memory and flushes a write-ahead log once per second by default, so a write call can wait up to that interval for acknowledgement. Data is persisted as Parquet files organised into time windows, ten minutes by default, on local disk or object storage, and queried with Apache DataFusion over Arrow. In Core, a single query may read at most 432 Parquet files by default, which the documentation equates to roughly 72 hours; going wider means raising the limit and accepting more memory and object-store requests. InfluxDB 3 Enterprise adds compaction, which rewrites small files into larger sorted ones for long-range queries. The 1.x and 2.x releases used a different engine, TSM with the TSI series index, so old tuning advice rarely transfers.

TimescaleDB writes go through normal PostgreSQL: WAL, heap pages and B-tree indexes on the current chunk. A policy later converts chunks past a given age into the columnstore, where rows are grouped by a segmentby column, ordered by an orderby column and stored as compressed column arrays. Since 2.18 the policy function is add_columnstore_policy, replacing the deprecated add_compression_policy. Replication, backups and point-in-time recovery are PostgreSQL's own. The columnar storage ideas are the same ones covered in columnar database architecture, and chunking is range partitioning automated.

Advertisement

The same question in three languages

Ask each system one question: the average CPU per host in five-minute buckets over the last hour. The code shows the three idioms, including TimescaleDB setup using the 2.13 and later by_range form and the 2.18 and later columnstore functions.

# PromQL: user-mode CPU percent per instance (node_exporter labels hosts as "instance"),
# rate() over a counter, evaluated at a 5m step by the caller
avg by (instance) (rate(node_cpu_seconds_total{mode="user"}[5m])) * 100

-- InfluxDB 3 SQL: date_bin buckets, field and tag columns are ordinary columns
SELECT date_bin(INTERVAL '5 minutes', time) AS bucket, host, avg(usage_user) AS cpu
FROM cpu
WHERE time >= now() - INTERVAL '1 hour'
GROUP BY bucket, host
ORDER BY bucket;

-- TimescaleDB: a plain table, made a hypertable, then queried with time_bucket
CREATE TABLE cpu (time timestamptz NOT NULL, host text NOT NULL, usage_user double precision);
SELECT create_hypertable('cpu', by_range('time', INTERVAL '1 day'));
ALTER TABLE cpu SET (timescaledb.enable_columnstore,
                     timescaledb.segmentby = 'host',
                     timescaledb.orderby = 'time DESC');
CALL add_columnstore_policy('cpu', after => INTERVAL '7 days');

SELECT time_bucket('5 minutes', time) AS bucket, host, avg(usage_user) AS cpu
FROM cpu
WHERE time >= now() - INTERVAL '1 hour'
GROUP BY bucket, host
ORDER BY bucket;

The differences are larger than syntax. PromQL is built around counters and rates: rate() handles counter resets and extrapolation for you, and alerting rules are written in the same language, which is why it dominates monitoring. It has no joins with arbitrary tables and weak support for string data. SQL in both other systems handles joins, window functions and ad hoc analysis, but you must implement counter-reset handling yourself if you store raw counters. TimescaleDB is the only one of the three where the query can join readings to a devices table with a foreign key in the same statement.

Cardinality, downsampling and retention

Cardinality is the number of distinct series. In Prometheus each active series lives in the head block's memory and index, so a label holding user IDs or request IDs can multiply memory use until the server falls over; the standard remedies are covered in Prometheus at scale. InfluxDB 3's columnar engine was designed to remove the series-cardinality ceiling of the TSI-based releases, because tags are just columns in Parquet files, but high-cardinality tags still enlarge files and slow queries that group by them. In TimescaleDB, cardinality is simply the number of distinct values in a column; it costs index size, and choosing a very high-cardinality segmentby column produces tiny segments that compress poorly.

Downsampling differs sharply. Prometheus has recording rules, which precompute expressions into new series, but no built-in downsampling of raw data; long-term downsampled storage comes from systems such as Thanos or Mimir behind remote write. TimescaleDB offers continuous aggregates: materialised views over time_bucket that a background job refreshes incrementally, and that can themselves be kept longer than the raw data. InfluxDB 3 provides a processing engine with Python plugins that can run on writes or on a schedule, which you can use to write rollups into another table.

Retention is a flag in Prometheus (by time or size; leave headroom, as the documentation advises setting the size limit to about 80 to 85% of the disk). In TimescaleDB, add_retention_policy drops whole chunks, which is cheap because dropping a partition does not create dead tuples. Note the licence boundary: columnstore, continuous aggregates and retention policies are in the Community edition under the Timescale License, not in the Apache 2 edition that some cloud providers ship.

Worked example: sizing one fleet three ways

Assume 20,000 devices, each reporting 50 numeric metrics every 15 seconds, with 15 days of raw data and 13 months of five-minute rollups. All figures below follow from those assumptions; treat them as starting points to verify with a load test, not as benchmarks.

Ingest rate. 20,000 times 50 is 1,000,000 series. At one sample per 15 seconds that is about 66,667 samples per second, or 5.76 billion per day. Over 15 days that is 86.4 billion samples.

Prometheus. At the documented one to two bytes per sample, 86.4 billion samples need roughly 86 to 173 GB of disk, plus WAL and compaction headroom. A million active series is a well-trodden single-server load, but nothing here is replicated: you would run an HA pair and remote write to a long-term store for the 13-month rollups. If devices push rather than expose an endpoint, you also need a gateway or OTLP ingestion, because pull assumes reachable targets.

TimescaleDB. Store one wide row per device per interval: 20,000 divided by 15 is about 1,333 rows per second, or 115.2 million rows per day. A row with a timestamp, a device ID and 50 double-precision columns is around 430 to 450 bytes including PostgreSQL's tuple header, so the rowstore grows by roughly 50 GB a day before indexes. That number is why the columnstore policy matters: convert chunks after a day or two, measure the real ratio with hypertable_columnstore_stats(), and size disks from that measurement. A continuous aggregate at five minutes reduces the rollup to 20,000 rows per bucket, about 5.8 million rows a day.

InfluxDB 3. Write one line per device per interval with 50 fields, again about 1,333 lines per second. Core will accept that comfortably on suitable hardware, but by default a query reaches only about three days of files, so dashboards spanning two weeks need a raised file limit or Enterprise compaction, and the 13-month rollups need a separate table fed by a plugin or an external job.

The arithmetic exposes the real question: Prometheus suits the 15-day operational window and alerting; TimescaleDB or InfluxDB suits the long-lived, queryable history. Many teams run both, which is the same split Cassandra time-series designs make with raw and rollup tables.

Failure modes

  • Cardinality explosion in Prometheus. A deploy adds a label with unbounded values and memory climbs until the process is killed. Watch prometheus_tsdb_head_series and reject new high-cardinality labels in review.
  • Treating Prometheus as the system of record. A lost disk loses history, by design. If the data matters beyond alerting, remote write it somewhere durable.
  • Rowstore bloat in TimescaleDB. Without a columnstore policy, or with one that fails silently, chunks stay uncompressed and the disk fills. Monitor background job status as carefully as disk usage.
  • Late or out-of-order data. Writing into chunks already converted to the columnstore is slower; continuous aggregates need a refresh window that covers your lateness. Prometheus rejects out-of-order samples unless an out-of-order window is configured.
  • Query reach surprises in InfluxDB 3 Core. A dashboard that worked on day one starts erroring once the queried range exceeds the file limit. Test with your longest real range.
  • Licence surprises. A managed PostgreSQL may ship only the Apache 2 edition of TimescaleDB, without columnstore, continuous aggregates or retention policies. Check before you design around them.

Trade-offs

NeedPrometheusInfluxDB 3TimescaleDB
Alerting on infrastructure metricsBest fit, ecosystem defaultPossible, less toolingPossible, less tooling
Long retention, durable historyNeeds a remote storeGood with Enterprise compactionGood, PostgreSQL durability
Joins with business dataNoLimited to its own tablesNative
Wide device readings, mixed typesFloats onlyNatural fitNatural fit
Operational familiaritySingle binary, simpleNew engine to learnIt is PostgreSQL

If your team already runs PostgreSQL well, the reasons to pick Postgres apply here too: one database, one backup story, real SQL. If you are building observability, start with Prometheus and add a long-term store when retention demands it. If you ingest high-rate device or market data and want object-storage economics with SQL, evaluate InfluxDB 3, paying particular attention to the Core versus Enterprise boundary.

What to do next

  1. Write down your series count, sample interval, retention for raw and rolled-up data, and whether queries must join business tables.
  2. Redo the sizing arithmetic above with your numbers, then load a day of synthetic data into each candidate and measure bytes per sample or per row yourself.
  3. Run your three most important dashboard queries and your longest-range query against that day of data, scaled to the full range.
  4. Check licences and editions in the exact environment you will deploy to, including managed services.
  5. Decide where alerting lives and where history lives; if they differ, design the remote write or export path now.
  6. Set up monitoring for head series, background jobs and disk headroom before going to production.
Key takeaway: Prometheus is a pull-based monitoring system with a fast, local, non-replicated TSDB and the best alerting language; InfluxDB 3 is a columnar engine over Parquet with SQL and InfluxQL but no Flux, and a Core query reach of about 72 hours by default; TimescaleDB is PostgreSQL with automatic time partitioning, a columnstore and continuous aggregates. Size each one from your own series count and retention, measure compression rather than trusting quoted ratios, and expect to pair a monitoring system with a durable history store.