-
Notifications
You must be signed in to change notification settings - Fork 15
Adjust mod multiplier recalculation processes to minimise number of required database writes #374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
ebd58a6
a5bc17d
28e2933
1186659
e0b8c10
3546b02
6676129
942ac75
324f74f
9b87267
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,7 +11,10 @@ | |||||||||||
| using JetBrains.Annotations; | ||||||||||||
| using McMaster.Extensions.CommandLineUtils; | ||||||||||||
| using MySqlConnector; | ||||||||||||
| using osu.Game.Database; | ||||||||||||
| using osu.Game.Rulesets.Scoring; | ||||||||||||
| using osu.Server.QueueProcessor; | ||||||||||||
| using osu.Server.Queues.ScoreStatisticsProcessor.Helpers; | ||||||||||||
| using osu.Server.Queues.ScoreStatisticsProcessor.Models; | ||||||||||||
|
|
||||||||||||
| namespace osu.Server.Queues.ScoreStatisticsProcessor.Commands.Maintenance | ||||||||||||
|
|
@@ -37,7 +40,9 @@ public class RecalculateModMultipliersCommand | |||||||||||
| public async Task<int> OnExecuteAsync(CancellationToken cancellationToken) | ||||||||||||
| { | ||||||||||||
| ulong lastId = StartId ?? 0; | ||||||||||||
| ulong updatedScores = 0; | ||||||||||||
|
|
||||||||||||
| ulong skipped = 0; | ||||||||||||
| ulong updated = 0; | ||||||||||||
|
|
||||||||||||
| using var conn = await DatabaseAccess.GetConnectionAsync(cancellationToken); | ||||||||||||
|
|
||||||||||||
|
|
@@ -78,36 +83,66 @@ public async Task<int> OnExecuteAsync(CancellationToken cancellationToken) | |||||||||||
|
|
||||||||||||
| foreach (var score in scoresWithMods) | ||||||||||||
| { | ||||||||||||
| score.beatmap = beatmapsById[score.beatmap_id]; | ||||||||||||
| var scoreInfo = score.ToScoreInfo(); | ||||||||||||
| string source = score.is_legacy_score ? "stable" : "lazer "; | ||||||||||||
|
|
||||||||||||
| if (scoreInfo.TotalScoreWithoutMods == 0 && scoreInfo.TotalScore != 0) | ||||||||||||
| if (!beatmapsById.TryGetValue(score.beatmap_id, out var beatmap)) | ||||||||||||
| { | ||||||||||||
| throw new InvalidOperationException($"Score with ID {score.id} has {scoreInfo.TotalScore} total score but {scoreInfo.TotalScoreWithoutMods} total score without mods. " | ||||||||||||
| + $"This is likely to indicate that {nameof(scoreInfo.TotalScoreWithoutMods)} was not correctly backpopulated on all scores " | ||||||||||||
| + "(or there is a process pushing new scores that was not updated to populate the field)."); | ||||||||||||
| Console.WriteLine($"[{score.id,11} {source}] Skipped due to missing beatmap"); | ||||||||||||
| skipped++; | ||||||||||||
| continue; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| double multiplier = 1; | ||||||||||||
| score.beatmap = beatmap; | ||||||||||||
| var scoreInfo = score.ToScoreInfo(); | ||||||||||||
| var difficultyInfo = beatmap.GetLegacyBeatmapConversionDifficultyInfo(); | ||||||||||||
| var ruleset = LegacyRulesetHelper.GetRulesetFromLegacyId(score.ruleset_id); | ||||||||||||
|
|
||||||||||||
| long oldTotalScore = score.total_score; | ||||||||||||
| long newTotalScore; | ||||||||||||
|
|
||||||||||||
| foreach (var mod in scoreInfo.Mods) | ||||||||||||
| multiplier *= mod.ScoreMultiplier; | ||||||||||||
| if (score.is_legacy_score) | ||||||||||||
| { | ||||||||||||
| var scoringAttributes = getScoringAttributesFor(score, conn)?.ToAttributes(); | ||||||||||||
|
|
||||||||||||
| if (scoringAttributes == null) | ||||||||||||
| { | ||||||||||||
| Console.WriteLine($"[{score.id,11} {source}] Skipped due to missing scoring attributes"); | ||||||||||||
| continue; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| StandardisedScoreMigrationTools.UpdateFromLegacy(scoreInfo, ruleset, difficultyInfo, scoringAttributes.Value); | ||||||||||||
| newTotalScore = scoreInfo.TotalScore; | ||||||||||||
| } | ||||||||||||
| else | ||||||||||||
| { | ||||||||||||
| if (scoreInfo.TotalScoreWithoutMods == 0 && scoreInfo.TotalScore != 0) | ||||||||||||
| { | ||||||||||||
| Console.WriteLine($"[{score.id,11} {source}] Skipped due to missing total score without mods"); | ||||||||||||
| continue; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| long newTotalScore = (long)Math.Round(scoreInfo.TotalScoreWithoutMods * multiplier); | ||||||||||||
| var multiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(scoreInfo.BeatmapInfo!.Difficulty)); | ||||||||||||
| double multiplier = multiplierCalculator.CalculateFor(scoreInfo.Mods); | ||||||||||||
|
|
||||||||||||
| if (newTotalScore == scoreInfo.TotalScore) | ||||||||||||
| newTotalScore = (long)Math.Round(scoreInfo.TotalScoreWithoutMods * multiplier); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if (newTotalScore == oldTotalScore) | ||||||||||||
| { | ||||||||||||
| Console.WriteLine($"[{score.id,11} {source}] Skipped due to no change in score"); | ||||||||||||
| continue; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| Console.WriteLine($"Updating score {score.id}. Without mods: {scoreInfo.TotalScoreWithoutMods}. With mods: {scoreInfo.TotalScore} (old) -> {newTotalScore} (new)"); | ||||||||||||
| Console.WriteLine($"[{score.id,11} {source}] Updating score: {oldTotalScore,8} (old) -> {newTotalScore,8} (new)"); | ||||||||||||
|
|
||||||||||||
| sqlBuffer.Append($@"UPDATE `scores` SET `total_score` = {newTotalScore} WHERE `id` = {score.id};"); | ||||||||||||
| elasticItems.Add(new ElasticQueuePusher.ElasticScoreItem { ScoreId = (long?)score.id }); | ||||||||||||
| updatedScores++; | ||||||||||||
| updated++; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| lastId += (ulong)BatchSize; | ||||||||||||
|
|
||||||||||||
| Console.WriteLine($"Processed up to {lastId - 1} ({updatedScores} updated)"); | ||||||||||||
| Console.WriteLine($"Processed up to {lastId - 1} ({updated} updated, {skipped} skipped)"); | ||||||||||||
|
|
||||||||||||
| flush(conn); | ||||||||||||
| } | ||||||||||||
|
|
@@ -117,6 +152,18 @@ public async Task<int> OnExecuteAsync(CancellationToken cancellationToken) | |||||||||||
| return 0; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static BeatmapScoringAttributes? getScoringAttributesFor(SoloScore score, MySqlConnection conn) | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we want to expose and use the cached path for this. osu-queue-score-statistics/osu.Server.Queues.ScoreStatisticsProcessor/Helpers/BatchInserter.cs Lines 348 to 352 in 1421bad
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered it. My primary concern was running out of memory because the aforementioned static dictionary is never cleared other than via I'm not even sure why this seemingly doesn't explode in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a finite number of ranked beatmaps in the mentioned case ( For our purposes here, I think we also consider unranked beatmaps, which may tip the scales.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There are presumably lazer scores set on unranked beatmaps, yes. I am not aware of any process culling them at this time. |
||||||||||||
| { | ||||||||||||
| BeatmapScoringAttributes? scoreAttributes = conn.QuerySingleOrDefault<BeatmapScoringAttributes>( | ||||||||||||
| "SELECT * FROM osu_beatmap_scoring_attribs WHERE beatmap_id = @BeatmapId AND mode = @RulesetId", new | ||||||||||||
| { | ||||||||||||
| BeatmapId = score.beatmap_id, | ||||||||||||
| RulesetId = score.ruleset_id, | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| return scoreAttributes; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void flush(MySqlConnection conn, bool force = false) | ||||||||||||
| { | ||||||||||||
| int bufferLength = sqlBuffer.Length; | ||||||||||||
|
|
||||||||||||
Uh oh!
There was an error while loading. Please reload this page.