Skip to content

Load a year of TeslaMate History in seconds, and show History loading - #96

Merged
blike merged 4 commits into
mainfrom
teslamate-history-queries
Nov 5, 2026
Merged

blike merged 4 commits into
mainfrom
teslamate-history-queries

Conversation

@blike

@blike blike commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

A year of TeslaMate History took 14–16 s for a busy car (4.7M positions). It now takes about 3 s, computing everything from TeslaMate per request. The page also now shows when it's loading, and Drives and Charging fit a phone.

TeslaMate queries

Measured against a real TeslaMate copy (20M positions, two cars), 12 months:

Shadow Before After
drives (moving time) 13.6 s 2.4–2.8 s
locations (map) 7.4 s 0.65 s
Whole page at once 14–16 s 3.0–3.5 s
  • Moving time: casting numeric coordinates was the main cost, about four casts per position. Repeats are now found on the raw values, and only new readings (about a third) are cast, once each. The scan is bounded by the drives' dates so TeslaMate's BRIN index finds the rows. A long listing is split into up to 4 date ranges run concurrently, because window functions run on one core. The TeslaMate pool goes from 3 to 6 connections.
  • Map: positions are sampled every nth by id, not numbered and sorted. The window's first and last positions are always kept.
  • Span queries (drives, charges, states, updates) skip rows that ended before the window.
  • Same output: all 2,804 drives have identical moving time compared with main; the map has the same ends and about the same point count. 7-day and 30-day windows are as fast or faster.
  • Fixed: a drive's last position was dropped because JS dates lose microseconds. A flaky DB test now measures from when its fixture loaded.

server/scripts/bench-teslamate-history.ts times every History call against a TeslaMate URL.

Responses

  • Compression: @fastify/compress cuts a year of History from about 9.7 MB to 1.3 MB.
  • Track cap: MAX_TRACK_POINTS goes from 20k to 60k to improve heatmap accuracy. It's shared with Rivian's /locations, so long Rivian windows can return up to 3× more points.

History loading

  • Period switch: cards still showing the previous period's data are dimmed, with "Loading" centred on them. Cards whose previous period was empty show their placeholder rather than "No … in this period". The summary card shows placeholders rather than old totals.
  • Status: a "Loading" status sits beside the Period picker until every card has the new data.
  • No flicker: everything waits about 0.3 s first, so quick loads and prefetched periods don't flash. The 60 s background refreshes don't trigger it.

Drives and Charging on phones

  • Calendar: it moves to the page header below lg, and the sticky picker takes the full width.
  • Drive header: the date stays on one line, shortened on phones. The driver sits after the date from sm up and top right on phones. On phones the weather goes below the places, which wrap instead of being cut off.

Testing

  • Server and web typecheck, lint and unit tests pass.
  • TeslaMate DB tests pass 8/8 runs.
  • The Rivian DB tests weren't run: the local test database has a stray column from another branch's migration.

blike added 4 commits November 4, 2026 21:27
A year of a busy car is millions of positions, and History took 14 to 16
seconds over one; now about three, with the same figures, and nothing
worked out ahead of time or kept.

Moving time cast every position's coordinates, slow for numeric, about
four times over. Repeats are now found on the stored values, only new
readings are cast, once each, and the scan is bounded by the drives'
dates so TeslaMate's index on them finds the rows. A long listing's
drives are split into up to four runs of time, read side by side, since
Postgres works each query's window functions out on one core; the pool
holds six connections so they don't queue.

The map's positions are picked every nth by id rather than numbered and
sorted, keeping the window's first and last. Drives, charges and states
that ended before a window are left out before anything is joined to
them. A drive's last position is no longer missed for a Date's lost
microseconds, and the test of the open drive's end no longer depends on
how long setup took.

scripts/bench-teslamate-history.ts times every History call against a
TeslaMate database.
A year of History's listings and routes ran to about 10 MB of JSON,
refetched every minute; compressed, it's about 1.3 MB. Live streams
write to the raw response, so they go out as before.

A window's routes may hold up to 60,000 points, not 20,000: a year of
daily driving simplified at 20 m is around 50,000, and past the cap
points are dropped evenly and routes cut corners.
Changing the period left every card on the last period's data, under the
new period's name, until its own data came, with nothing to say so; a
period with nothing showed "No finished drives" while the next loaded.

A card still showing the last period's data is dimmed, with "Loading" in
its middle; one whose last period had nothing shows its placeholder, as
does the summary, whose old totals would mislead. A placeholder says
"Loading" in its middle too, and beside the period, "Loading" shows until
every card has this period's data. Each waits a moment, so a quick load
or a period already loaded doesn't flash, and a minute's refresh doesn't
count.
Below the list's breakpoint the calendar moves to the page header's
right, leaving the picker the width; the picker's arrows are narrower
and its figure keeps its place while the title gives way.

A drive's date and times are one line, with who drove after them, at
the right on a phone, where the date is shortened to fit. There the
weather goes below the places, which wrap rather than being cut short.
@blike
blike merged commit 03f9ca0 into main Nov 5, 2026
1 check passed
@blike
blike deleted the teslamate-history-queries branch November 5, 2026 05:30
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.

1 participant