Goal
GET /api/world/ships is polled by the frontend every 5s and returns every ship in the world without any pagination, viewport, or spatial bound. Fine for v1 (one grid, a handful of players, ~no rows yet) — but the payload and query cost both grow linearly with the entire player base, and the polling cadence means we pay that cost continuously.
Raised by CodeRabbit on PR #37 (the v2 of ShipRepository.findAllByOrderByCreatedAtAsc() + the WorldController#listShips endpoint).
Scope
- Bounded query options (pick one when we get here):
- Viewport / region filter:
findByXBetweenAndYBetween(...) driven by the client's current camera bounds. Best for "render what I see" but adds API surface and requires the client to send its viewport.
- Pagination via
Pageable: simpler, but doesn't actually solve the "render every player on the same canvas" requirement — clients still need all of them.
- Tile/sector partitioning: precompute which sector each ship is in, query by sector(s). Adds schema (likely a generated column or migration) but the query stays cheap as the world grows.
- DB index on
ship(created_at) to back the current ORDER BY created_at so it doesn't sort over the full table.
- Reconsider whether
/api/world/ships is the right shape. Maybe it splits into GET /api/world/ships?in=<x1,y1,x2,y2> (viewport) once the client owns its camera bounds.
Out of scope
- Doing this now. v1 has ~tens of ships at most; the unbounded fetch is cheap.
- WebSocket / push-based updates (a separate, bigger conversation).
Trigger
Open this when:
- We hit ~hundreds of total ships, OR
- The
/api/world/ships query starts showing up in slow-query logs, OR
- We start designing a bigger map (>100×100) where viewport semantics actually matter
References
Goal
GET /api/world/shipsis polled by the frontend every 5s and returns every ship in the world without any pagination, viewport, or spatial bound. Fine for v1 (one grid, a handful of players, ~no rows yet) — but the payload and query cost both grow linearly with the entire player base, and the polling cadence means we pay that cost continuously.Raised by CodeRabbit on PR #37 (the v2 of
ShipRepository.findAllByOrderByCreatedAtAsc()+ theWorldController#listShipsendpoint).Scope
findByXBetweenAndYBetween(...)driven by the client's current camera bounds. Best for "render what I see" but adds API surface and requires the client to send its viewport.Pageable: simpler, but doesn't actually solve the "render every player on the same canvas" requirement — clients still need all of them.ship(created_at)to back the currentORDER BY created_atso it doesn't sort over the full table./api/world/shipsis the right shape. Maybe it splits intoGET /api/world/ships?in=<x1,y1,x2,y2>(viewport) once the client owns its camera bounds.Out of scope
Trigger
Open this when:
/api/world/shipsquery starts showing up in slow-query logs, ORReferences
ShipRepository.javalines 14-19