Skip to content

Faster telemetry writes and statistics, one upstream send path, shared nginx config - #520

Merged
joszz merged 5 commits into
mainfrom
claude/gallant-rubin-ihbf2v
Oct 19, 2026
Merged

joszz merged 5 commits into
mainfrom
claude/gallant-rubin-ihbf2v

Conversation

@joszz

@joszz joszz commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Pull Request

Summary

The second batch (P2) from the project review; the first was #519.

  • perf(telemetry): merging an MQTT message into its poll's row no longer reads the row (about eighty columns) first: the patch goes onto a key-only stand-in, so EF sends one UPDATE of just the fields the message carried. Checked against a real PostgreSQL: one UPDATE plus the pg_notify per message, where there was a SELECT too; a row that has gone still takes the patch as a new row. Buffering a whole poll in memory was considered and left out: the Worker's own checks read the row right after the write, and a crash would lose up to the merge window.
  • perf(statistics): finished days' statistics are kept in memory per vehicle and time zone, and only the span the cache lacks is read; a statistics page opened again reads today rather than the whole period (up to ten years). A finished day cannot change, since every row is stamped when it arrives; a day counts as finished an hour after its local midnight, past the merge window. Documented under Caching in ARCHITECTURE.md.
  • refactor(upstream): UpstreamRateGate.SendAsync waits out the interval, sends, and reads the answer for a slow-down; the five outside-service clients no longer repeat those steps by hand, and each keeps its own answer to being refused.
  • perf(nginx): the frontend and all-in-one configs share one server body (frontend/nginx-app.conf) and only set their own $api_upstream. The build writes a .gz beside every compressible file at the highest level (scripts/precompress.mjs) and gzip_static serves it, instead of nginx compressing each response at level 1.
  • refactor(data): the repositories demo mode shares with a real deployment are registered once.

Measured and left out: precompiling the vue-i18n messages saved under 1 KB gzipped on the entry bundle, not worth the new build dependency.

Type of Change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Dependency update
  • Other (performance)

Related Issues

Follows #519.

Checklist

  • Tests added or updated (SendAsync, fresh-context merges, the daily statistics cache)
  • Linting passes (pnpm lint / dotnet build), plus dotnet format --verify-no-changes, 1432 backend and 1130 frontend tests
  • No secrets or personal data committed
  • Responsive on mobile (no layout changes)
  • i18n keys added for any new UI strings (none added)
  • If unraid/garagestack.xml changed: not changed

Also checked locally: both nginx configs pass nginx -t, and the frontend config, serving the real build, sends the prebuilt .gz byte for byte with Vary, the security headers and the cache headers. The Docker images and the Playwright smoke tests were not built or run locally (no Docker); CI covers them.


Generated by Claude Code

AddGarageStackData and AddDemoServices listed the same ten repositories and the
upstream clients; demo mode swaps only the vehicle, telemetry and trip ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6DtW2eHhipteRopmy3cif
…ld time

The frontend and all-in-one nginx configs repeated every location; both now set
their own $api_upstream and include frontend/nginx-app.conf. The build writes a
.gz beside every compressible file at the highest level (scripts/precompress.mjs)
and gzip_static serves it, instead of nginx compressing each response at
level 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6DtW2eHhipteRopmy3cif
…a slow-down

Every outside-service client waited out its interval, marked the request sent,
sent it, checked the status for 429/503/504 and opened the backoff, in that
order, by hand. UpstreamRateGate.SendAsync does those steps and hands back the
response with the backoff it opened, if any; each client keeps its own answer
to being refused (null, a sentinel, or an exception).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6DtW2eHhipteRopmy3cif
… first

Every MQTT message of a poll after the first is merged into the poll's row, and
each one read the whole row (some eighty columns) before updating it. The patch
now goes onto a key-only stand-in, so EF sends one UPDATE of just the fields the
message carried. A row that has gone takes the patch as a new row, as before.
Checked against PostgreSQL: one UPDATE and the pg_notify per message, where
there was a SELECT too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6DtW2eHhipteRopmy3cif
GetDailyStatsAsync streamed every reading of the period on every request:
hundreds of thousands of rows for a year, up to ten years. A finished day cannot
change (rows are stamped when they arrive), so each one is cached per vehicle
and time zone, and only the span from the first day the cache lacks to the last
is read. A day counts as finished an hour after its local midnight, past the
merge window that can fold a message just after midnight into a row stamped
before it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6DtW2eHhipteRopmy3cif
@joszz
joszz enabled auto-merge October 19, 2026 19:25
@joszz
joszz merged commit c98880c into main Oct 19, 2026
21 checks passed
@joszz
joszz deleted the claude/gallant-rubin-ihbf2v branch October 19, 2026 19:27
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.

2 participants