Databases

Time-Series Databases in Production: InfluxDB 3, TimescaleDB, QuestDB, and When Prometheus Isn't Enough

A principal cloud architect's guide to choosing the right time-series database in 2026. Covers InfluxDB 3's IOx rewrite, TimescaleDB Hypercore, QuestDB's columnar engine, and VictoriaMetrics for production IoT, metrics, and financial workloads.

Architecture diagram comparing time-series database storage engines: InfluxDB 3 IOx with Apache Arrow, TimescaleDB Hypercore hybrid columns, and QuestDB columnar WAL

Around 2012 I was building an industrial IoT platform that ingested sensor readings from fifteen thousand machines across six manufacturing plants in Southeast Asia. We were writing time-stamped telemetry into a standard PostgreSQL database. By month three, queries that had been returning in milliseconds were taking seconds. The tables had swollen to hundreds of millions of rows, the B-tree indexes were consuming more space than the data itself, and the time-range queries the operations team needed every hour were taking the database to its knees.

We had learned the hard way that time-series data has properties that make general-purpose relational databases genuinely poor at handling it beyond a certain scale. The data was almost entirely append-only. The queries always filtered on time ranges. The schema had hundreds of tag combinations we were treating as additional indexed columns. We needed compression that understood the temporal correlation in sensor readings. PostgreSQL was not designed for any of that.

That incident sent me deep into specialized time-series databases, and I have been running them in production ever since. Twenty years in, I have watched the category evolve from narrow tools purpose-built for specific domains into a mature ecosystem with clear architectural trade-offs and genuinely competitive products. In 2026, the landscape has changed significantly enough that recommendations I would have made three years ago are wrong. InfluxDB rewrote itself from the ground up on Apache Arrow. TimescaleDB shipped a hybrid row-columnar storage engine called Hypercore. QuestDB emerged as a serious performance challenger. VictoriaMetrics became the default Prometheus replacement for teams hitting real scale limits.

This article covers what I actually run and why, what the architectural differences mean in practice, and how to choose without making a decision you will regret at 10x scale.

Why Time-Series Data Is Different

Time-series data has four properties that make general-purpose databases struggle:

Monotonic writes. Almost every insert carries a timestamp that is equal to or greater than the last one. The working set for writes is always the current time window. Traditional databases index across the entire dataset for writes, wasting resources on data that will never be written to again.

Query patterns centered on time ranges. The vast majority of reads ask questions like “give me the last five minutes” or “show me yesterday between 9 AM and 6 PM.” B-tree indexes built for equality and arbitrary range queries are poorly suited to this pattern at scale.

High cardinality from tags. Any tag that can take many distinct values, like device ID, user ID, or Kubernetes pod name, multiplies your index size exponentially. I have seen Prometheus deployments collapse because someone added a label for request URL without thinking about cardinality. TSDBs handle this with separate inverted indexes for tags and dedicated storage for the metric values themselves.

Compression that exploits temporal correlation. Sensor readings tend to change gradually. Timestamps tend to increase by nearly the same delta. Delta-of-delta encoding, XOR compression for floats, and run-length encoding for repeated values can compress typical time-series data by 10-50x compared to naive row storage. This matters enormously at IoT scale.

These properties explain why the architectures of the major TSDBs look the way they do.

Comparison of time-series database storage architectures showing InfluxDB 3 IOx columnar Parquet storage, TimescaleDB Hypercore hybrid row-columnar hypertables, and QuestDB column-per-series WAL design

InfluxDB 3: The Complete Rewrite Nobody Expected

InfluxDB 1.x and 2.x were built on TSM (Time-Structured Merge Tree), a custom storage engine with InfluxQL as the query language. It was solid for its era. Then the company announced IOx, and I remember reading the architecture documents thinking: this is a bet-the-company rewrite.

IOx (InfluxDB IOx) is built on Apache Arrow and Apache Parquet. The data lives in object storage. Queries use DataFusion, the Arrow query engine that also powers DeltaLake and many other modern data tools. InfluxDB 3 Core is now the open-source product, while InfluxDB Cloud is the managed service built on the same foundation.

What this means in practice: InfluxDB 3 is now a cloud-native columnar database with SQL as the primary query language. It supports the InfluxDB line protocol for writes (so existing integrations like Telegraf and client libraries still work), but you query with SQL rather than InfluxQL. The data is stored as Parquet files in object storage, with a catalog layer tracking the metadata.

The advantages are significant. Object storage is cheap, so long-term retention costs are trivially low. The Arrow-native execution engine handles analytical queries well. You can use tools like DuckDB or Apache Spark to read the underlying Parquet files directly, which matters for data science workflows. The architecture also makes multi-tenant deployments simpler because you can use separate storage prefixes per tenant.

The trade-offs are also real. IOx adds latency compared to a pure in-process engine because writes go through a WAL, compaction, and eventually object storage. For workloads that need sub-second freshness for writes and need to query that data immediately, you will feel the buffering. The product line underwent significant changes as the company pivoted, and if you are running InfluxDB today, you need to understand which product you are actually using: 1.x with TSM, 2.x, or 3.x with IOx. They are quite different.

My recommendation: InfluxDB 3 is excellent for IoT workloads and analytics where you want long retention in cheap object storage, SQL querying, and compatibility with the Telegraf ecosystem. It is not the best choice for sub-second metrics collection where you need to query data that arrived in the last second.

TimescaleDB: When You Want PostgreSQL to Stay

TimescaleDB is a PostgreSQL extension, and that is both its greatest strength and its most misunderstood property. When I tell people it is “just a PostgreSQL extension,” they often assume it means it has the performance profile of PostgreSQL. That stopped being accurate when Timescale shipped Hypercore.

The traditional TimescaleDB architecture partitions your data into chunks, which are regular PostgreSQL tables grouped by time range (and optionally by space partitions on a tag column). A hypertable looks like a normal PostgreSQL table from the application’s perspective, but writes go to the current chunk, and time-range queries can prune irrelevant chunks entirely. This gave you 10-100x better query performance than a naive PostgreSQL setup for time-series workloads, while keeping full SQL compatibility, joins, foreign keys, and all the PostgreSQL extensions you already rely on.

Hypercore changes the storage layer. Recent chunks are stored in row format for fast inserts, which is what PostgreSQL does natively. Older chunks are automatically converted to columnar format with compression. You configure a policy that says something like “keep the last 7 days in row format, convert everything older to columnar with compression.” The result is that analytical queries on historical data hit columnar storage with 20-90x compression, while your recent writes are as fast as PostgreSQL allows.

The continuous aggregates feature is one of the most underrated capabilities in any TSDB. You define a materialized view that TimescaleDB updates automatically as data arrives: “give me the hourly average, min, and max for each device.” Queries hit the pre-aggregated view rather than the raw data. For dashboards that query across months of history, this transforms a query that would scan billions of rows into one that scans thousands of rows in an aggregates table.

CREATE MATERIALIZED VIEW sensor_hourly
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 hour', recorded_at) AS bucket,
  device_id,
  avg(temperature) as avg_temp,
  min(temperature) as min_temp,
  max(temperature) as max_temp
FROM sensor_readings
GROUP BY bucket, device_id;

The weak points are ingestion throughput and cardinality. TimescaleDB writes go through PostgreSQL’s MVCC machinery, which adds overhead versus a purpose-built columnar storage engine. At sustained multi-million row-per-second ingestion rates, you will start to see CPU contention on PostgreSQL’s write path. And because the time and space partitioning scheme still uses PostgreSQL indexes for tag lookups, extremely high-cardinality tag sets can cause index bloat.

My recommendation: TimescaleDB is my first choice when the workload involves relational data alongside time-series data, when the team is already deeply invested in PostgreSQL tooling, or when you need SQL joins between your time-series data and other relational tables. I have run it for financial tick data, API performance monitoring, and industrial sensor pipelines at hundreds of thousands of writes per second without issues. If you need to push past a few million writes per second on a single node, look elsewhere.

QuestDB: When You Need Maximum Ingestion Speed

QuestDB’s architecture is designed around one goal: maximize write and query throughput with minimal resource usage. The storage model is columnar, with one file per column per table. The WAL (write-ahead log) is designed for parallel ingestion. SIMD instructions accelerate batch operations across columns. The result is benchmark numbers that routinely outperform other TSDBs by large margins on write throughput.

QuestDB supports InfluxDB line protocol for writes, which means most tools in the metrics ecosystem (Telegraf, Vector, Prometheus remote write adapters) can send data to QuestDB without code changes. It also has a PostgreSQL wire-compatible interface for queries, so your SQL tooling (psql, DBeaver, Grafana) works out of the box. The SQL dialect is standard with time-series extensions like SAMPLE BY for automatic time bucketing.

SELECT
  timestamp,
  sensor_id,
  avg(value) avg_value
FROM readings
WHERE timestamp > dateadd('d', -7, now())
SAMPLE BY 1h ALIGN TO CALENDAR;

The SAMPLE BY clause automatically fills missing intervals and aligns to calendar boundaries, which saves you the subquery gymnastics you would need in standard SQL.

Where QuestDB falls short is in the ecosystem and operational maturity compared to InfluxDB or TimescaleDB. The community is smaller, the third-party integrations are fewer, and the documentation, while improving, is thinner than what you get with a product backed by a decade of production deployments. There is also no equivalent of TimescaleDB’s continuous aggregates: you need to build your own pre-aggregation logic.

My recommendation: QuestDB is the right call when raw write throughput is your primary constraint and you are willing to invest in building some of the operational tooling yourself. Financial market data platforms, high-frequency IoT deployments, and observability backends where you are collecting millions of spans per second are good fits. For most teams building their first TSDB deployment, the operational maturity trade-offs make TimescaleDB or InfluxDB 3 easier starting points.

VictoriaMetrics: The Prometheus Replacement

VictoriaMetrics deserves its own discussion even though it targets a narrower use case than the other three. If you are running Prometheus at scale and hitting its limitations (high cardinality degrading performance, single-node storage limits, multi-tenant complexity), VictoriaMetrics is almost certainly the right answer.

The article on Prometheus at scale with VictoriaMetrics, Thanos, and Grafana Mimir covers the Prometheus ecosystem in depth, but here is the summary for TSDB selection purposes: VictoriaMetrics is a Prometheus-compatible time-series database with significantly better compression (typically 70% better than Prometheus’s TSDB), better performance at high cardinality, and a simpler operational model than Thanos or Mimir for many workloads.

The MetricsQL dialect extends PromQL with useful additional functions. The single-node version covers most use cases up to tens of millions of active time series. The cluster version handles horizontal scaling. Both versions support long-term storage in object storage via the remote storage API.

Where VictoriaMetrics is the wrong choice is when you need SQL, when you need to store non-metrics time-series data (events, logs, financial tick data with multiple fields), or when you need to join your time-series data against relational data. It is purpose-built for the Prometheus metrics use case, and that focus is both its strength and its constraint.

The Cardinality Problem Nobody Warns You About

Every TSDB deployment I have seen go wrong has involved cardinality. Cardinality is the number of distinct label combinations for a metric, and it is the primary factor that determines memory usage, index size, and query performance.

Here is the math that catches people off guard. If you have a metric with three labels (service, region, pod), and service has 20 distinct values, region has 6, and pod has 50, your cardinality is 20 x 6 x 50 = 6,000 time series. That is manageable. Now add an HTTP endpoint label with 500 distinct paths: you are at 3 million time series. Add a customer ID label with 100,000 distinct customers: you are at 300 billion. I have seen this happen to real production systems within weeks of launch.

The architectural response to cardinality varies by database. Prometheus and VictoriaMetrics use an inverted index on labels and will degrade predictably as cardinality grows. InfluxDB 3 (IOx) stores tags as columns in Parquet files, which handles high cardinality better for reads but requires careful schema design. TimescaleDB treats tags as regular columns, so high cardinality tags need to be handled with PostgreSQL indexing hygiene. QuestDB has columnar storage with a symbols table for low-cardinality columns and raw string storage for high-cardinality ones.

The practical rule I follow: any label that could take more than 10,000 distinct values at any point in time is a cardinality bomb. Customer IDs, trace IDs, request UUIDs, session IDs, and anything derived from user input should not be metric labels. If you need to analyze behavior at that granularity, you want a tracing system or a log analytics backend, not a TSDB.

Retention Policies and Downsampling

One of the most important features any TSDB provides is automated data lifecycle management. Raw data at full resolution is expensive to store and slow to query across long time ranges. A sensor reading every second is 86,400 rows per sensor per day. For historical analysis at the week or month level, you want pre-aggregated hourly or daily rollups, not raw second-level data.

All the major TSDBs support this, but the implementations differ enough to matter.

InfluxDB 3 uses retention policies at the database level and continuous queries (or Tasks in 2.x/Flux) to create downsampled data in separate tables. The IOx backend stores everything in object storage, so retention costs are low and you can afford longer raw retention than with disk-backed storage.

TimescaleDB’s data retention policies drop chunks older than a configured interval (for example, add_retention_policy('sensor_readings', INTERVAL '90 days')). Continuous aggregates are the downsampling mechanism: you can have separate continuous aggregate views for hourly, daily, and monthly resolution, each with their own retention policies. The continuous aggregate refresh is automatic and incremental. This is the most operationally clean approach I have worked with.

QuestDB supports retention via scheduled jobs and partitioned tables with automatic dropping of old partitions. The SQL interface for this is less polished than TimescaleDB’s.

Diagram showing data lifecycle in a time-series database: raw ingestion at 1-second resolution, automatic rollup to hourly aggregates via continuous aggregates, and tiered retention dropping raw data after 30 days while keeping hourly rollups for 2 years

Choosing the Right Database

Here is how I make the call in practice:

Choose TimescaleDB when you are already using PostgreSQL, your data involves relational joins, your team is more familiar with SQL than with TSDB-specific tooling, or you need the ability to do ad-hoc complex queries across your time-series data and related tables. The learning curve is nearly zero for a PostgreSQL team. The operational model (backup, monitoring, connection pooling with PgBouncer) is exactly what you already know. If you are on AWS and using RDS or Aurora, Timescale Cloud is a managed option that removes the operational overhead.

Choose InfluxDB 3 when you are building an IoT or telemetry platform, you want cheap long-term retention in object storage, you need SQL for analytics, and you have existing integrations with Telegraf or the InfluxDB ecosystem. The IOx architecture is well-suited to write-once-read-seldom patterns that benefit from columnar Parquet storage.

Choose QuestDB when write throughput is your primary constraint, you are comfortable building some operational tooling yourself, and you need the fastest possible ingestion with SQL querying. Financial market data at tick level, high-frequency IoT, or high-volume observability backends are the target use cases.

Choose VictoriaMetrics when you are already on the Prometheus ecosystem and need to scale past single-node Prometheus limits. It is not a general-purpose TSDB but it is the best tool for the Prometheus replacement job. For everything else about why Prometheus scaling matters, the Prometheus at scale article goes much deeper.

Do not use ClickHouse as a TSDB unless you understand the trade-offs. ClickHouse is an excellent OLAP database that handles time-series queries well, but it is not purpose-built for TSDB patterns. High-frequency writes require batching, it lacks native time-series features like continuous aggregates, and the operational model is different. That said, I have seen organizations use ClickHouse for time-series analytics at enormous scale, typically for user-behavior event data rather than infrastructure metrics.

Production Lessons

A few things I have learned that the documentation rarely says clearly:

Start with a schema review. Every TSDB has a concept of measurement/table, tags, and fields. The decision about what goes in a tag versus a field determines your cardinality and query performance. For metrics, treat tags as the dimensions you will group by in queries. Keep the cardinality of each tag bounded. Put the actual measured values in fields.

Your ingestion pipeline matters as much as your storage engine. High-throughput TSDB deployments need a buffer between producers and the database. Writing from thousands of agents directly to your TSDB at one-second intervals will overwhelm any database. A Kafka or NATS JetStream layer in front, with batching adapters that flush every few seconds, changes the write pattern from thousands of tiny writes to hundreds of batched writes and dramatically improves throughput.

Design your continuous aggregates before you have the data. In TimescaleDB, adding continuous aggregates after the table is full means a backfill that can run for hours on large datasets. Know your query patterns at design time and create the aggregates then. For other TSDBs, the same principle applies: your downsampling strategy should be part of your schema design, not an afterthought.

Watch your disk write amplification on SSD-backed deployments. TSDBs that use LSM-tree or copy-on-write storage engines can generate significant write amplification during compaction. For high-ingestion workloads on NVMe SSDs, check the vendor documentation for expected write amplification factors and make sure your SSDs are rated for the sustained write load you will actually generate.

Diagram of a production time-series ingestion pipeline with Kafka as a buffer layer, multiple TSDB workers consuming from topic partitions, and Grafana reading from the TSDB for dashboards

Integrating TSDBs with Your Data Platform

Modern time-series workloads rarely live in isolation. The trend I am seeing is TSDB-plus-lakehouse architectures where the TSDB handles the hot, recent data that dashboards and alerts query, while a data lakehouse stores the historical data for analytics and machine learning.

InfluxDB 3’s Parquet-native storage makes this natural: you can configure it to write Parquet files to S3 or GCS, and the same bucket becomes readable by Spark, Trino, or Athena for historical analytics. The Apache Iceberg and data lakehouse article covers the lakehouse side of this architecture.

For TimescaleDB, the integration with the broader PostgreSQL ecosystem means Foreign Data Wrappers (FDWs) can provide federated queries across your TSDB and a data warehouse. This is particularly useful for teams that want to join time-series data against dimensional tables (customer attributes, product catalogs) stored in Postgres or Redshift.

The Bottom Line

Twenty years ago, the TSDB category barely existed. RRDtool and its successors were niche tools for network monitoring. Today you have a choice of architecturally sophisticated databases each optimized for different trade-offs, and the right choice depends on your cardinality profile, write throughput requirements, query patterns, and team expertise.

The recommendation I give most teams starting from scratch in 2026 is TimescaleDB, because the operational familiarity from PostgreSQL dramatically reduces the learning curve and the Hypercore storage engine has closed most of the performance gap with purpose-built TSDBs for typical workloads. If you already have a Prometheus deployment and need to scale it, VictoriaMetrics is the obvious upgrade path. If you are building a greenfield IoT platform or a high-frequency data ingestion system, InfluxDB 3 or QuestDB deserve serious evaluation.

Whatever you choose, get the schema right before you write the first byte of production data. I have rebuilt TSDB schemas under live traffic more times than I want to admit, and it is always more painful than taking an extra day to design it correctly the first time.