InfluxDB wins if your cardinality stays low and you want a purpose-built metrics store, TimescaleDB wins if you’re already running Postgres, and VictoriaMetrics wins the moment cardinality or storage cost gets out of hand.
You’ve got metrics pouring in from your infrastructure, and now you need a place to stuff them that won’t choke, bankrupt you, or require a PhD in query syntax. Three names keep popping up: InfluxDB, TimescaleDB, and VictoriaMetrics. They all claim to solve the time-series problem, but they solve it differently.
This isn’t a “pick the winner” article because there isn’t one. It’s a “which one fits your chaos” guide.
The Three Engines at a Glance
These are simply three different approaches to the same basic toolbox:
- InfluxDB is the purpose-built time-series native. Fast ingestion, optimized for the metric lifecycle, but comes with hard cardinality walls and a learning curve on retention policies.
- TimescaleDB is PostgreSQL wearing a time-series hat. If you’re already in the Postgres ecosystem, it feels like home. Query language is just SQL. Cardinality limits are soft.
- VictoriaMetrics is the scrappy contender. Insanely efficient storage, can handle wild cardinality, and treats your RAM like it’s going out of style. Built for exactly what Prometheus scrapes.
The metaphor: InfluxDB is a sports car optimized for the race track. TimescaleDB is a sedan that’s been tuned for highway driving. VictoriaMetrics is a motorcycle: light, fast, and it doesn’t care if the road is crazy.
InfluxDB: The Time-Series Native
InfluxDB was born for metrics. Every design decision assumes you’re shoving timestamps and numbers at it constantly, then querying the heck out of them in predictable patterns.
The Good
Ingestion speed is the first thing you notice. InfluxDB can swallow millions of points per second on modest hardware. This is because the entire storage engine is optimized for “write-heavy, predictable query patterns.”
Retention is built in, not bolted on. You set a policy”keep raw data for 7 days, then downsample to 1-hour aggregates for a year”and InfluxDB handles rotation, deletion, and cleanup without you baby-sitting a cron job.
Flux query language (in InfluxDB 2.x) is powerful if you like functional programming. It’s not SQL, which is either great or terrible depending on your team’s background.
The Bad
Cardinality is your enemy. InfluxDB 2.x falls apart under high cardinality, the TSM/TSI engine balloons memory and slows to a crawl as your series count climbs into the millions. There’s no single magic number where it hard-stops in OSS, but you’ll feel the pain long before then: ingest stalls, queries time out, and the process gets OOM-killed. That sounds like science fiction until you tag every request path + HTTP method + error code + customer ID on a busy API, then wonder why metrics stop flowing at 2 AM.
(Worth knowing: InfluxDB 3, a full Rust rewrite built on Apache Arrow/Parquet, went GA in April 2025 and claims unlimited cardinality, which torpedoes this whole gripe. But it’s a different beast with a different storage model, and tons of shops are still on 2.x. This section is about the 2.x you’re probably running.)
Example: If you measure http_requests with tags for method, path, status, customer, and region, you’re creating a new series for every combination. 10 methods × 50 paths × 20 statuses × 500 customers × 3 regions = 15 million series. Your cluster catches fire.
Multi-tenancy is awkward. Each org gets its own cardinality bucket, but switching orgs in queries feels like jumping between databases. If you’re building a SaaS product where each customer is their own tenant, you’ll spend an afternoon on this.
Cloud pricing is aggressive. Hosted InfluxDB Cloud is billed on writes + queries. At scale, it gets expensive fast. Self-hosted InfluxDB is free, but you’re running the database yourself.
A Typical Docker Compose Setup
version: '3.8'services: influxdb: image: influxdb:2.7 ports: - "8086:8086" environment: DOCKER_INFLUXDB_INIT_MODE: setup DOCKER_INFLUXDB_INIT_USERNAME: admin DOCKER_INFLUXDB_INIT_PASSWORD: changeme DOCKER_INFLUXDB_INIT_ORG: your-org DOCKER_INFLUXDB_INIT_BUCKET: metrics volumes: - influxdb_data:/var/lib/influxdb2 healthcheck: test: ["CMD", "influx", "ping"] interval: 10s timeout: 5s retries: 5
volumes: influxdb_data:Ingesting with Python:
import timefrom influxdb_client import InfluxDBClient, Pointfrom influxdb_client.client.write_api import SYNCHRONOUS
client = InfluxDBClient(url="http://localhost:8086", token="your-token", org="your-org")write_api = client.write_api(write_options=SYNCHRONOUS)
point = Point("cpu_usage") \ .tag("host", "server-01") \ .tag("region", "us-west") \ .field("percent", 45.2) \ .time(int(time.time() * 1e9))
write_api.write(bucket="metrics", record=point)TimescaleDB: PostgreSQL on Steroids
TimescaleDB is a PostgreSQL extension that wraps hypertables around your time-series data. If you’re already running Postgres, this is like finding out your car had a turbo the whole time.
The Good
SQL. Just SQL. No special query language, no learning curve beyond what you already know. Your entire team can write queries immediately. This matters more than it sounds.
Soft cardinality. There’s no hard limit. You can tag millions of unique dimensions. It gets slower, sure, but it doesn’t brick at a magic number.
Compression is excellent. TimescaleDB can compress time-series data 10-40x out of the box. Storage footprint is small.
Ecosystem integration. Since it’s Postgres, you get all the Postgres goodness: full-text search, JSON columns, PostGIS for geospatial data, full ACID semantics. Want to correlate metrics with log data? Put it all in the same Postgres instance.
The Bad
Ingestion throughput is lower than InfluxDB or VictoriaMetrics. You’re probably looking at 100k to 500k points/sec on a single instance. That’s respectable, but if you’re running a mega-scale monitoring system, you’ll need read replicas and sharding strategies.
Operational overhead. TimescaleDB still has to play by Postgres rules: you need to think about WAL, checkpoint tuning, vacuum, and background workers. It’s not a set-it-and-forget-it appliance.
Continuous aggregates add complexity. TimescaleDB’s answer to downsampling is continuous aggregates, which are powerful but require upfront thinking about what you want to pre-compute.
A Typical Docker Compose Setup
version: '3.8'services: timescaledb: image: timescale/timescaledb:latest-pg17 ports: - "5432:5432" environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: changeme POSTGRES_DB: metrics volumes: - timescaledb_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5
volumes: timescaledb_data:Ingesting with Python:
import psycopg2from psycopg2.extras import execute_valuesfrom datetime import datetime, timezone
conn = psycopg2.connect( host="localhost", database="metrics", user="postgres", password="changeme")
# Create hypertable (one-time setup)with conn.cursor() as cur: cur.execute(""" CREATE TABLE IF NOT EXISTS metrics ( time TIMESTAMPTZ NOT NULL, host TEXT NOT NULL, region TEXT NOT NULL, cpu_percent FLOAT8 ); SELECT create_hypertable('metrics', 'time', if_not_exists => TRUE); """) conn.commit()
# Insert datawith conn.cursor() as cur: data = [ (datetime.now(timezone.utc), 'server-01', 'us-west', 45.2), ] execute_values( cur, "INSERT INTO metrics (time, host, region, cpu_percent) VALUES %s", data ) conn.commit()
conn.close()VictoriaMetrics: Built for Prometheus
VictoriaMetrics is ruthlessly pragmatic. It was designed to solve one problem well: be a long-term storage backend for Prometheus. Everything else cascades from that.
The Good
Cardinality handling is insane. VictoriaMetrics shrugs at high cardinality. It doesn’t have hard limits. If you’re tagging everything with request IDs, customer UUIDs, and trace IDs, it’ll handle it without falling over. This is its secret weapon.
Compression is stupid efficient. VictoriaMetrics can achieve 20-100x compression on real-world metric data. If you’re doing 10 billion points per day, you can store a year of data on a single large disk.
Query language is MetricsQL, which is essentially Prometheus PromQL plus some extensions. If your team knows Prometheus, they already know how to query VictoriaMetrics. The learning curve is basically zero.
Resource footprint is tiny. A single VictoriaMetrics instance can replace a multi-node InfluxDB cluster, using a fraction of the RAM and CPU.
Retention is configurable but automatic. Set -retentionPeriod, and old data is deleted on its own. No custom cron jobs, no complex policies.
The Bad
It’s not a general database. VictoriaMetrics is only for time-series data. If you need to store other data types, you’re pulling in another system. It’s specialized, which is a strength and a weakness.
Querying is metrics-first. If you want to do complex joins or relational queries, TimescaleDB or InfluxDB’s approach is cleaner. VictoriaMetrics excels at “sum this metric over time” and “show me the rate of change,” not SQL JOINs.
Community is smaller. InfluxDB has a massive ecosystem. VictoriaMetrics is growing fast but doesn’t have the same breadth of integrations (yet).
A Typical Docker Compose Setup
version: '3.8'services: victoriametrics: image: victoriametrics/victoria-metrics:latest ports: - "8428:8428" command: - "-storageDataPath=/data" - "-retentionPeriod=12" volumes: - victoriametrics_data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8428/-/healthy"] interval: 10s timeout: 5s retries: 5
volumes: victoriametrics_data:Ingesting with Python (via Prometheus remote write):
import requestsimport structimport zlibimport time
# Using the Prometheus remote write protocoldef write_metric_to_victoriametrics(metric_name, labels, value): # This is a simplified example. In production, use a client library. # Install: pip install prometheus-client from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
registry = CollectorRegistry() gauge = Gauge(metric_name, 'metric', labelnames=labels.keys(), registry=registry) gauge.labels(**labels).set(value)
# Push to VictoriaMetrics (via Prometheus push gateway) push_to_gateway('localhost:9091', job='my_job', registry=registry)
write_metric_to_victoriametrics('cpu_usage', {'host': 'server-01', 'region': 'us-west'}, 45.2)Ingestion Benchmarks: A Reality Check
Here’s a rough comparison of how many points per second each engine can swallow on a single 4-core instance with 8GB RAM:
| Engine | Points/sec | Notes |
|---|---|---|
| InfluxDB 2.x | 1 to 2M | Limited by cardinality; drops with high cardinality |
| TimescaleDB | 100k to 500k | More predictable, limited by Postgres tuple insertion |
| VictoriaMetrics | 1 to 5M | Scales well; barely flinches at high cardinality |
These are ballpark figures. Your mileage will vary wildly depending on tag cardinality, data shape, and hardware. But the trend is real: VictoriaMetrics wins on throughput, InfluxDB is second (if you don’t hit cardinality walls), TimescaleDB is the reliable third that plays nice with your existing Postgres infrastructure.
Which One Should You Actually Pick?
InfluxDB if:
- You’re building a brand-new, purpose-built metrics system
- Your cardinality is reasonable (tens of thousands, not millions)
- You want a managed solution (InfluxDB Cloud is convenient)
- You don’t mind the Flux learning curve
TimescaleDB if:
- You’re already running Postgres
- Your team knows SQL cold
- You want to coexist with other data types in the same database
- Cardinality isn’t a concern
VictoriaMetrics if:
- You’re replacing long-term storage for Prometheus
- Cardinality is high (lots of unique tag combinations)
- You want the smallest possible storage footprint
- You want to run this on minimal hardware and never think about it again
The Real Talk
Most teams pick InfluxDB first because it’s the household name. Then, six months into production, they either hit cardinality walls (at 2 AM, naturally) or realize they could have saved 70% on storage with VictoriaMetrics.
TimescaleDB quietly wins in shops that are already Postgres-native. No new operational knowledge, same monitoring stack, same query patterns. It’s boring in the best way.
And VictoriaMetrics? It’s the scrappy option that works a little too well once you understand what it’s for. It won’t replace your application database, and it won’t handle ad-hoc analytics. But for metrics? It’s a laser-focused tool that does one thing and does it obsessively well.
Pick one, set it up, monitor it with the other two, and call it even.