AlgoArena Team6 min readCutting 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.
This note came from a blunt question: why could two people entering a lobby create thousands of database 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.
The problem was easy to miss because each individual piece looked defensible. A lobby needs a realtime listener. Matchmaking needs a problem. The problem library exists. But combining those facts on the client made the waiting room pay for work that belonged to match creation.
The symptom was not just a high number. It was the shape of the number. A cheap screen had become proportional to problem-library reads, which meant product growth would make a waiting room more expensive even if the UI did not change.
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
problemIndexbuckets 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.
The most useful part of the fix was conceptual. The lobby does not need to know the candidate problem set; it only needs to know whether a match exists, who is waiting, and whether the user's heartbeat is current. The server can pick a problem when it has both players and the matchmaking bucket.
That gives the system a cleaner ownership boundary:
- the client subscribes to queue state
- the server owns problem selection
- the problem index stays compact
- the match document receives the selected problem
Each layer now pays for the information it actually displays or decides.
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.
What the figure shows
The figure compares the dominant read sources instead of showing a timeline. That is the important evidence: the client problem fetch was the cost driver. Notifications and server match creation were comparatively small.
This kind of figure is useful because it prevents a vague optimization story. The team did not "make the database cheaper" in general. It removed one product-layer mistake: a realtime page fetching data it did not render.
Operational boundary
This note uses estimates from the documented failure path, not a claim that every lobby visit now costs exactly 106 reads. Reconnects, browser tabs, listeners, and product changes can move the number.
The durable conclusion is architectural: keep high-cardinality problem data out of the waiting room, and keep realtime subscriptions tied to visible state.
Method
This note treats the product behavior as the unit of analysis. The useful evidence is the artifact the platform can preserve: the counters, traces, replay rules, validation path, or scoring dimensions that a reviewer can inspect after the session. That keeps the claim grounded in observable work rather than a broad statement about AI, practice, or assessment quality.
Limitations
This is not a universal benchmark and it should not be read as one. The numbers and flows here describe AlgoArena's current product surface, current data shape, and current operating constraints. Any future classifier, scoring model, or automated review workflow still needs calibration, false-positive analysis, and human review before it should carry higher-stakes decisions.
Product implication
The product implication is the same across the research archive: make the work easier to inspect without making the experience hostile. Good research packaging should give learners, teachers, and hiring teams a sharper explanation of what happened, while leaving the interface itself calm enough to use repeatedly.
What would make this stronger
The next version should pair this note with more longitudinal evidence: repeated sessions, clearer cohort slices, and failure cases where the current heuristic is not enough. Publishing those limits is part of the credibility. A research surface should not only show the clean path; it should show what would change our mind.