Skip to content

fix: competitive/ranked setupStats memory leak (unbounded row arrays) - #2929

Open
vinxp97 wants to merge 1 commit into
UltiMafia:masterfrom
vinxp97:fix/competitive-setupstats-memory-leak
Open

fix: competitive/ranked setupStats memory leak (unbounded row arrays)#2929
vinxp97 wants to merge 1 commit into
UltiMafia:masterfrom
vinxp97:fix/competitive-setupstats-memory-leak

Conversation

@vinxp97

@vinxp97 vinxp97 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Competitive seasons (and ranked) were pushing unbounded per-game stats onto SetupVersion.setupStats:

  • alignmentRows — one row per faction per finished clean game
  • roleRows — one row per player role per game
  • gameLengthRows — one row per game

Popular competitive setups concentrate thousands of games onto a few setup versions, creating multi-MB documents. Every competitive/ranked game end then loads the full setupStats into memory for fortune calculation (adjustSkillRatingsalignmentRowsToWinRateMap), and setup pages do the same for granular winrate UI. That matches reports of growing process memory under competitive play.

Fix

  1. Write fixed-size aggregates instead of $pushing rows:
    • setupStats.alignmentAgg[faction][gameType] = { wins, games }
    • setupStats.roleAgg[roleKey][gameType] = { wins, games }
    • setupStats.lengthAgg[gameType] = { sumMs, count }
  2. Read path merges aggregates with legacy *Rows during transition so history is not lost before migration.
  3. Postgame cleanup calls events.removeAllListeners() so role/card listeners cannot retain finished games after they leave the process games map.
  4. Migration migrations/compactSetupStatsRows.js folds existing row arrays into aggregates and clears them (run on prod after deploy).

Deploy notes

After merging, run once against production Mongo:

node migrations/compactSetupStatsRows.js --dry-run
node migrations/compactSetupStatsRows.js

Optional: --min-rows 100 to only touch large docs first.

Test plan

  • Finish a ranked or competitive game; confirm fortune still pays
  • Confirm setup page granular winrates still show for a setup that has history
  • After migration dry-run, verify largest setupStats docs shrink when run for real
  • Watch games process RSS over a competitive evening — should stop climbing with setupStats size

Competitive/ranked games pushed per-game alignmentRows, roleRows, and
gameLengthRows onto SetupVersion.setupStats. Heavy competitive setups
grew multi-MB docs that were fully loaded on every game end for fortune
payouts and on setup page views, driving process memory growth.

Switch writes to fixed-size aggregates (alignmentAgg/roleAgg/lengthAgg),
read aggregates with legacy row fallback (merged during transition),
clear game EventEmitter listeners on postgame cleanup, and add
migrations/compactSetupStatsRows.js to fold existing rows into aggregates
and drop the arrays in production.
@vinxp97 vinxp97 changed the title fix: stop unbounded setupStats growth (competitive memory leak) fix: competitive/ranked setupStats memory leak (unbounded row arrays) Aug 10, 2026
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