← codec comparison (index.html) · the time axis (time.html)
Process historians that compress with swinging-door trending (SDT; PI's compression, and many others) scan each tag at a fixed rate, drop scans that are within an exception deadband (ExcDev) of the last reported one, and archive only the points that swinging door keeps: actual scans, at irregular times, such that straight lines between them stay close to every snapshot (nominally within CompDev; in practice up to twice that). That is what an export of the archive gives you, and what this page encodes: one block group per tag and day, values and exact timestamps. The data are six simulated tags (5 days each, 1 s scans) chosen to cover what SDT archives look like: from 1 point in 2,000 scans (a valve) to nearly every scan (a noisy tag with a tight CompDev).
Store the archived points themselves. A historian can also export a regular series (interpolated, or held as a forward fill gives), but that throws the points away and costs more: resampled exports.
Regenerate: cd experimental && uv run python make_sdt_report.py (a few minutes; sizes and
errors only, no timings). The simulator is tslab/common/historian.py.
Each tag is scanned every second for a day, then filtered by exception (ExcDev = CompDev / 2, the usual rule; the scan before each exception is reported too) and by swinging door (CompMax 8 h). Times are whole seconds, as a scan class gives. Errors are in CompDevs, CompDev being the historian's nominal tolerance. The historian's trend (straight lines between archived points) strays from the scans by up to about 2.5 CompDevs: swinging door archives the real scan where the doors close, and the line to it is only guaranteed within CompDev at that scan, so earlier snapshots can be up to 2 CompDevs off it (about 1.9 here); the exception filter adds the rest.
| tag | what it is | CompDev | points per day | scans per point | trend vs scans: max | RMS |
|---|---|---|---|---|---|---|
| temperature | Daily cycle + slow drift (τ 30 min) + noise σ 0.03 °C, 0.01 °C resolution | 0.1 | 7,802 | 11.1 | 2.45 | 0.68 |
| flow | Setpoint steps (6/day) through a 60 s lag + process noise (σ 1.5, τ 5 s) + measurement noise σ 0.8, 0.1 resolution: noise above CompDev | 1 | 32,678 | 2.6 | 2.25 | 0.65 |
| tank-level | Fill and drain ramps (0.01–0.05 %/s) between holds + noise σ 0.02 %, 0.01 % resolution | 0.2 | 51 | 1,707.5 | 2.79 | 0.68 |
| valve | Position commands in 5 % steps (30/day), 2 %/s travel, no noise, 0.1 % resolution | 0.5 | 57 | 1,505.2 | 1.96 | 0.06 |
| ph-float32 | pH 7 + slow drift (τ 10 min) + noise σ 0.004, stored as float32 (not decimal) | 0.02 | 8,153 | 10.6 | 2.46 | 0.69 |
| motor-current | Load steps (24/day) through a 10 s lag + noise σ 1 A, 0.01 A resolution: CompDev far below the noise, so most scans are archived | 0.2 | 70,974 | 1.2 | 1.82 | 0.37 |
Points per day: mean over the days. Trend vs scans: the worst over the days, and the mean RMS, in CompDevs, before any fluxcode.
Bytes per archived point over all 5 days, everything a decoder needs included. Raw is an int64 time and a float64 value (16 bytes). zstd and Gorilla are lossless: zstd-3 of the time deltas and the values as two int64/float64 columns; Gorilla with its own delta-of-delta timestamps (in seconds) and XOR values. fluxcode is one block group per tag-day in 1 h blocks (layout), with exact times; its values and times columns split its bytes, the times' being those of the same blocks with the noise floor off minus the same blocks without times.
| tag | points per day | raw | zstd | Gorilla | fluxcode defaults | values | times | max error | exact | kB per day | noise floor off | max error |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| temperature | 7,802 | 16 | 3.52 | 7.73 | 1.54 | 0.84 | 0.70 | exact | 100% | 12.04 | 1.54 | exact |
| flow | 32,678 | 16 | 2.64 | 7.77 | 1.28 | 0.87 | 0.41 | exact | 100% | 41.68 | 1.28 | exact |
| tank-level | 51 | 16 | 11.08 | 10.03 | 11.47 | 5.40 | 6.08 | exact | 100% | 0.58 | 11.47 | exact |
| valve | 57 | 16 | 6.64 | 4.52 | 7.72 | 2.63 | 5.09 | exact | 100% | 0.44 | 7.72 | exact |
| ph-float32 | 8,153 | 16 | 5.01 | 4.08 | 2.47 | 1.81 | 0.66 | 0.00076 | 3% | 20.12 | 2.47 | 0.00076 |
| motor-current | 70,974 | 16 | 2.40 | 7.44 | 1.37 | 1.24 | 0.13 | exact | 100% | 97.56 | 1.37 | exact |
Max error: the worst |decoded − archived| over the days, in CompDevs. Exact: the share of points that decode bit-exact. Values and times: the times' bytes are those of the same blocks with the noise floor off minus the same without times. The noise floor section explains where the defaults and the noise floor off differ.
What matters downstream is the trend: straight lines between the decoded points, compared with the scans the historian saw. SDT alone is the historian's own error; fluxcode's is added on top. In CompDevs, worst over the 5 days.
| tag | SDT alone: max | RMS | + fluxcode defaults: max | RMS | + fluxcode, noise floor off: max | RMS |
|---|---|---|---|---|---|---|
| temperature | 2.445 | 0.679 | 2.445 | 0.679 | 2.445 | 0.679 |
| flow | 2.250 | 0.650 | 2.250 | 0.650 | 2.250 | 0.650 |
| tank-level | 2.785 | 0.678 | 2.785 | 0.678 | 2.785 | 0.678 |
| valve | 1.958 | 0.058 | 1.958 | 0.058 | 1.958 | 0.058 |
| ph-float32 | 2.455 | 0.694 | 2.455 | 0.694 | 2.455 | 0.694 |
| motor-current | 1.817 | 0.368 | 1.817 | 0.368 | 1.817 | 0.368 |
fluxcode's noise floor (default 0.25σ) coarsens the step of blocks whose samples look like white measurement noise. An SDT archive's sparse points look like that without being noise: swinging door keeps exactly the points a straight line can't predict. So a block with irregular times gets a floor only if it has a cadence: at least 90% of its intervals one scan (between half and 1.5 times the median of 15 evenly spaced intervals). Then the noise is measured on consecutive scans only. An archive that kept nearly every scan is raw sensor data, and gets the floor; any sparser one gets none and keeps its values. Below, each tag's archive at five CompDevs (ExcDev half of it), 1 h blocks, all 5 days, against the same with the noise floor off.
| tag | CompDev | points per day | one-scan share | blocks floored | kB per day | noise floor off | max error | exact |
|---|---|---|---|---|---|---|---|---|
| temperature | 0.025 | 48,119 | 51%–56% | 0 of 120 | 47.56 | 47.56 | exact | 100% |
| 0.05 | 27,252 | 23%–32% | 0 of 120 | 32.16 | 32.16 | exact | 100% | |
| 0.1 | 7,802 | 1%–6% | 0 of 120 | 12.04 | 12.04 | exact | 100% | |
| 0.2 | 1,668 | – | – | 3.60 | 3.60 | exact | 100% | |
| 0.4 | 385 | – | – | 1.29 | 1.29 | exact | 100% | |
| flow | 0.25 | 67,577 | 75%–79% | 0 of 120 | 68.76 | 68.76 | exact | 100% |
| 0.5 | 52,838 | 57%–61% | 0 of 120 | 59.86 | 59.86 | exact | 100% | |
| 1 | 32,678 | 33%–39% | 0 of 120 | 41.68 | 41.68 | exact | 100% | |
| 2 | 10,030 | 5%–11% | 0 of 120 | 15.52 | 15.52 | exact | 100% | |
| 4 | 844 | – | – | 2.05 | 2.05 | exact | 100% | |
| tank-level | 0.05 | 1,651 | – | – | 3.88 | 3.88 | exact | 100% |
| 0.1 | 52 | – | – | 0.59 | 0.59 | exact | 100% | |
| 0.2 | 51 | – | – | 0.58 | 0.58 | exact | 100% | |
| 0.4 | 51 | – | – | 0.59 | 0.59 | exact | 100% | |
| 0.8 | 51 | – | – | 0.60 | 0.60 | exact | 100% | |
| valve | 0.125 | 71 | – | – | 0.46 | 0.46 | exact | 100% |
| 0.25 | 71 | – | – | 0.46 | 0.46 | exact | 100% | |
| 0.5 | 57 | – | – | 0.44 | 0.44 | exact | 100% | |
| 1 | 57 | – | – | 0.44 | 0.44 | exact | 100% | |
| 2 | 54 | – | – | 0.44 | 0.44 | exact | 100% | |
| ph-float32 | 0.005 | 46,078 | 46%–52% | 0 of 120 | 87.70 | 87.70 | 0.00076 | 3% |
| 0.01 | 25,292 | 19%–26% | 0 of 120 | 53.62 | 53.62 | 0.00076 | 3% | |
| 0.02 | 8,153 | 0%–4% | 0 of 120 | 20.12 | 20.12 | 0.00076 | 3% | |
| 0.04 | 1,936 | – | – | 5.90 | 5.90 | 0.00076 | 3% | |
| 0.08 | 475 | – | – | 2.00 | 2.00 | 0.00038 | 4% | |
| motor-current | 0.05 | 82,351 | 94%–96% | 120 of 120 | 62.49 | 106.44 (-41%) | 0.6 | 4% |
| 0.1 | 78,248 | 89%–92% | 99 of 120 | 69.30 | 103.73 (-33%) | 0.6 | 21% | |
| 0.2 | 70,974 | 80%–84% | 0 of 120 | 97.56 | 97.56 | exact | 100% | |
| 0.4 | 58,601 | 64%–70% | 0 of 120 | 85.92 | 85.92 | exact | 100% | |
| 0.8 | 39,837 | 45%–51% | 0 of 120 | 64.25 | 64.25 | exact | 100% |
One-scan share: the range over the 1 h blocks of at least 256 points (smaller blocks get no noise floor). Blocks floored: those that decode differently with the noise floor off, of the blocks of at least 256 points. Max error: the worst |decoded − archived| at the defaults, in CompDevs of the tag's own (the first table), so that the rows of a tag compare. Exact: the share of points that decode bit-exact at the defaults.
fluxcode's B (max_quantize_bits) from 4 to 16 with the noise floor off, and the defaults (diamond: B = 16
and the noise floor).
The error is the worst |decoded − archived| over the 5 days, in CompDevs; exact decodes sit on the bottom line. B sets the
step from each block's range, so the same B gives a different error on each tag: there is no B that means "a tenth of
CompDev".
| tag | B = 4 | B = 6 | B = 8 | B = 10 | B = 12 | B = 14 | B = 16 |
|---|---|---|---|---|---|---|---|
| temperature | 0.93 B 5 | 1.15 B 1.2 | 1.40 B 0.3 | 1.54 B 0.075 | 1.54 B exact | 1.54 B exact | 1.54 B exact |
| flow | 0.83 B 4 | 1.08 B 1 | 1.25 B 0.2 | 1.28 B 0.05 | 1.28 B exact | 1.28 B exact | 1.28 B exact |
| tank-level | 10.52 B 19 | 11.00 B 5 | 11.40 B 1.2 | 11.81 B 0.3 | 11.89 B 0.075 | 11.47 B exact | 11.47 B exact |
| valve | 7.84 B 8 | 8.02 B 2 | 7.72 B exact | 7.72 B exact | 7.72 B exact | 7.72 B exact | 7.72 B exact |
| ph-float32 | 0.93 B 3.1 | 1.16 B 0.78 | 1.42 B 0.2 | 1.67 B 0.049 | 1.94 B 0.012 | 2.19 B 0.0031 | 2.47 B 0.00076 |
| motor-current | 0.53 B 10 | 0.79 B 2.5 | 1.04 B 0.6 | 1.28 B 0.15 | 1.37 B 0.038 | 1.37 B exact | 1.37 B exact |
Each cell: bytes per archived point (values + times), and below it the max error in CompDevs.
An archive has few points per hour, so how a day is cut into blocks matters more than for 1 kHz data: every block stores its own anchors, sizes and time reference. Bytes per point at the defaults, all 5 days.
| tag | points per day | 10 min blocks | 1 h blocks | 1000-point blocks | one block per day |
|---|---|---|---|---|---|
| temperature | 7,802 | 1.71 | 1.54 | 1.51 | 1.50 |
| flow | 32,678 | 1.28 | 1.28 | 1.27 | 1.34 |
| tank-level | 51 | 21.42 | 11.47 | 4.60 | 4.60 |
| valve | 57 | 13.20 | 7.72 | 3.52 | 3.52 |
| ph-float32 | 8,153 | 2.79 | 2.47 | 2.38 | 2.30 |
| motor-current | 70,974 | 1.33 | 1.37 | 1.34 | 1.39 |
Time blocks come from encode_time_blocks (empty hours cost almost
nothing); point blocks from encode_group(block_len=…). One block per day is 65,535 points at most, so the
densest tag gets two.
Scan-aligned times are whole seconds; with source timestamps each point keeps the device's time, a scan delayed by 0–200 ms at ms resolution (as OPC timestamps often are). The values are the same. Bytes per point for the times only (fluxcode defaults, the block group minus the same blocks without times), and Gorilla's whole size for comparison.
| tag | fluxcode times: scan-aligned | source timestamps | Gorilla (all): scan-aligned | source timestamps |
|---|---|---|---|---|
| temperature | 0.70 | 2.02 | 7.73 | 10.69 |
| flow | 0.41 | 1.62 | 7.77 | 9.67 |
| tank-level | 6.08 | 7.58 | 10.03 | 11.77 |
| valve | 5.09 | 6.73 | 4.52 | 5.74 |
| ph-float32 | 0.66 | 1.94 | 4.08 | 7.00 |
| motor-current | 0.13 | 1.24 | 7.44 | 8.67 |
A historian can also be read at a fixed rate: interpolated (straight lines between the archived points) or held (each scan takes the last archived value, as a forward fill gives). If that is all you have, fluxcode stores the regular series (defaults, 1000-scan blocks, its 1 s times included), but the archived points are cheaper and keep the information: an interpolated series is off the decimal grid, and its rounded ramps leave residuals of a step or so. kB per day, mean over the 5 days; errors are max |decoded − read-back| in CompDevs.
| tag | archived points + times: kB/day | interpolated at 1 s: kB/day | max error | held at 1 s: kB/day | max error |
|---|---|---|---|---|---|
| temperature | 12.04 | 46.36 | 0.00061 | 17.21 | exact |
| flow | 41.68 | 96.53 | 0.00096 | 46.35 | 0.05 |
| tank-level | 0.58 | 4.53 | 0.0024 | 1.13 | exact |
| valve | 0.44 | 1.12 | 0.0019 | 0.49 | exact |
| ph-float32 | 20.12 | 47.76 | 0.00038 | 37.06 | 0.00038 |
| motor-current | 97.56 | 63.34 | 0.62 | 61.64 | 0.6 |
If the scans are still available (at the source, or before the historian compresses), fluxcode can store every one of them instead. Here every scan of the day is one block group (1000-scan blocks, the 1 s times included): at the defaults, and at the coarsest B (noise floor off) whose max error is within a budget: one CompDev (SDT's nominal tolerance), or the error SDT actually made that day (the same max error, a fair comparison). The SDT route is the archive with fluxcode (defaults), its error that of the trend. kB per day, mean over the 5 days; errors are the worst max |decoded − scan| in CompDevs.
| tag | SDT + fluxcode: kB/day | max error | every scan, defaults: kB/day | max error | within 1 CompDev: kB/day | B | max error | within SDT's error: kB/day | B | max error |
|---|---|---|---|---|---|---|---|---|---|---|
| temperature | 12.04 | 2.45 | 54.86 | exact | 30.26 | 6 | 0.6 | 20.82 | 5 | 1.2 |
| flow | 41.68 | 2.25 | 61.93 | 0.1 | 46.45 | 5–6 | 1 | 35.81 | 4–5 | 2 |
| tank-level | 0.58 | 2.79 | 47.77 | exact | 20.54 | 8 | 0.6 | 11.52 | 6 | 2.5 |
| valve | 0.44 | 1.96 | 0.70 | exact | 0.69 | 7–8 | exact | 0.69 | 7–8 | exact |
| ph-float32 | 20.12 | 2.45 | 136.69 | 0.049 | 20.21 | 5 | 0.78 | 13.98 | 4 | 1.6 |
| motor-current | 97.56 | 1.82 | 62.67 | 0.6 | 73.41 | 7–8 | 0.6 | 62.03 | 6–7 | 1.2 |
The same comparison as CompDev changes (ExcDev stays half of it), mean kB per day: