Skip to content
Go back

InfluxDB vs TimescaleDB vs VictoriaMetrics

By SumGuy 10 min read
InfluxDB vs TimescaleDB vs VictoriaMetrics
Contents

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:

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 time
from influxdb_client import InfluxDBClient, Point
from 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 psycopg2
from psycopg2.extras import execute_values
from 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 data
with 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 requests
import struct
import zlib
import time
# Using the Prometheus remote write protocol
def 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:

EnginePoints/secNotes
InfluxDB 2.x1 to 2MLimited by cardinality; drops with high cardinality
TimescaleDB100k to 500kMore predictable, limited by Postgres tuple insertion
VictoriaMetrics1 to 5MScales 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:

TimescaleDB if:

VictoriaMetrics if:

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.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
SQLite Replication: Litestream and rqlite

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts