perf(proxy): trim routes from stops/location response - #167
Draft
ai-tiro wants to merge 1 commit into
Draft
Conversation
The /api/v3/stops/location response used by the Nearby map carried a full `routes` array on every stop (each route with route_name, route_gtfs_id, route_number and a geopath:[] placeholder). For a typical Melbourne CBD fetch (max_results=100, max_distance=500) this is the dominant bytes-on-wire cost: ~39 KB, of which the pins use none of it. The mobile client deserialises the nearby response into StopDto, which declares only stop_id/stop_name/stop_suburb/route_type/stop_latitude/ stop_longitude and has no `routes` field at all — kotlinx.serialization silently drops it via ignoreUnknownKeys. The Nearby bottom sheet fetches routes separately via the per-stop detail endpoint, not from this response. So `routes` here is pure dead weight on the wire. PTV's v3 API has no query param to omit routes (the stops/location op only exposes route_types/max_results/max_distance/stop_disruptions), so the only lever is to strip it in the proxy. trimStopsLocation decodes the envelope and stops array as raw messages, deletes only the `routes` key from each stop, and leaves every other field byte-for-byte intact. On any structural surprise it returns the body unchanged and ServeHTTP falls back to a verbatim copy, so the endpoint can never be broken by an unexpected upstream shape. The trim is scoped to /v3/stops/location/ only; all other endpoints keep their existing io.Copy passthrough. Measured on the real upstream body: 38978 -> 7535 bytes (80.7% smaller). Co-Authored-By: ai-tiro <ai-tiro@jfx.ac> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #163
What
The
/api/v3/stops/locationresponse used by the Nearby map carried a full per-stoproutesarray. The proxy now strips that array from the response for that endpoint only, before returning it to the client. All other endpoints keep their existing verbatimio.Copypassthrough.trimStopsLocationdecodes the envelope and thestopsarray as raw JSON messages, deletes only therouteskey from each stop object, and leaves every other field — top-level (disruptions,status) and per-stop (stop_id,stop_name,stop_latitude,stop_longitude,route_type,stop_suburb, …) — byte-for-byte intact. On any structural surprise (not an object,stopsnot an array, a non-object stop, marshal error) it returns the body unchanged andServeHTTPfalls back to a verbatim copy, so the endpoint can never be broken by an unexpected upstream shape.Measured size win
Real upstream body, Melbourne CBD (
stops/location/-37.8136,144.9631?max_results=100&max_distance=500):routes)Verified by feeding the captured live response through
trimStopsLocationdirectly. This matches the 38–41 KB figure in the issue and removes the field that dominates bytes-on-wire (departures are ~120 B–2 KB).What I confirmed about client field usage (the critical safety check)
The mobile client does not consume
routesfrom this endpoint:StopDto(mobile/core/network/.../model/StopDto.kt), which declares onlystop_id,stop_name,stop_suburb,route_type,stop_latitude,stop_longitude— there is noroutesfield on the DTO at all.kotlinx.serializationis configured withignoreUnknownKeys, sorouteswas already being silently dropped on-device.StopDto.toDomain()reads only those six fields.stopDetailRepository.getStopDetail(...)), not from this response.So trimming
routeshere is invisible to every current consumer.What I tried that didn't work / alternatives considered
docs/ptv-timetable-api-v3-swagger.json: thestops/locationop only exposesroute_types,max_results,max_distance,stop_disruptions— noexpand/include/exclude lever. PTV always returnsroutes. So a server-side strip is the only option.route_types=0,1,2,3(comma-joined) to the live proxy returns an emptystops:[]; PTV wants repeatedroute_types=params. Not in scope here, just noting it tripped up my first measurement.Testing
cd backend && go build ./... && go vet ./... && go test ./...— all pass.TestHandler_StopsLocationTrimsRoutes— assertsroutes/geopath/route_gtfs_idare gone, all pin fields +disruptions/statussurvive, body is valid JSON and smaller.TestHandler_NonStopsLocationPassthroughKeepsRoutes— astops/{id}/route_type/{type}-style body that legitimately carriesroutesis returned byte-for-byte unchanged, proving the trim is scoped.TestTrimStopsLocation_Fallbacks— malformed JSON, non-object top level, missingstops, non-arraystops, non-object stop, and no-routes-present all return the body untouched.Justification / caveats
/v3/stops/location/and only on a200response; everything else is untouched.🤖 Generated with Claude Code