You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(history): pin the sidecar-header esp_tsdb — 21.4 s commits to 0.63 s
Upstream esp_tsdb rewrites the database header in place at offset 0 on
every commit. LittleFS stores a file as a CTZ skip-list of block
*addresses*, so modifying byte N forces every block after N to be
rewritten — overwrite cost is linear at ~20.4 ms/KB (measured across a
190x size range; appends stay flat at ~170 ms whatever the size).
Rewriting offset 0 is therefore the most expensive write the filesystem
offers, and it grew as the databases filled.
That accounted for 15.2 s of a 21.4 s commit — 4 databases x ~3.8 s —
with the remaining ~6.2 s in wrapped-ring block overwrites.
The fork writes the header to an alternating sidecar (<db>.h0/.h1) so the
hot path appends instead of rewriting. Rig, 132 commits: median 633 ms,
max 1,019 ms, no upward drift as the databases fill. It also adds
tsdb_peek_span, which the rolling-files work would need.
Pinned by SHA, not branch: a branch can move under a build and this is
the config people copy. The Config Builder mirrors it, so generated
configs match the reference (same shape the P4 board already uses).
Knock-on corrections, all previously stating 21 s as a fact of life:
- the guide's cost table (~1.2% of device time at the default -> ~0.04%)
and its "cause isn't yet understood" caution, which is now answered
- the build-time warning under 15 min: the reason to think twice is now
retention, not a multi-second stall
- the 5-minute floor's rationale — a duty-cycle guard at 21 s, a
retention guard at 0.65 s (~19 days of panel history)
Verified: reference config compiles with the pin, and the fetched
dependency is confirmed to be the fork (sidecar present in tsdb_core.c,
tsdb_peek_span exported) rather than a silent registry fallback. Full
esp_tsdb host suite passes on the fork — sidecar, multi, resize, schema,
freecap, wrap — so it does not regress existing behaviour. 46/46 site
tests; docs build clean.
Not upstreamed yet; that stays a separate decision.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
12
12
-**Docs screenshots generate themselves.** The guides now show the real device UI — dashboard, history, topology, node table, diagnostics — rendered during the docs build by driving the actual `app.html` against synthetic API responses. No firmware, no hardware, and nothing committed: the images are rebuilt from the current UI every time, so they cannot drift from it. All data is invented, with IPs and MACs from the IETF documentation ranges and a reserved fake-serial prefix, enforced by a test so a real serial, SSID or address can't reach a published page.
13
13
14
14
### Changed
15
+
- **Writing history got about 30× faster — 21 seconds down to under one.** Each snapshot used to lock the filesystem for roughly 21 seconds, which is why chart requests could arrive mid-write and wait, and why the snapshot interval kept being coarsened. The cause turned out to be the storage library rewriting its database header at the very start of the file on every commit: LittleFS stores a file as a chain of block *addresses*, so changing the first bytes forces every later block to be rewritten too — the most expensive write the filesystem offers, and it got worse as the databases filled. The header now goes to a small companion file that is replaced whole instead, so the main file is only ever appended to. Measured on the reference rig across 132 snapshots: median 0.63 s, worst 1.02 s, and flat as the databases fill rather than creeping up. This needs a forked build of `esp_tsdb`, which the reference config and the Config Builder now pin by exact commit; the change has been offered to nobody yet, so it is a fork rather than a version bump. Short intervals are still a trade-off, but the reason is retention now, not write cost.
15
16
- **History resolution is now yours to set, and twice as fine by default.** The snapshot interval had been coarsened twice — 5 to 30 minutes, then 30 to 60 — not because anyone wanted less detail, but because each snapshot's flash writes were what triggered the crash below, so the only lever available was doing it less often. With the underlying fault addressed it becomes a normal trade-off, so it's now a setting: `history_interval`, 5 to 1440 minutes, defaulting to 30. Lower is not free — each database holds a fixed number of readings however often you fill it, so twice the detail is half the span, and each snapshot costs the same few seconds to write regardless of how little changed. At the 30-minute default you get about 4 months of per-panel history and 2 years system-wide; at 10 minutes, about 5 weeks and 7 months, with three times the write load. Anything under 15 minutes says so at build time. Retuning later is safe — each row stores energy since the previous snapshot rather than a running total, so lifetime figures stay correct and existing history isn't invalidated.
16
17
-**Docs rewritten for first-time installers.** A new **Start Here** guide covers what to buy, what the jargon means, and the five steps from parts on the desk to a live dashboard — the "Get Started" button used to drop you into a high-voltage warning and a terminal-block diagram. The sidebar is now ordered by when you need something rather than alphabetically, the Config Builder explains what each field is for, and the reference-heavy pages say up front whether you need to read them. No behaviour changed.
17
18
-**Docs site recoloured to the brand mark.** Link and accent colours moved from green to the mark's silicon indigo and busbar silver; amber keeps its one meaning — this part is live. The blueprint grid behind the headline is now a PV array receding toward the horizon — cells, busbars and module frames, drawn in CSS gradients so it stays sharp at any size and re-tints with the theme.
| 60 min | ~7.5 months | ~3.7 years | ~0.02% | coarse |
88
+
89
+
Writing a snapshot takes about **0.65 seconds**, and the device holds its history
90
+
lock for all of it, so a chart request arriving mid-write waits that long. Flash
91
+
wear still rises as the interval shortens. Values under 15 minutes log a
92
+
build-time warning — retention, not write cost, is now the reason to think twice.
93
+
94
+
The main cost of a short interval is span: each database holds a fixed number of
95
+
rows however often you fill it, so twice the detail is half the history.
106
96
107
97
Changing the interval later is safe: `period_e_*`stores energy since the
108
98
previous snapshot rather than a running total, so lifetime figures stay correct
109
99
across a change and existing history is not invalidated.
110
100
101
+
:::note[Why this needs a forked esp_tsdb]
102
+
A snapshot used to take **21 seconds**, and the reference config pins a fork of
103
+
`esp_tsdb`to avoid it. Upstream writes the database header in place at offset 0
104
+
on every commit. LittleFS stores a file as a skip-list of block *addresses*, so
105
+
changing byte N forces every block after N to be rewritten — overwrite cost is
106
+
linear at about 20.4 ms/KB, while appends stay flat at ~170 ms whatever the size.
107
+
Rewriting the header at offset 0 is therefore the most expensive write the
108
+
filesystem offers, and it accounted for 15.2 s of the 21 s.
109
+
110
+
The fork writes the header to an alternating sidecar file (`<db>.h0` / `<db>.h1`)
111
+
so the hot path appends instead. Measured on the reference rig over 132 commits:
112
+
median 633 ms, maximum 1,019 ms, with no upward drift as the databases fill. The
113
+
change is not upstream yet, so the board config pins the fork by commit SHA.
114
+
:::
115
+
111
116
> **Board note:** the `board:` value above (`m5stack-atoms3`) is an example. The reference rig for this project is the **AtomS3R** — set `board:` to whatever board you actually run so you don't flash the wrong target.
112
117
113
118
The TSDB code is conditionally compiled — without those two dependencies on the include path, `tigo_history.h` short-circuits and the History / TSDB-stats endpoints don't exist. You can run the rest of the component without TSDB; you just lose persistent history.
0 commit comments