DS DevShelfHub Projects · AI tools
Articles / Stop Tuning Postgres for Time-Series Workloads: Use TimescaleDB and Move On

Infrastructure

Postgres + TimescaleDB: Fix Time-Series Performance 320x

By DevShelfHub

Why your indexing, partitioning, and autovacuum tweaks keep buying only a few months. The workload pattern that signals a time-series problem, the three architectural reasons vanilla Postgres struggles, and what TimescaleDB actually changes — hypertables, compression, continuous aggregates, retention. Plus a live demo where a single hypertable conversion makes the same query 320× faster.

Postgres + TimescaleDB: Fix Time-Series Performance 320x

Introduction

Your Postgres queries used to take 50 milliseconds. Now they take five seconds. Nothing changed in your code — the table just got bigger. So you do the textbook things: add an index, partition the table, throw more RAM at it, tune autovacuum. Each fix buys a few months. Then you’re back on the treadmill, spending real engineering time and real infrastructure money to keep the same queries running at the same speed.

None of those fixes are wrong. The problem is that they’re all symptom-level. Postgres’s storage and transaction model wasn’t designed for append-only, time-stamped workloads. Once you spot the architectural mismatch, the solution stops being “tune harder” and becomes “use the right engine for the data shape.” That right engine is TimescaleDB, an open-source Postgres extension. This guide walks through why your queries slow down, what TimescaleDB actually changes under the hood, and a live demo where a single hyper-table conversion makes the same query 320× faster.

📚 Table of contents

  • The patch-fix treadmill: index, partition, tune, repeat
  • The workload pattern that screams “time series”
  • Why vanilla Postgres struggles under the hood
  • What TimescaleDB actually does differently
  • The four features that matter: hypertables, compression, continuous aggregates, retention
  • Live demo: 549ms → 1.7ms on the same query
  • Setup walkthrough — Tiger Cloud, MCP, and Cursor
  • When TimescaleDB is (and isn’t) the right call
  • Common mistakes & pro tips
  • Frequently asked questions

🏃 The patch-fix treadmill

The arc is familiar to anyone who’s run a fast-growing events or logs table.

  1. Table hits a few million rows. Queries crawl. You add a B-Tree index. Latency drops back.
  2. Index grows with the table. Writes slow. You partition by time range. Latency drops back.
  3. Partition count climbs into the hundreds. Planner overhead bites. You bump CPU, RAM, autovacuum tuning. Latency drops back.
  4. The table is still growing. The workload hasn’t changed. You’re a year in and your bills are climbing.

Every one of those moves is correct Postgres practice. They’re also all reactive. They treat the symptom — slow queries — not the cause: a row-versioning database is doing a job built for a timestamp-versioning one.

📡 The workload pattern that screams “time series”

If most of these describe your system, you don’t have a Postgres optimisation problem — you have a time series workload running on the wrong engine.

  • Data arrives continuously, not in nightly batches.
  • Almost every row has a timestamp; almost every query filters on a time range.
  • Rows are append-only — once inserted they don’t change.
  • You retain data for months or years for analytics, compliance, or ML training.
  • Queries need to be fast: dashboards, alerts, real-time analysis.

🔬 Why vanilla Postgres struggles here

Three architectural choices in Postgres collide with the time-series pattern.

🧾 23 bytes per row of MVCC bookkeeping

Every row carries metadata for transactions, updates, and deletes. On an append-only table that’s gigabytes of overhead doing nothing useful.

🧹 Autovacuum on rows that never change

The janitor walks your 500-million-row events table looking for cleanup work that doesn’t exist — while competing with real queries for CPU and IO.

🌳 Time-blind B-Tree indexes

A B-Tree treats row 1 and row 500,000,000 identically. It has no concept of “hot recent data” vs “cold archive,” so time-range queries do more work than they should.

What TimescaleDB actually does differently

TimescaleDB is an open-source extension — not a new database. You still write SQL. Your ORM, drivers, monitoring, and existing extensions keep working. What changes is how data is stored, partitioned, compressed, and queried under the hood.

The four features that matter

📦 Hypertables

Convert a normal table to a hypertable with one statement. TimescaleDB silently partitions data into time-bounded chunks. No manual partition management, no chunk-drop scripts, no planner bloat — the planner skips irrelevant chunks automatically.

🗜️ Column compression

Compresses time-series data 90%+. The kicker: compressed data is often faster to query analytically because there’s less to read off disk.

📊 Continuous aggregates

Materialised views that update incrementally instead of rebuilding from scratch. Hourly and daily rollups stay current without reprocessing the entire table.

🧊 Retention & tiering

Automatic policies drop old chunks or tier them to cheap object storage (S3) while keeping them queryable. Cold-but-not-dead data costs almost nothing.

🧪 Live demo: 549ms → 1.7ms

A reproducible scenario: an events table with about 2.6 million rows simulating one record per second across the past 30 days. Each row has a timestamp, a device id, and a numeric value. The query under test:

SQL
EXPLAIN ANALYZE
SELECT device_id, AVG(value)
FROM events
WHERE created_at > NOW() - INTERVAL '1 hour'
GROUP BY device_id;

Before — plain Postgres table

  • Query: average over the past hour, grouped by device.
  • Plan: sequential scan over 2.6M rows.
  • Execution time: 549.5 ms.
  • Rows read: 2,600,000.

Now flip the table to a hypertable with two statements:

SQL
CREATE EXTENSION IF NOT EXISTS timescaledb;

SELECT create_hypertable('events', 'created_at');

After — hypertable, same query

  • Plan: chunk pruning, scan only the relevant time slice.
  • Execution time: 1.7 ms — about 320× faster.
  • Rows read: 3,219.

No new index, no new partitioning logic, no application changes. The win scales with table size: the bigger the dataset, the wider the gap.

⚙️ Setup walkthrough — Tiger Cloud + MCP + Cursor

TimescaleDB is open source and runs anywhere Postgres does. The fastest path is Tiger Cloud (the managed Postgres + TimescaleDB platform from Tiger Data, formerly Timescale). A 30-day trial gives you a working database in about 60 seconds.

  1. Sign up, create a service, pick the default Postgres + TimescaleDB + pgvector image.
  2. Install the Tiger CLI — brew or curl on macOS/Linux, Go on Windows.
  3. Authenticate, set the service password, and install the MCP server for your editor of choice:
Bash
# 1. Authenticate (opens a browser tab)
tiger auth login

# 2. Set the password for this Tiger Cloud service
tiger password set

# 3. Install the MCP server into your editor (Cursor / Claude Code / Codex)
tiger mcp install

From there, prompts like “run this query and explain analyze” or “convert this table to a hypertable” execute against the live database. It removes the SSH and psql friction for quick exploration.

When TimescaleDB is — and isn’t — the right call

✅ Strong fit

  • Append-only event logs, IoT readings, metrics, financial ticks.
  • Heavy time-range queries with rollups (per minute, hour, day).
  • Need to keep months or years of data hot-ish for analytics.
  • Want to stay inside the Postgres ecosystem (tools, drivers, ORMs).

⚠️ Less obvious fit

  • OLTP-heavy tables with constant updates (users, orders).
  • Tiny datasets that fit comfortably in plain Postgres for years.
  • Workloads where the bottleneck is single-row lookups by ID, not time scans.

💡 Common mistakes & pro tips

❌ Common mistakes

  • Putting non-time-series tables into hypertables and confusing chunk pruning.
  • Skipping compression and watching storage costs balloon.
  • Materialising every aggregate up front instead of letting continuous aggregates incrementally refresh.
  • Forgetting to set retention policies and accumulating cold data on hot storage.

✅ Pro tips

  • Pick a chunk interval that fits one to two days of data; review after a month.
  • Enable compression on chunks older than 7–14 days for the biggest win.
  • Use continuous aggregates for any dashboard query that runs more than once a minute.
  • Pair retention policies with cold-tier (S3) storage so old chunks stay queryable.

Conclusion

If your data has timestamps, is append-only, grows continuously, and gets queried by time range, you’re running a time-series workload. Vanilla Postgres can survive it for a while, but the architecture is fighting you the whole time. TimescaleDB removes the mismatch in two lines of SQL, keeps everything else you already love about Postgres, and quietly puts your bills back under control. It’s the most leverage you can get from a single extension install.

Related reading: production web scraping architecture in Pythontraditional RAG vs vectorless RAGLangChain reviewPython one-liners every engineer should knowNext.js video player with ImageKit tutorial

Stop Tuning Postgres for Time-Series Workloads: Use TimescaleDB and Move On FAQ

Do I need to migrate to a new database?

No. TimescaleDB is a Postgres extension. Enable it on your existing instance (or create a new Tiger Cloud service) and convert the offending tables one at a time.

Will my ORM still work?

Yes. Hypertables look like normal tables to the application. Migrations, queries, and joins keep working unchanged.

Is it free?

The extension is open source. Tiger Cloud is the managed offering with a 30-day free trial; you can self-host TimescaleDB on any Postgres instance you control.

How does it compare to ClickHouse or InfluxDB?

ClickHouse and InfluxDB are excellent purpose-built engines but live outside the Postgres ecosystem. TimescaleDB's strength is that you keep SQL, joins, transactions, and your existing tooling.