QuestDB
QuestDB is QUBIQ's time-series store. A QuestDB connection can serve two purposes:
- The system historian — the one connection designated to receive tag history.
- A data source — queried like any SQL connection, without being the historian.
Only one connection may be the historian at a time.
Configuration
| Field | Description |
|---|---|
| Name | Connection identifier. |
| Host | QuestDB host. |
| Port | Postgres-wire query port, 8812 by default. |
| ILP port | Line-protocol ingestion port, 9009 by default. |
| Database / Username / Password | Query credentials. |
| Use TLS | Encrypt the connections. |
| ILP auth | Enable token authentication for ingestion. |
| ILP token / key ID | ILP credentials, when ILP auth is enabled. |
| AI username / password | Optional read-only credential for the AI assistant. |
Two ports, two protocols: ILP for writes (fast, append-only, fire-and-flush) and pgwire for reads (SQL). Both must be reachable.
Historian tuning
These apply when the connection is the designated historian.
| Field | Meaning | Guidance |
|---|---|---|
| Worker count | Parallel ILP senders | 1–4. More only helps at high tag counts. |
| Auto-flush rows | Flush after N buffered rows | Higher = better throughput, higher latency to visibility. |
| Auto-flush interval | Flush after N milliseconds regardless of row count | Bounds visibility latency on slow-moving plants. |
| Deduplication | Enable QuestDB DEDUP UPSERT KEYS | Makes replayed writes idempotent. Leave on. |
| Partition | Base table partition unit (DAY, HOUR, MONTH) | DAY suits most plants; retention drops whole partitions. |
| Rollup partition | Partition unit for the 1-minute rollup | Coarser than the base table. |
| Rollup refresh every | How often the rollup re-aggregates | Balance freshness against load. |
Between ingestion and QuestDB's flush there is a sub-second window where a value is stored but not yet queryable. The historian keeps a small in-memory tail of recent samples and merges it into live chart queries, so charts look live despite batched writes.
Deduplication matters
Store-and-forward can replay a batch that was written but not acknowledged — a crash between flush and ack. With DEDUP UPSERT KEYS on, the replay overwrites identical rows instead of duplicating them. Turning dedup off trades correctness for a small write gain; do not.
Designating the historian
Exactly one QuestDB connection stores tag history, named by a system setting rather than a per-connection flag — so a two-historian state cannot be represented at all.
The designation lives in Settings → Historian. Reading it needs View connections; changing it
needs Edit connections.
Only the designated connection (plus any still holding historised tags) is provisioned with a historian table, a rollup, WAL settings, an ingestion worker and a retention sweep. A QuestDB connection added purely as a data source stays untouched.
Ownership
Two QUBIQ installations writing to one QuestDB historian table is silent corruption: interleaved
tag IDs, doubled counts, unexplainable gaps. QUBIQ therefore records physical ownership inside
the store itself, not in its own database — an application-level "only one historian" check cannot
see another machine.
If a store is owned by a different installation (a cloned VM, a restored backup, a decommissioned instance), QUBIQ refuses to write and shows an ownership banner. Claiming it is deliberate and audited:
Use Take ownership on the connection (needs Edit connections). Confirm the previous owner is truly retired before doing this.
Schema drift and repair
QuestDB auto-creates a table for an ILP line that arrives for a table that does not exist — with
a schema QUBIQ did not choose: the designated timestamp named timestamp instead of ts, no dedup
keys, no index. Every read path expects ts, so the historian goes silent with "Invalid column: ts".
QUBIQ detects this, alarms on it, and offers two repairs:
| Repair | When |
|---|---|
| Rebuild rollup | The 1-minute aggregate is stale or wrong. |
| Rebuild table | The base table has the wrong designated timestamp. QuestDB cannot rename one, so the data is copied into a correctly-shaped table. |
Both require Edit connections. Both can take minutes on a large store — which is exactly why the automatic path declines above a size threshold and leaves the decision to an operator.
Write-ahead-log supervision
QuestDB applies writes through a WAL. If the apply job hits an unrecoverable error the table is suspended: rows keep landing in the WAL, nothing is applied, and charts go silent.
The historian watches for this and distinguishes two cases:
- Recoverable stall — resumed automatically.
- Poison or missing segment (after a disk event or unclean restart) — alarmed for operator attention, because blindly resuming would loop.
Retention
The retention sweep runs daily and drops whole partitions past the configured age. Partition drops are metadata operations in QuestDB, so retention is nearly free — which is why day-granular partitioning is the sensible default.
Using QuestDB as a plain data source
A QuestDB connection that is not the historian behaves like any SQL connection: named queries,
query bindings, pipeline SQL nodes, system.db.runQuery. Useful for time-series you ingest by
other means.
The QuestDB pipeline node has two modes: ingress (high-speed ILP writes) and SQL query.
→ Pipeline nodes
Sizing
| Factor | Effect |
|---|---|
| Tag count with history enabled | Linear on write rate |
| Change rate and deadband | The biggest lever — a 1%-of-span deadband often removes 90% of rows |
| Retention window | Linear on disk |
| Partition size | Affects drop granularity, not total size |
Estimate ≈ tags × writes-per-second × 86,400 × retention-days rows, then apply QuestDB's compression. Set deadbands before you set retention; it is far cheaper to not store a row.
Troubleshooting
| Symptom | Check |
|---|---|
| Reads work, writes do not | ILP port blocked, or ILP auth enabled without a valid token. |
| "Invalid column: ts" | Schema drift — rebuild the table. |
| Charts stop but ingestion looks fine | Table suspended by a failed WAL apply. |
| Ownership banner | Another installation claims this store. |
| Data appears seconds late | Auto-flush interval too high; lower it or accept the live-tail merge. |
| Disk filling faster than expected | Deadbands not configured, or history enabled on chattering tags. |