Every database in this comparison can store a billion ticks. The differences that matter are what happens when you query them — and vendor benchmarks won’t tell you, because each engine wins its own benchmark. What follows is a comparison by workload shape, based on each system’s documented architecture and the patterns that repeat across quant teams. Where we make a performance claim, it’s an architectural consequence, not a number from a marketing page; run your own benchmark on your own queries before committing (a starter harness is sketched at the end).

Start with your workload, not the database

Tick-data work stresses three very different capabilities:

  1. Research scans — “average spread by symbol-hour across three years”: full-table analytical scans, massively parallel, read-only. Column stores dominate.
  2. Live ingestion + recent-data queries — streaming millions of events/day while querying the last hour: write throughput plus fresh-data visibility.
  3. Time-series joins — as-of joins (“last quote at or before each trade”), window logic, order-book reconstruction: this is where general-purpose SQL engines historically embarrass themselves and specialized engines shine.

Score each candidate against your mix; almost every wrong choice in this space is a category-2 tool bought for a category-1 job or vice versa.

The candidates

DuckDB — the research default

In-process analytical engine over Parquet files; zero ops. For a single researcher doing historical analysis, it is simply the correct starting point in 2026: full SQL including a native ASOF JOIN, vectorized scans that saturate NVMe, and Arrow-native interchange with Polars/pandas so results move without serialization. Its honest limits: it’s single-node, single-writer, and not an ingestion server — pair it with a raw Parquet archive (see the stack guide) rather than treating it as a live store. Many teams discover their “we need a cluster” problem was actually a “we needed partitioned Parquet and DuckDB” problem.

ClickHouse — the scale-up column store

The open-source heavyweight for analytical scans over tens of billions of rows: aggressive compression, brutal parallelism, mature replication, and a huge operational knowledge base. It has an ASOF JOIN, materialized views for continuous aggregation, and handles high-rate inserts well in batches. Costs: it’s a server you operate (or pay ClickHouse Cloud to), its SQL dialect has sharp edges, and mutation/update patterns are awkward by design — model your data append-only. Choose it when data or concurrency genuinely outgrows one process.

TimescaleDB — Postgres with a time-series engine

A Postgres extension adding hypertables, columnar compression, and continuous aggregates. Its killer feature is the ecosystem: full Postgres SQL, joins against your reference/positions tables, every ORM and BI tool, transactional updates. For pure tick-scan analytics it’s architecturally behind the column stores (row-based hot storage, compression as a maturing add-on), and there’s no native as-of join — you emulate with LATERAL, which works but won’t win races. Choose it when tick data must live next to relational data and operational simplicity beats peak scan speed. (Licensing note: the compressed/columnar features live under the Timescale license, not vanilla Postgres open source — check terms if you redistribute.)

QuestDB — ingestion-first time series

Purpose-built for exactly this domain: very high streaming ingestion via InfluxDB line protocol, SQL with native ASOF JOIN and time-partitioned storage, minimal configuration. It’s the strongest open-source answer to “I need to ingest live feeds all day and query them in SQL.” The trade-offs are a younger ecosystem, thinner distributed story, and less battle-tested operational tooling than ClickHouse/Postgres. Well suited to a small desk capturing its own feed.

kdb+/q — the incumbent

Still the reference architecture on institutional desks: column store, memory-mapped historical partitions, and q — a language where as-of joins (aj) and time-series transforms are primitives, not workarounds. Nothing matches its expressiveness-per-line for order-book work. The costs are equally real: commercial licensing (list pricing is “talk to sales”; budget accordingly), a language with a steep learning curve and a thin hiring pool outside finance hubs, and an ecosystem that assumes you’re an institution. For a solo shop it’s rarely rational unless you already speak q; for a desk hiring from banks, it’s often the path of least resistance.

The comparison, condensed

DuckDBClickHouseTimescaleDBQuestDBkdb+
Ops burdenNoneRealModerate (it’s Postgres)LightReal + license
Historical scan speedExcellent (single node)Excellent (any scale)Good with compressionGoodExcellent
Live ingestionNot its jobGood (batched)GoodExcellentExcellent
As-of joinsNativeNativeEmulatedNativeNative, best-in-class
Joins vs. reference dataGoodLimited by designBest (full Postgres)BasicGood, in q
CostFreeFree / cloudFree / cloud (license nuance)Free / cloudCommercial
Team fitSolo researcherData-heavy teamMixed workload teamSmall live deskInstitutional desk

Defaults we’d actually pick

  • Solo researcher, historical work: Parquet + DuckDB. Revisit only when a second concurrent writer or a second machine appears.
  • Small team, growing archive, shared queries: ClickHouse, with Parquet remaining the interchange/archive format.
  • Live capture of your own feeds with SQL on fresh data: QuestDB for the hot path, exporting daily partitions to Parquet for research.
  • Tick data must join operational/relational state constantly: TimescaleDB, accepting the scan-speed ceiling.
  • Institutional desk with q skills in-house: kdb+, knowingly.

Benchmark it yourself — in an afternoon

Any comparison you didn’t run yourself is a hypothesis. The minimal honest harness: one liquid symbol-month of real ticks (not synthetic — compression and cardinality behave differently), loaded identically into each candidate, timing five queries: (1) full-scan aggregate by symbol-day, (2) one-symbol one-day slice, (3) trades-to-quotes as-of join, (4) 1-minute OHLCV resample, (5) your actual signal query. Run cold and warm, record load time and on-disk size. The spread between engines on query 3 and query 5 is usually what decides it — and it’s exactly what generic benchmarks don’t measure.

Assessments dated September 2026. No sponsored placements; if that ever changes it will be disclosed inline.


Also in this series: The full research stack → · Choosing a data vendor → · Size it first: tick data storage sizer →