Skip to content

fix(state): keep maintenance bounded and retain forecast observations - #1283

Merged
frahlg merged 2 commits into
masterfrom
fix/storage-contention-20260917
Sep 17, 2026
Merged

frahlg merged 2 commits into
masterfrom
fix/storage-contention-20260917

Conversation

@frahlg

@frahlg frahlg commented Sep 17, 2026

Copy link
Copy Markdown
Member

Problem and result

Hourly maintenance can exclude live storage long enough to delay telemetry and saved settings. A completed forecast observation is also discarded after one transient write deadline.

Closes #1149, #1275, #1276, #1277, #1279 and #1280.

  • Use passive checkpoints while Core runs. Retry the same verified archive prefix after a one-row write deadline.
  • Read dashboard aggregates outside the writer lock, shorten timed-out batches, and roll ledger rows in bounded atomic batches. Snapshot conflicts reread the source; continuous writers have a fallback within the same one-second budget.
  • Commit immutable forecasts before bounded retention work. Run budget scans before taking SQLite's writer lock and encode score pages before starting writes.
  • Keep completed observations and frozen features in a bounded retry queue. Separate observation collection from issued-forecast archival. Retry model persistence, preserve configuration identity and drain pending observations during shutdown.
  • Expose maintenance phase, failures and recovery, plus explicit forecast health for the simple UI in feat(web): simple forecast status with details on demand #1278. Keep learning start dates unchanged.

Scope and safety

Core storage and its forecast integration only. Preserve FULL durability for goals and telemetry, verified Parquet before source deletion, immutable forecast IDs, exact energy totals and the existing database schema. No device-control commands or UI files change.

The UI task merged #1278 at 792cd21; this branch includes that base. Pending backup PR #1261 retains its existing work; this PR adds status fields in shared files without changing its backup paths. The separate test synchronization fix is in the preceding commit/PR for #1281.

The existing backup latency admission still fails on the Pi: a synthetic 48 MiB backup delayed a durable setting write by 6.43 seconds, above the unchanged two-second limit. A longer control without backup also hit 8.28 seconds; the stall is not specific to backup. #1246 stays open and is under investigation. The proposal in #1287 reduces telemetry history at ingestion and in archives. This PR does not claim all storage work or real charging is validated.

Verification

  • Full local PATH=/opt/homebrew/bin:$PATH make verify passes, including the merged feat(web): simple forecast status with details on demand #1278 UI base.
  • Race detector passes three runs of observation retry, health, maintenance contention, cancellation and ledger batch tests.
  • New regressions fail on unmodified v3.5.3-beta.1: passive checkpoint behavior, single-row archive retry, both cancelled rollup lock waits, and live commits during a delayed dashboard scan.
  • The corresponding tests pass on the candidate. A 254,880-row backlog test completes with concurrent writes and no failed writer.
  • ARM64 test programs pass on a Raspberry Pi using temporary databases: storage and observation regressions, late corrections, atomic rollups, archive corruption refusal, full-disk retry, process-kill recovery and durable WAL commits. A short baseline records 200 durable goals and 200 committed ticks with zero rejection and a worst goal save of 250 ms. A later ten-run control commits all 2,000 ticks with zero rejection but fails latency in one run at 8.28 seconds.

These Pi results exercise synthetic data on real hardware while installed Core keeps running. They do not yet establish a clean full maintenance cycle on the installation's historical database. These fixtures do not establish physical charging behavior.

Checklist

  • Follows VISION.md: durable local state and truthful feedback.
  • Shared files and the separate UI task were checked and coordinated.
  • Failure/recovery tests and local verification pass.
  • No UI files change here; feat(web): simple forecast status with details on demand #1278 owns the UI.
  • Changeset included.
  • DCO sign-offs included.

@frahlg
frahlg changed the base branch from fix/registry-recovery-test-20260917 to master September 17, 2026 07:10
@frahlg
frahlg force-pushed the fix/storage-contention-20260917 branch from 7f12e1e to 11a76ce Compare September 17, 2026 07:29
@frahlg
frahlg marked this pull request as ready for review September 17, 2026 07:34
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-17T07:38:22.701703Z 11a76ce Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11a76ceca4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +46 to +52
func (f *forecastTracker) enqueueObservation(job forecastObservationJob) {
if len(f.pendingObservations) == maxPendingForecastObservations {
f.mu.Lock()
f.observationOverflow = true
f.mu.Unlock()
slog.Error("forecast archive: observation queue full", "start_ms", job.observation.StartMS)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drain recovered backlog before dropping a new interval

When the queue contains 64 jobs and persistence recovers before the next observation tick, observe calls this function before attempting flushObservations; the newly completed interval is therefore discarded, and the immediately following flush can successfully empty the old backlog. Capture the new jobs locally and try draining before rejecting them so recovery does not cause avoidable, permanent loss of measurement evidence.

AGENTS.md reference: AGENTS.md:L22-L25

Useful? React with 👍 / 👎.

Comment on lines +47 to +50
if len(f.pendingObservations) == maxPendingForecastObservations {
f.mu.Lock()
f.observationOverflow = true
f.mu.Unlock()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear overflow health after the pipeline recovers

After any queue overflow, this flag is set permanently: neither a successful flush nor later healthy observations clear it. Consequently, learningHealth reports observation_queue_full for the rest of the process lifetime even when the queue is empty, persistence is working, and training is current; track a current/full condition or clear the flag after recovery while retaining any historical failure separately.

AGENTS.md reference: AGENTS.md:L22-L25

Useful? React with 👍 / 👎.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg force-pushed the fix/storage-contention-20260917 branch from 11a76ce to b293a2c Compare September 17, 2026 07:47
@frahlg
frahlg merged commit 19ab0ea into master Sep 17, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(state): keep forecast archive prune off the insert transaction

1 participant