documentation/concepts/partitions.md
QuestDB partitions tables by time intervals, storing each interval's data in a separate directory. This physical separation is fundamental to time-series performance - it allows the database to skip irrelevant time ranges entirely during queries and enables efficient data lifecycle management.
Partitioning provides significant benefits for time-series workloads:
Partitioning requires a designated timestamp column. QuestDB uses this timestamp to determine which partition stores each row.
import Screenshot from "@theme/Screenshot"
<Screenshot alt="Diagram showing how table data is organized into time-based partition directories, each containing column files" src="images/docs/concepts/partitionModel.svg" width={700} forceTheme="dark" />
Each partition is a directory on disk named by its time interval. Inside, each
column is stored as a separate file (.d for data, plus index files for
SYMBOL columns).
Available intervals: HOUR, DAY, WEEK, MONTH, YEAR, or NONE.
Target 30-80 million rows per partition for tables with average-sized rows. Tables with many columns should aim for the lower end; tables with few columns can go higher.
Choose your interval based on how much data you ingest:
| Your data volume | Recommended interval |
|---|---|
| >1 billion rows/day | HOUR |
| 30-500 million rows/day | DAY |
| 5-30 million rows/day | WEEK |
| 1-5 million rows/day | MONTH |
| <1 million rows/day | YEAR |
Why this matters:
Other considerations:
DAY
partitions align well)For ILP (InfluxDB Line Protocol) ingestion, the default is DAY. Change it via
line.default.partition.by in server.conf.
Specify partitioning at table creation:
CREATE TABLE trades (
ts TIMESTAMP,
symbol SYMBOL,
price DOUBLE,
amount DOUBLE
) TIMESTAMP(ts) PARTITION BY DAY;
| Creation method | Default partition |
|---|---|
SQL CREATE TABLE (no PARTITION BY) | NONE |
SQL CREATE TABLE (with PARTITION BY) | As specified |
| ILP auto-created tables | DAY |
| Interval | Directory format | Example |
|---|---|---|
HOUR | YYYY-MM-DDTHH | 2026-01-15T09 |
DAY | YYYY-MM-DD | 2026-01-15 |
WEEK | YYYY-Www | 2026-W03 |
MONTH | YYYY-MM | 2026-01 |
YEAR | YYYY | 2026 |
Use SHOW PARTITIONS or the table_partitions() function:
SHOW PARTITIONS FROM trades;
| index | partitionBy | name | minTimestamp | maxTimestamp | numRows | diskSizeHuman |
|---|---|---|---|---|---|---|
| 0 | DAY | 2026-01-15 | 2026-01-15T00:00:00Z | 2026-01-15T23:59:59Z | 1440000 | 68.0 MiB |
| 1 | DAY | 2026-01-16 | 2026-01-16T00:00:00Z | 2026-01-16T12:30:00Z | 750000 | 35.2 MiB |
The table_partitions() function returns the same data and can be used in
queries with WHERE, JOIN, or UNION:
SELECT name, numRows, diskSizeHuman
FROM table_partitions('trades')
WHERE numRows > 1000000;
A partitioned table's directory structure:
db/trades/
├── 2026-01-15/ # Partition directory
│ ├── ts.d # Timestamp column data
│ ├── symbol.d # Symbol column data
│ ├── symbol.k # Symbol column index
│ ├── symbol.v # Symbol column values
│ ├── price.d # Price column data
│ └── amount.d # Amount column data
├── 2026-01-16/
│ ├── ts.d
│ ├── ...
└── _txn # Transaction metadata
When out-of-order data arrives for an existing partition, QuestDB may split that partition to avoid rewriting all its data. This is an optimization for write performance.
A split occurs when:
cairo.o3.partition.split.min.size (default: 50MB)Split partitions appear with timestamp suffixes in SHOW PARTITIONS:
| name | numRows |
|---|---|
| 2026-01-15 | 1259999 |
| 2026-01-15T205959-880001 | 60002 |
QuestDB automatically squashes splits:
cairo.o3.last.partition.max.splits (default: 20)To manually squash all splits:
ALTER TABLE trades SQUASH PARTITIONS;
Partition operations (ATTACH, DETACH, DROP) treat all splits of a
partition as a single unit.