- QuestDB is an open source (Apache License 2.0) time-series database with a column-oriented, time-partitioned storage engine; QuestDB Enterprise builds on the same core.
- Every table should have a designated timestamp: data is stored sorted by it, and it enables partitioning, SAMPLE BY, LATEST ON, ASOF JOIN, TTL and deduplication.
- SAMPLE BY buckets rows by time with gap filling, LATEST ON returns the latest row per series, and ASOF JOIN attaches the most recent matching row from another table.
- Data arrives through QuestDB's client libraries (the newer QWP protocol), the InfluxDB line protocol for existing collectors such as Telegraf, or SQL INSERT over the PostgreSQL wire protocol, which is also the recommended way to query.
- It is not a transactional database for records such as work orders, and PGWire compatibility is partial; keep relational business data in a relational database.
What QuestDB is, and who this is for
QuestDB describes itself as an open source time-series database engineered for low latency. It is a standalone server written mainly in Java with C++ and Rust components, queried with SQL that is extended for time series. Most of its documentation examples use market data such as trades and prices, but the same design applies to sensor and equipment telemetry: many series, continuous arrival, and queries over recent time ranges.
This guide is for developers and data engineers who want SQL rather than a separate query language, and who are evaluating QuestDB next to TimescaleDB and InfluxDB. On 27 October 2026 the latest release on GitHub was 10.1.0, published on 22 October 2026. Background on time-series storage is in the time-series database overview.
Column-based storage, designated timestamp and partitions
Columns. QuestDB's storage engine is column-oriented: inside each partition every column is a separate file, so a query that averages one value reads only that column. The query engine compiles filters with a JIT compiler and processes data in vectorised page frames.
Designated timestamp. One TIMESTAMP column per table is declared as the designated timestamp. Rows are stored physically sorted by it, it cannot be NULL or updated, and time predicates on it are applied first, by binary search within partitions. Without it, a table loses partitioning and most time-series SQL.
Partitions. Tables are partitioned by HOUR, DAY, WEEK, MONTH or YEAR, each partition a directory on disk. The documentation suggests 30 to 80 million rows per partition for average-sized rows. Queries skip partitions outside their time range, and old data is removed a partition at a time.
Write path. Partitioned tables use a write-ahead log by default, which allows concurrent writers, crash recovery and replication, and merges late-arriving rows. Out-of-order data rewrites only the partitions it touches, so smaller partitions reduce write amplification when gateways replay buffered readings.
SYMBOL type. Repeated strings such as sensor, building or asset IDs belong in SYMBOL columns, which are dictionary-encoded: each distinct value is stored once and rows hold integer references. The documentation says the result is "much faster filtering and grouping compared to regular strings"; that is the vendor's statement, not a figure we checked.
CREATE TABLE zone_co2 (
ts TIMESTAMP,
sensor_id SYMBOL,
building SYMBOL,
ppm INT
) TIMESTAMP(ts) PARTITION BY DAY TTL 90 DAYS
DEDUP UPSERT KEYS (ts, sensor_id);Time-series SQL: SAMPLE BY, LATEST ON and ASOF JOIN
QuestDB accepts standard SELECT, WHERE, GROUP BY and joins, and adds keywords for common time-series questions. All three below need a designated timestamp.
- SAMPLE BY groups rows into time buckets (
15m,1h,1d) and applies aggregates to each, which is QuestDB's main downsampling tool.FILLhandles empty buckets withNULL,PREV,LINEARor a constant, and buckets align to calendar boundaries by default, optionally in a time zone. - LATEST ON ... PARTITION BY returns the most recent row for each series, the query behind a "current value" dashboard tile.
- ASOF JOIN matches each row with the latest row in another table at or before its timestamp, optionally on equal keys and within a
TOLERANCE. The documentation's own examples include looking up the current sensor calibration for each reading.
SELECT ts, sensor_id, avg(ppm) AS avg_ppm, max(ppm) AS max_ppm
FROM zone_co2
WHERE ts >= '2026-10-27' AND ts < '2026-10-28'
SAMPLE BY 15m FILL(PREV);SELECT * FROM zone_co2
LATEST ON ts PARTITION BY sensor_id;-- sensor_calibration has its own designated timestamp (valid_from)
SELECT r.ts, r.sensor_id, r.ppm, c.offset_ppm, r.ppm + c.offset_ppm AS corrected_ppm
FROM zone_co2 r
ASOF JOIN sensor_calibration c ON (sensor_id);The same "as of" lookup in plain PostgreSQL
PostgreSQL has no ASOF JOIN, but a LATERAL subquery with ORDER BY ... LIMIT 1 answers the same question, and an index on (sensor_id, valid_from) (here the primary key) supports it; check the plan with EXPLAIN on your own volumes. The query below ran on PostgreSQL 18.6; the third reading picks up the calibration changed at 09:15.
CREATE TABLE zone_co2 (
sensor_id text NOT NULL,
ts timestamptz NOT NULL,
ppm integer NOT NULL,
PRIMARY KEY (sensor_id, ts)
);
CREATE TABLE sensor_calibration (
sensor_id text NOT NULL,
valid_from timestamptz NOT NULL,
offset_ppm integer NOT NULL,
PRIMARY KEY (sensor_id, valid_from)
);
INSERT INTO zone_co2 VALUES
('co2-l3-01', '2026-10-27 08:00+00', 612),
('co2-l3-01', '2026-10-27 09:00+00', 748),
('co2-l3-01', '2026-10-27 10:00+00', 805),
('co2-l3-02', '2026-10-27 09:30+00', 540);
INSERT INTO sensor_calibration VALUES
('co2-l3-01', '2026-10-01 00:00+00', -15),
('co2-l3-01', '2026-10-27 09:15+00', -5),
('co2-l3-02', '2026-10-20 00:00+00', 10);
SELECT r.sensor_id, r.ts, r.ppm, c.offset_ppm, r.ppm + c.offset_ppm AS corrected_ppm
FROM zone_co2 AS r
LEFT JOIN LATERAL (
SELECT offset_ppm
FROM sensor_calibration AS s
WHERE s.sensor_id = r.sensor_id
AND s.valid_from <= r.ts
ORDER BY s.valid_from DESC
LIMIT 1
) AS c ON true
ORDER BY r.sensor_id, r.ts; sensor_id | ts | ppm | offset_ppm | corrected_ppm
-----------+------------------------+-----+------------+---------------
co2-l3-01 | 2026-10-27 08:00:00+00 | 612 | -15 | 597
co2-l3-01 | 2026-10-27 09:00:00+00 | 748 | -15 | 733
co2-l3-01 | 2026-10-27 10:00:00+00 | 805 | -5 | 800
co2-l3-02 | 2026-10-27 09:30:00+00 | 540 | 10 | 550Ingestion: client libraries, InfluxDB line protocol and PGWire
QuestDB offers several write paths, and its documentation now ranks them:
- Client libraries (QWP). The first-party clients for Java, Python, Go, Rust, Node.js, C and C++ and .NET are the recommended route. They are moving to the QuestDB Wire Protocol (QWP), a binary protocol; on 27 October 2026 QWP support was stable in Java, C and C++, Rust and Python, in beta for .NET and Go, and planned for Node.js. Clients without QWP continue to use ILP for writes and PGWire for queries.
- InfluxDB line protocol (ILP). QuestDB accepts ILP over HTTP (port 9000 by default) or TCP (port 9009), and the documentation recommends HTTP for most uses because it reports errors and retries. ILP is now described as a compatibility protocol for existing InfluxDB collectors, Telegraf and Kafka or Flink pipelines. Tables and columns can be created automatically on first write.
- PostgreSQL wire protocol (PGWire). Standard PostgreSQL drivers can connect, and this is the recommended way to query. It also accepts
INSERTfor lower-volume writes, but the documentation directs high-throughput ingestion to the clients. Its compatibility list names SSL,COPY FROM STDINandDELETEstatements as unsupported, so not every PostgreSQL tool will work unchanged.
For a building pipeline, an edge gateway or Telegraf can subscribe to the MQTT broker and send ILP to QuestDB, or a Kafka connector can write from a topic. As elsewhere, the broker moves and buffers messages, QuestDB stores and queries them, and a warehouse or lakehouse handles long-range analytics joined with CAFM and CMMS data. Set DEDUP UPSERT KEYS on (ts, sensor_id) so that a gateway replaying buffered readings does not create duplicates.
On speed, the vendor reports a peak of 19 million rows per second per instance, measured by QuestDB on version 10.0 over QWP and published in its GitHub README. That is a vendor benchmark on its own hardware and data, not an independent result, and it is not comparable with figures other vendors publish.
Open source vs QuestDB Enterprise
Licences. QuestDB Open Source is licensed under the Apache License 2.0; the text is in the repository as LICENSE.txt. Its section 2 grants "a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work", subject to the licence's conditions, such as giving recipients a copy of the licence. QuestDB Enterprise is a commercial product offered through sales; we did not find its licence terms published as a document, so read the agreement you are offered. Not legal advice: this paragraph quotes the licence text and does not say whether a particular use is permitted; read the licence and take advice for your situation.
QuestDB Enterprise uses the open source engine as its core library, so everything in open source works in Enterprise, and adds:
- high availability with replication and automatic failover, and read replicas, using object storage to ship the write-ahead log;
- security features: TLS on all protocols (including HTTPS and TCPS for ILP), single sign-on, role-based access control down to columns, and audit logs;
- automated backups and tiered storage that converts cold partitions to Parquet on object storage, queried through the same SQL. Open source can convert partitions to Parquet with
ALTER TABLE, but does not tier them automatically; - storage policies, which in Enterprise replace TTL for user tables.
Cloud and pricing. QuestDB's Enterprise page lists deployment in the cloud, on premises, hybrid or bring-your-own-cloud (BYOC), with a free Enterprise trial and pricing through sales. On 27 October 2026 the questdb.com pricing address redirected to that Enterprise page and no self-service price list was published, so we do not quote prices.
Retention, sizing and security in practice
- Retention: in open source,
TTLdrops partitions whose whole time range has passed the retention window; manual retention usesALTER TABLE ... DROP PARTITION. Keep longer-lived roll-ups in a separate table or a materialized view built withSAMPLE BY, which can have its own TTL. - Partition size: pick the interval from your row rate (the documentation suggests DAY for roughly 30 to 500 million rows a day, MONTH for 1 to 5 million). As an illustrative assumption, a BMS with 20,000 points at one reading a minute writes about 29 million rows a day, at the boundary between the documented WEEK (5 to 30 million) and DAY (30 to 500 million) bands; use your own point count and sample rate.
- Sizing: storage is per column file, so estimate bytes per column type times rows, then measure on a sample;
SYMBOLcolumns cost an integer reference per row plus the dictionary. - Security: the open source PGWire endpoint does not support SSL, so run it on a private network or behind a TLS-terminating proxy, change default credentials, and consider Enterprise where TLS and RBAC are required.
- Durability: open source is a single node; back it up with the documented backup methods and test restores.
When not to use QuestDB
- Transactional records. Work orders, assets and contracts need row-level updates, deletes and constraints; QuestDB is built for append-mostly time-series tables, and its PGWire endpoint does not accept
DELETE. - PostgreSQL feature dependence. If you rely on PostgreSQL extensions, stored procedures or full driver compatibility, PostgreSQL with TimescaleDB keeps one engine.
- Small, slow data. Our editorial view: at an illustrative few thousand points every 15 minutes, a partitioned PostgreSQL table is often enough, as shown in How to Store IoT Sensor Data.
- High availability on the open source edition. Replication and failover are Enterprise features.
- Metrics stacks built on PromQL or Flux. QuestDB speaks SQL; existing Prometheus dashboards would need rewriting.
For a wider comparison, see the guide to choosing a time-series database and Time-Series Database Performance.
Frequently asked questions
Is QuestDB open source?
Yes. QuestDB Open Source is under the Apache License 2.0 (LICENSE.txt in the questdb repository). QuestDB Enterprise is a commercial product built on the same core with high availability, security and tiered storage added. This is a summary, not legal advice.
Does QuestDB use SQL?
Yes. It uses SQL with time-series extensions such as SAMPLE BY, LATEST ON and ASOF JOIN, and you can query it from PostgreSQL drivers over the PostgreSQL wire protocol.
What is a designated timestamp in QuestDB?
The column that defines a table's time axis. Data is stored sorted by it, and it is required for partitioning, SAMPLE BY, LATEST ON, ASOF JOIN, TTL and deduplication. It cannot be NULL or changed after creation.
Can I send InfluxDB line protocol to QuestDB?
Yes, over HTTP or TCP. QuestDB now treats ILP as a compatibility protocol for existing collectors such as Telegraf, and recommends its own client libraries for new applications.
How does QuestDB handle duplicate sensor readings?
Declare DEDUP UPSERT KEYS on the timestamp and series columns. The documentation says only one row exists for a given set of key columns: a new row with the same keys replaces the old one, and an identical row is skipped, so a retried or replayed row does not add a duplicate. Choose the keys carefully: rows that differ in a key column are kept as separate rows.
Sources
- QuestDB documentation: Introduction
- QuestDB documentation: Architecture
- QuestDB documentation: Designated timestamp
- QuestDB documentation: Time partitions
- QuestDB documentation: Symbol
- QuestDB documentation: Write-Ahead Log
- QuestDB documentation: Deduplication
- QuestDB documentation: Time To Live (TTL)
- QuestDB documentation: CREATE TABLE
- QuestDB documentation: SAMPLE BY
- QuestDB documentation: LATEST ON
- QuestDB documentation: ASOF JOIN
- QuestDB documentation: InfluxDB Line Protocol overview
- QuestDB documentation: PostgreSQL Wire Protocol
- QuestDB documentation: Connect to QuestDB
- QuestDB Enterprise
- questdb repository on GitHub (README)
- questdb repository: LICENSE.txt
- QuestDB release 10.1.0 on GitHub
- QuestDB documentation: Materialized views
Checked 27 October 2026.
How we research these articles: our editorial method. Research-based from official documentation; SQL examples are run on a local database where the page says so. CodeWithSQL earns nothing from the vendors mentioned.