Skip to content

feat: reduce backend memory by 300+ MB via on-demand SQLite stop_times#100

Merged
z4ab merged 2 commits into
mainfrom
feat/reduce-memory-usage
Jun 18, 2026
Merged

feat: reduce backend memory by 300+ MB via on-demand SQLite stop_times#100
z4ab merged 2 commits into
mainfrom
feat/reduce-memory-usage

Conversation

@z4ab

@z4ab z4ab commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Problem

The backend was using ~600 MB of RAM — ~300 MB of which was the _stop_times dict permanently in memory. This dict maps every trip ID to its list of stop-time entries, and with GRT + LRT data that's roughly 500k entries stored as Python dicts.

Solution

Stop keeping _stop_times in the in-memory Cache. Instead, load it from the existing SQLite cache on demand when get_stop_departures() is called. The SQLite DB already stores the full stop_times JSON blob — we just weren't using it as the live data source.

Changes

  • services/gtfs_db.py — Added load_stop_times_for_urls() to query stop_times from SQLite by feed URL; made get_db_path() public
  • services/cache.py — Removed self._stop_times, stop_times collection in refresh_once(), and the deep-copy in get_stop_departures(); now loads from SQLite on demand instead; added _deep_size() estimator and get_memory_estimate() method
  • api/routes.py — Added memory_estimate field to health response
  • tests/test_api.py — Updated FakeCache to match new interface

Trade-off

get_stop_departures() now does a SQLite read + JSON parse per request instead of accessing pre-loaded dicts. This adds ~50-150ms latency to that endpoint but removes ~300 MB from baseline memory. Since stop departures is a user-initiated endpoint (not polled), this is a worthwhile trade.

Impact

Metric Before After
Baseline backend RAM ~600 MB ~250-300 MB
Total project RAM ~700 MB ~350-400 MB
stop_times storage In-memory dict SQLite, on-demand

…loading

The _stop_times dict was the single largest memory consumer, holding
~500k entries (~300 MB) permanently in RAM. Now it's loaded from the
existing SQLite cache only when get_stop_departures() is called,
freeing ~300 MB of baseline memory.

Changes:
- Move _stop_times from in-memory Cache to on-demand SQLite queries
- Add load_stop_times_for_urls() to gtfs_db.py for fetching stop_times
  by feed URL from the SQLite cache
- Make get_db_path() public so Cache can reference it
- Add recursive _deep_size() estimator for per-component memory tracking
- Expose memory_estimate in /health endpoint
- Update FakeCache in tests to match new interface
@railway-app

railway-app Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🚅 Deployed to the bus-tracker-pr-100 environment in grt-bus-tracker

Service Status Web Updated (UTC)
bus-tracker-api 🕛 Waiting for status checks (View Logs) Web Jun 18, 2026 at 1:18 pm
bus-tracker-frontend ⏭️ Skipped (View Logs) Web Jun 18, 2026 at 1:18 pm
1 service not affected by this PR
  • bus-tracker-frontend

@railway-app
railway-app Bot temporarily deployed to grt-bus-tracker / bus-tracker-pr-100 June 18, 2026 13:17 Destroyed
@railway-app
railway-app Bot temporarily deployed to grt-bus-tracker / bus-tracker-pr-100 June 18, 2026 13:18 Destroyed
@z4ab
z4ab merged commit c42326d into main Jun 18, 2026
2 checks passed
@z4ab
z4ab deleted the feat/reduce-memory-usage branch June 25, 2026 18:36
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