Skip to content

Commit 884f2b6

Browse files
authored
Split non preserved user retrieval into smaller batches (#367)
Attempting to run on a large number of users can cause OOM due to retrieval overheads. This adds bog-standard batching to fix this. Has been tested on production (dry-run) to work as expected.
1 parent bf2f807 commit 884f2b6

1 file changed

Lines changed: 29 additions & 9 deletions

File tree

osu.Server.Queues.ScoreStatisticsProcessor/Commands/Maintenance/MarkNonPreservedScoresCommand.cs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,41 @@ public async Task<int> OnExecuteAsync(CancellationToken cancellationToken)
5959

6060
using var db = await DatabaseAccess.GetConnectionAsync(cancellationToken);
6161

62-
Console.WriteLine("Fetching all users...");
63-
int[] userIds = (await db.QueryAsync<int>($"SELECT `user_id` FROM {databaseInfo.UserStatsTable} WHERE {Where}")).ToArray();
64-
Console.WriteLine($"Fetched {userIds.Length} users");
62+
const int users_per_fetch = 1000;
6563

66-
for (int i = 0; i < userIds.Length; i++)
64+
int lastUserId = 0;
65+
int totalUsersProcessed = 0;
66+
67+
while (!cancellationToken.IsCancellationRequested)
6768
{
68-
await processUser(db, userIds[i], cancellationToken);
69+
Console.WriteLine("Fetching more users...");
70+
71+
int[] userIds =
72+
(await db.QueryAsync<int>($"SELECT `user_id` FROM {databaseInfo.UserStatsTable} WHERE {Where} AND user_id > {lastUserId} ORDER BY user_id LIMIT {users_per_fetch}")).ToArray();
73+
74+
if (userIds.Length == 0)
75+
break;
76+
77+
Console.WriteLine($"Fetched {userIds.Length} users");
6978

70-
if (i > 0 && i % 100 == 0)
71-
Console.WriteLine($"Processed {i:N0} of {userIds.Length:N0} users ({totalMarked:N0} marked)");
79+
for (int i = 0; i < userIds.Length; i++)
80+
{
81+
int userId = userIds[i];
82+
83+
await processUser(db, userId, cancellationToken);
84+
85+
if (i > 0 && i % 100 == 0)
86+
Console.WriteLine($"Processed {i:N0} of {userIds.Length:N0} users ({totalMarked:N0} marked)");
87+
88+
lastUserId = userId;
89+
90+
Interlocked.Increment(ref totalUsersProcessed);
91+
}
7292
}
7393

7494
Console.WriteLine();
7595
Console.WriteLine($"Finished in {stopwatch.Elapsed.TotalSeconds:N0} s!");
76-
Console.WriteLine($"Processed {userIds.Length} users");
96+
Console.WriteLine($"Processed {totalUsersProcessed} users");
7797
Console.WriteLine($"{totalMarked:N0} marked for deletion");
7898

7999
return 0;
@@ -108,7 +128,7 @@ private async Task processUser(MySqlConnection db, int userId, CancellationToken
108128
//
109129
// This could for instance, be a case of a user pinning a score on an unranked beatmap, the unpinning
110130
// at a future point in time. In the future we may want to consider cleaning these up, but the overhead
111-
// of doing this with the *current structure* of this commmand loop is too high to be worthwhile.
131+
// of doing this with the *current structure* of this command loop is too high to be worthwhile.
112132
IEnumerable<SoloScore> scores = await db.QueryAsync<SoloScore>(new CommandDefinition(
113133
"""
114134
WITH beatmaps AS (

0 commit comments

Comments
 (0)