Skip to content

Optimize GTFS ingestion (~40% faster) and drop bulk-load PRAGMAs - #42

Merged
chtitux merged 1 commit into
mainfrom
optimize-ingestion
Apr 20, 2026
Merged

Optimize GTFS ingestion (~40% faster) and drop bulk-load PRAGMAs#42
chtitux merged 1 commit into
mainfrom
optimize-ingestion

Conversation

@chtitux

@chtitux chtitux commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Rewrites the CSV loader to parse each file only once, use positional arrays instead of per-row Record<string, string> objects, and reuse a single prepared INSERT per table rather than re-preparing a multi-row VALUES (?,?),(?,?),... statement per 1000-row batch.
  • Replaces the pre-flight double-parse (used only to populate progress totals) with an O(bytes) newline-count estimate. ProgressInfo.totalRows is now documented as an estimate (typically exact, may be ±a few per file).
  • Drops the bulk-load PRAGMA block (synchronous=OFF, journal_mode=MEMORY, temp_store=MEMORY, cache_size=-64000, locking_mode=EXCLUSIVE) and its post-ingest reset. Benchmarked effect on sql.js is within noise (≤1%); removal unblocks pluggable-adapter work.

Full design doc: documents/gtfs-optimize-ingestion.md.

Measured impact

Full GtfsSqlJs.fromZipData() wall time, 5 runs after warmup, median:

Feed Before After Δ
ASTUCE Rouen (5 MB zip / 34 MB uncompressed / ~430k stop_times) 2647 ms 1669 ms −37%
Car Jaune (1 MB zip / 4.7 MB uncompressed) 312 ms 188 ms −40%

Benchmark scripts (scripts/bench-pragmas.ts, scripts/bench-ingest.ts, scripts/bench-after.ts) are included so the wins are reproducible on other feeds / machines.

Behaviour changes (CHANGELOG)

  • ProgressInfo.totalRows semantics: exact → estimate. Consumers using it as a post-ingest row count should use SELECT COUNT(*) instead.
  • locking_mode = EXCLUSIVE no longer set during ingestion. No known consumer was relying on this.

Public API signatures are unchanged.

Test plan

  • npm run typecheck passes
  • npm run lint passes
  • npm test — all 139 tests pass, including a new tests/progress-callback.test.ts that guards totalRows estimate accuracy, percentComplete bounds, and the terminal complete event.
  • npx tsx scripts/bench-after.ts /tmp/gtfs-bench/astuce.zip — median 1669 ms (under 1700 ms budget from the plan).
  • npx tsx scripts/bench-after.ts /tmp/gtfs-bench/car-jaune.zip — median 188 ms.
  • Manual smoke test on a very large feed (e.g. Île-de-France Mobilités) to confirm wins scale and no memory regression.

🤖 Generated with Claude Code

Three code-level wins in the loader, measured on the ASTUCE Rouen feed
(~430k stop_times rows, 34 MB uncompressed):

1. Count CSV rows via newline scan for progress totals instead of fully
   parsing every file twice. totalRows is now documented as an estimate.
2. Parse CSVs with papaparse header:false and bind values by pre-computed
   column index — no per-row { col: value } object allocation.
3. Prepare a single row-sized INSERT per table and reuse it with
   stmt.run(rowVals), instead of preparing a fresh multi-row INSERT per
   1000-row batch.

Also dropped the bulk-load PRAGMA block (synchronous=OFF,
journal_mode=MEMORY, temp_store=MEMORY, cache_size=-64000,
locking_mode=EXCLUSIVE) and its post-ingest reset. Benchmarked effect
on sql.js is within noise (≤1%), and their removal unblocks upcoming
pluggable-adapter work.

Measured:
- ASTUCE: 2647 → 1669 ms median (−37%)
- Car Jaune: 312 → 188 ms median (−40%)

See documents/gtfs-optimize-ingestion.md for the full plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chtitux
chtitux merged commit 1eff229 into main Apr 20, 2026
3 checks passed
chtitux added a commit that referenced this pull request Apr 20, 2026
…test

Two updates:

1. PRAGMAs section updated to reflect PR #42 as merged/verified
   (grep -ri PRAGMA src/ returns zero matches). Previously the doc
   framed PRAGMA removal as a future prerequisite; now it is done.

2. Testing strategy gains an end-to-end test on BetterSqlite3Adapter.
   better-sqlite3 is a pure Node install (no simulator, no WASM
   toolchain) so it can run in the same vitest invocation as the
   sql.js tests, giving CI coverage on at least one non-sql.js
   adapter without requiring RN-specific CI. Op-sqlite and
   expo-sqlite remain manual/local.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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