Back to Research
Systems Oct 12, 2025 5 min read

Cutting Realtime Match Reads in the Queue

How an expensive lobby fetch turned two players queuing into thousands of reads, and how moving selection back to the server fixed the cost shape.

AlgoArena Research

Cutting Realtime Match Reads in the Queue

1,106+
Before
106
After
98%
With refreshes

Evidence Shape

The queue only needed identity, heartbeat, and server-side problem selection. Bulk problem reads belonged nowhere near the lobby.

Client problem fetch500 reads per lobby visit
Notificationslistener startup
Server match creationbucket plus selected problem
01Find the read spike
02Separate lobby state from problem selection
03Keep bucket selection server-side
Before: 1,106+ (minimum reads for two people opening the lobby) | After: 106 (estimated reads after removing the bulk problem fetch) | With refreshes: 98% (reduction from the observed failure pattern)

This note came from a blunt question: why could two people entering a lobby create thousands of Firestore reads?


The matchmaker itself was not the problem. The server already selected a problem through compact problemIndex buckets. The noisy part was the lobby page, which was still calling a client-side problem selection hook and fetching a large batch of problems even though the lobby never displayed them.


What failed


The old lobby path mixed two jobs:


  • show the user that they were waiting for a match
  • prepare a list of possible problems

  • Only the first job belonged in the lobby. The second job was already handled by the backend when a match was created.


    In the documented failure case, one lobby visit pulled about 500 problem documents plus notification listener reads. Two people queuing therefore had a minimum read shape of roughly 1,106 reads before refreshes, tabs, or listener reconnects made it worse.


    What changed


    The fix was small because the architecture already had the right server-side primitive:


  • remove the expensive useProblemSelection(true) call from the lobby
  • leave problem picking to the match creation path
  • keep problemIndex buckets as ID lists instead of storing full problem data

  • That kept the lobby focused on queue state and pushed problem selection back to the place that had enough context to do it cheaply.


    Result


    The estimate fell from 1,106+ reads for a clean two-player queue path to roughly 106 reads. In the observed refresh-heavy case, the model dropped from about 7,200 reads to about 156.


    The lesson is not "never use realtime listeners." The lesson is that realtime screens should subscribe to the smallest thing that can change the UI. A waiting room does not need a problem library.