Historian
The historian stores tag values over time and answers trend queries. It runs as its own service
(QBQ-Historian) and writes to a QuestDB store.
From 1.0.8 the historian depends on QuestDB 9.4 features. Multi-tag charts use them directly. Upgrade QuestDB before you upgrade QUBIQ. A store older than 9.4.0 is refused when you designate it, and the refusal names the version it found. A store that is already the historian keeps ingesting, so an upgrade notice never costs you data. Its charts are not guaranteed to work, though, and every start logs a critical error until QuestDB is upgraded. → Configuration
The path a value takes
- Scaling is applied at the edge, so stored values are in engineering units.
- The historian stores only tags with history enabled.
- Each reading is stored at its event time: the device's own timestamp when the device sends a plausible one, otherwise the time the server received it. The row also records its quality, and where the time came from. → Quality and time
- The tag's storage rules decide which readings become rows: the deadband (absolute or a percent of range), the minimum time between samples, and the store interval. A change of quality is always stored. → Storage rules
- Writes go through the store-and-forward buffer. An unreachable QuestDB buffers and replays in order rather than losing data.
- Three rollup tiers (1 minute, 1 hour and 1 day) pre-aggregate for long-range trends. → Rollup tiers
- A live tail buffer holds the most recent samples in memory and merges them into live chart queries. It covers the sub-second window between ingestion and QuestDB's flush.
Enabling history
Per tag, on the tag's History tab, or for many tags at once from the tree's Enable History… menu:
| Property | Effect |
|---|---|
| Enable History | Record this tag's values |
| Deadband | Store a value only when it moved by at least this much, in units or as % of range. The biggest lever on volume. |
| Min time between samples (ms) 1.0.8 | Store at most one value per this interval |
| Store interval (ms) | Store the current value again after this long without a stored row, so a value that holds still keeps a recent sample |
| Interpolation 1.0.8 | How the stored values connect on trends: Auto, Linear or Step |
→ Storage rules · Tag properties
Quality is stored with the value New in 1.0.8
Every row carries its quality, Good, Uncertain or Bad, and a non-Good row also keeps
the device's reason (Unknown: Connection down). When a device goes Bad, history shows it as Bad.
It does not show the last value carried on as if nothing had happened.
- Bad rows are left out of every value and every aggregate: the line, the averages, the rollups and the statistics. A Bad row holds the last known value, not a measurement.
- Charts shade the stretches that were not Good and name the reason under the crosshair.
- An
Unknownquality, which is what a lost connection reports, is stored as Bad.
Outages are marked, not drawn over New in 1.0.8
A tag stored on change writes nothing while its value holds still. On a chart, the stored value is therefore carried forward. That is right while the plant is running, and wrong across an outage nobody recorded. The historian marks two kinds of outage with one Bad row per affected tag. Each marker stops the line and shades the gap with its reason:
| Marker | Written when | Reason shown |
|---|---|---|
| Shutdown | The whole platform was down | Shutdown: QUBIQ was not running, no data was collected |
| Collector down | A collector service (QBQ-Bridge, QBQ-Expression) died or was restarted while the rest kept running | Collector down: <service> was not running, no data was collected |
When only the historian or QuestDB was down, nothing is marked. The collectors kept publishing into the store-and-forward buffer, which replays the real data.
Rollup tiers Changed in 1.0.8
| Tier | Rows per tag per year | Serves | Partitioned by |
|---|---|---|---|
Raw (historian) | Every stored sample | Short windows, raw export, exact statistics | The connection's Partition By (Day by default) |
1 minute (historian_1m) | 525,600 | Windows of 12 hours or more | Rollup Partition (Hour by default) |
1 hour (historian_1h) 1.0.8 | 8,760 | Weeks to years | Month |
1 day (historian_1d) 1.0.8 | 365 | Multi-year trends | Year |
A trend reads the coarsest tier that answers it exactly: one whose buckets fit whole inside the chart's buckets on the viewer's time zone. A UTC day chart reads the 1-day tier. A Jakarta day starts at 17:00 UTC, so it reads the 1-hour tier instead. A tier that is missing or behind is skipped, and the chart reads the next finer source.
Each tier is built from the raw table and has its own retention. Repairing one never invalidates another, and the coarse tiers can keep years of trend after the raw rows have expired.
One designated store
Exactly one QuestDB connection is the system historian. A single system setting names it; there is no per-connection flag, so "two historians" cannot be represented at all.
Only that connection, plus any still holding historised tags, gets a historian table, rollups, WAL settings, an ingestion worker and retention. A QuestDB connection added purely as a data source is left alone.
History epochs
Changing the historian would ordinarily truncate every chart at the switchover. A "last 30 days" query against the new store would return only the days since the change, with no sign the rest exists.
QUBIQ records an epoch instead: connection X was the historian from t1 to t2. A read resolves its window to the overlapping epochs, queries each one's store, and stitches the timeline back together.
Two consequences worth knowing:
- Migrating the historian is non-destructive. Trends span the change.
- Retiring a store means retiring its epoch, not deleting history you still want to read.
Range statistics are recombined exactly across epochs. Min and max are extremal; counts, sums, time-weighted sums, totals, counter increases and times in state are additive. Averages and standard deviations are rebuilt from the (Σx², Σx, n) trio. Averaging them would be wrong whenever the epochs hold different sample counts.
Store ownership
Two QUBIQ installations writing to one QuestDB historian table is silent corruption. An
application-level check cannot see another machine, so QUBIQ records physical ownership inside the
store itself.
A store owned by a different installation, such as a cloned VM, a restored backup or a decommissioned instance, still receives history. But QUBIQ makes no schema changes to it, does no rollup maintenance and applies no retention, and Settings → Historian shows an ownership banner. Claiming the store is a deliberate, audited action.
→ QuestDB
Self-healing, within limits
| Mechanism | Handles |
|---|---|
| Schema-drift repair | A historian table missing columns, deduplication or its index is repaired in place. One QuestDB auto-created with the wrong designated timestamp cannot be repaired in place and is reported. |
| WAL supervision Changed in 1.0.8 | A table QuestDB suspended. Resource problems resume once the resource is freed. Anything else raises a critical alarm. Transactions are never skipped automatically. |
| Rollup repair Changed in 1.0.8 | An invalid rollup tier is rebuilt automatically only when the rebuild loses nothing. Otherwise it raises an alarm and waits for you. |
Rebuilding a rollup re-aggregates it from the raw table. Once retention has expired raw rows that a tier still holds, a rebuild would delete that older tier history. So the automatic path declines, and the decision is yours. → Repairing a store
Querying
| From | How |
|---|---|
| A screen | Historian, HmiTrendChart or HmiSparkline widget |
| A script | system.hist.getTrends, getStats and valueAt → System API |
| A spreadsheet | The Historian chart's Export → CSV — every stored sample 1.0.8 |
Always bound the result. A day of one-second data is 86,400 points per tag. The server aggregates to what the chart can draw, and keeps every spike while it does.
Retention Changed in 1.0.8
Retention is the table TTL: QuestDB expires whole raw partitions past the configured age. The rollup tiers keep their history independently, each with its own retention, so a five-year trend can outlive one year of raw data.
Up to 1.0.7 a daily sweep ran DROP PARTITION on the raw table. In QuestDB that invalidates every
rollup built on it, and the recovery rebuilt the rollups from whatever raw rows were left. On every
install with retention set, the 1-minute rollup's older history was destroyed each day. 1.0.8 sets a
TTL instead, and the views stay valid with all their history. Rollup history that was lost before
the upgrade does not come back.
Set storage rules before you set retention. Not storing a row is far cheaper than storing it and deleting it later.
What the historian is not
- Not a general data warehouse. It stores tag time-series. Batch records, orders and documents belong in SQL or MongoDB.
- Not the audit journal. Security events go to a separate external database, deliberately outside QUBIQ's control. → Audit journal
- Not a backup. A configuration backup does not include history. Back up QuestDB with your normal database procedures.
In this section
| Page | Contents |
|---|---|
| Configuration | QuestDB version, designating the store, storage rules, retention, repair, alarms |
| Quality and time | Quality in history, event time and clock skew, heartbeats, outage markers |
| Querying history | Retrieval modes, statistics, value at an instant, export, performance |
| Annotations | Notes on tag history, who may write them, and where they are kept |