Multi-purpose microservice for all things performance, made for Akatsuki.
The service runs different components based on the APP_COMPONENT environment variable:
| Component | Description |
|---|---|
api |
REST API server for PP calculation (default, port 8665) |
deploy |
Production PP recalculation tool |
processor |
AMQP consumer for rework recalculation queue |
mass_recalc |
CLI to queue all users for rework recalculation |
individual_recalc |
CLI to queue a single user for rework recalculation |
cargo build --releaseAPP_COMPONENT=api cargo run --releaseCopy .env.example to .env and configure:
# Service
APP_COMPONENT=api
API_HOST=127.0.0.1
API_PORT=8665
# Database
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USERNAME=root
DATABASE_PASSWORD=password
DATABASE_NAME=akatsuki
DATABASE_POOL_MAX_SIZE=16
# AMQP (RabbitMQ) - required for processor/mass_recalc/individual_recalc
AMQP_HOST=localhost
AMQP_PORT=5672
AMQP_USERNAME=guest
AMQP_PASSWORD=guest
AMQP_POOL_MAX_SIZE=10
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DATABASE=0
# External Services
BEATMAPS_SERVICE_BASE_URL=http://localhost:8080
SERVICE_READINESS_TIMEOUT=60The deploy component recalculates PP for all scores and updates user statistics. This uses the same PP calculation algorithm as live score submissions.
See PRACTICAL_RECALC_GUIDE.md for step-by-step instructions on running a recalculation in production.
| Mode | Value | Relax Bit |
|---|---|---|
| osu!std | 0 | 0 (vanilla), 1 (relax), 2 (autopilot) |
| osu!taiko | 1 | 0 (vanilla), 1 (relax) |
| osu!catch | 2 | 0 (vanilla), 1 (relax) |
| osu!mania | 3 | 0 (vanilla) |
APP_COMPONENT=deploy cargo run --releaseYou'll be prompted for:
- Modes: Comma-separated list (e.g.,
0,1,2,3) - Relax bits: Comma-separated list (e.g.,
0,1,2) - Total PP recalc only:
yto skip individual score recalc,nto recalc scores first - Total PP:
yto recalculate user total PP and leaderboards - Mod value recalc only: Filter to scores with specific mods
- Neq mod value recalc only: Filter to scores WITHOUT specific mods
- Mapper recalc only: Filter to beatmaps by mapper name
- Map recalc only: Filter to specific beatmap IDs
For automated/scripted recalculation:
# Full recalculation across all modes
DEPLOY_MODES=0,1,2,3 \
DEPLOY_RELAX_BITS=0,1,2 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
APP_COMPONENT=deploy cargo run --release| Variable | Description | Example |
|---|---|---|
DEPLOY_MODES |
Comma-separated game modes | 0,1,2,3 |
DEPLOY_RELAX_BITS |
Comma-separated relax bits | 0,1,2 |
DEPLOY_TOTAL_PP_ONLY |
Set to 1 to skip Phase 1 (score PP recalc) |
0 |
DEPLOY_TOTAL_PP |
Set to 1 to run Phase 2 (user total PP aggregation) |
1 |
DEPLOY_PREVIEW |
Set to 1 to log matching score/beatmap/user counts without updates |
1 |
DEPLOY_DRY_RUN |
Set to 1 to calculate and log each write without performing it; combined score+total dry-runs are capped at 100k planned score PP values |
1 |
DEPLOY_MODS_FILTER |
Only scores WITH these mods (bitmask) | 64 (DT) |
DEPLOY_NEQ_MODS_FILTER |
Only scores WITHOUT these mods | 64 |
DEPLOY_MAPPER_FILTER |
Filter by mapper name (fuzzy) | Sotarks |
DEPLOY_MAP_FILTER |
Comma-separated beatmap IDs | 123,456,789 |
DEPLOY_PP_ZERO |
Set to 1 to only repair scores where pp = 0 |
1 |
DEPLOY_AFTER_DATE |
Only repair scores submitted on/after a UTC date | 2026-07-01 |
DEPLOY_AFTER_TIME |
Only repair scores submitted on/after a Unix timestamp | 1782864000 |
Full server recalculation (all modes, all variants):
DEPLOY_MODES=0,1,2,3 \
DEPLOY_RELAX_BITS=0,1,2 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
APP_COMPONENT=deploy cargo run --releaseRecalculate only osu!std (vanilla + relax + autopilot):
DEPLOY_MODES=0 \
DEPLOY_RELAX_BITS=0,1,2 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
APP_COMPONENT=deploy cargo run --releaseRecalculate only DT scores (all modes):
DEPLOY_MODES=0,1,2,3 \
DEPLOY_RELAX_BITS=0,1,2 \
DEPLOY_MODS_FILTER=64 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
APP_COMPONENT=deploy cargo run --releaseRe-aggregate user totals only (skip Phase 1, use existing score PP):
DEPLOY_MODES=0,1,2,3 \
DEPLOY_RELAX_BITS=0,1,2 \
DEPLOY_TOTAL_PP_ONLY=1 \
DEPLOY_TOTAL_PP=1 \
APP_COMPONENT=deploy cargo run --releaseRecalculate specific beatmaps:
DEPLOY_MODES=0 \
DEPLOY_RELAX_BITS=0,1 \
DEPLOY_MAP_FILTER=75,129891,1816113 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
APP_COMPONENT=deploy cargo run --releaseRepair recent 0pp relax scores:
DEPLOY_MODES=0,1,2 \
DEPLOY_RELAX_BITS=1 \
DEPLOY_PP_ZERO=1 \
DEPLOY_AFTER_DATE=2026-07-01 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
APP_COMPONENT=deploy cargo run --releasePreview recent 0pp relax score repair counts without updates:
DEPLOY_MODES=0,1,2 \
DEPLOY_RELAX_BITS=1 \
DEPLOY_PP_ZERO=1 \
DEPLOY_AFTER_DATE=2026-07-01 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
DEPLOY_PREVIEW=1 \
APP_COMPONENT=deploy cargo run --releaseDry-run recent 0pp relax score repair and log skipped writes:
DEPLOY_MODES=0,1,2 \
DEPLOY_RELAX_BITS=1 \
DEPLOY_PP_ZERO=1 \
DEPLOY_AFTER_DATE=2026-07-01 \
DEPLOY_TOTAL_PP_ONLY=0 \
DEPLOY_TOTAL_PP=1 \
DEPLOY_DRY_RUN=1 \
APP_COMPONENT=deploy cargo run --releaseThe deploy component runs in two phases, controlled by environment variables:
Phase 1: Score PP Recalculation (runs unless DEPLOY_TOTAL_PP_ONLY=1)
- Fetches the
.osufile for each beatmap from beatmaps-service - Re-runs the PP calculation algorithm on each individual score
- Updates the
ppcolumn inscores,scores_relax, orscores_aptables - This is the computationally expensive phase
Phase 2: User Total PP Aggregation (runs if DEPLOY_TOTAL_PP=1)
- Uses the existing
ppvalues already in the score tables (does NOT recalculate them) - Recalculates which score is "best" per beatmap (
completed=3vscompleted=2) - Aggregates each user's top 100 scores using the weighted formula
- Updates
user_stats.pp - Updates Redis leaderboards (global and country)
- Publishes
peppy:update_cached_statsfor each user
Common configurations:
| TOTAL_PP_ONLY | TOTAL_PP | What happens |
|---|---|---|
0 |
1 |
Full recalc: Phase 1 then Phase 2 |
0 |
0 |
Score PP only: Phase 1 only (no user totals) |
1 |
1 |
Aggregation only: Phase 2 only (uses existing score PP) |
When to use aggregation only (DEPLOY_TOTAL_PP_ONLY=1):
- A previous recalc crashed during Phase 2 and you need to resume
- You fixed a bug in the aggregation/leaderboard logic (not the PP algorithm)
- You want to rebuild Redis leaderboards without changing score PP values
- Beatmaps are processed with 10 concurrent tasks
- Users are processed in batches of 1000 with 100 concurrent tasks
- Progress is logged every 100 beatmaps/users
The mass_recalc and processor components are for testing experimental PP algorithms (reworks). These use different calculation formulas than live score submission.
The processor supports the following rework IDs, all for osu!std relax (mode = 0, rx = 1). The names below identify their calculator implementations in src/processor/mod.rs; display names come from the reworks database table.
| ID | Name | Description |
|---|---|---|
| 19 | improved_miss_penalty | Improved miss penalty formula |
| 21 | flashlight_hotfix | Flashlight mod adjustments |
| 22 | remove_accuracy_pp | Removes accuracy PP component |
| 23 | stream_nerf_speed_value | Nerfs stream speed value |
| 24 | remove_manual_adjustments | Removes manual map adjustments |
| 25 | fix_inconsistent_powers | Fixes inconsistent power calculations |
| 26 | aim_accuracy_fix | Aim and accuracy fixes |
| 27 | improved_miss_penalty_and_acc_rework | Combined miss penalty + accuracy rework |
| 28 | everything_at_once | All experimental changes combined |
| 29 | kippy_attempt | Kippy's experimental relax PP formula |
| 30 | merge_test | Merge Test formula from the merge-test rosu-pp fork |
| 31 | relax_pp_update | Relax calculation from the relax-pp-update rosu-pp branch |
Recalculation also requires a matching entry in the reworks table. IDs without an implemented calculator are rejected by the API and CLI enqueue paths.
-
Start the processor (listens to AMQP queue):
APP_COMPONENT=processor cargo run --release
-
Queue all users for a rework:
APP_COMPONENT=mass_recalc cargo run --release # Enter the rework ID when promptedOr via environment:
MASS_RECALC_REWORK_ID=19 APP_COMPONENT=mass_recalc cargo run --release
-
Queue a single user:
APP_COMPONENT=individual_recalc cargo run --release # Enter user ID and rework ID when prompted
rework_scores- Individual score PP calculationsrework_stats- User total PP for each reworkrework_queue- Processing queue status- Redis:
rework:leaderboard:{rework_id}- Rework leaderboards
Calculate PP for one or more scores.
Request:
[
{
"beatmap_id": 75,
"beatmap_md5": "a5b99395a42bd55bc5eb1d2411cbdf8b",
"mode": 0,
"mods": 0,
"max_combo": 314,
"accuracy": 98.5,
"miss_count": 1
}
]Or with hit counts instead of accuracy:
[
{
"beatmap_id": 75,
"beatmap_md5": "a5b99395a42bd55bc5eb1d2411cbdf8b",
"mode": 0,
"mods": 0,
"max_combo": 314,
"count_300": 200,
"count_100": 10,
"count_50": 2,
"miss_count": 1
}
]Response:
[
{
"stars": 5.23,
"pp": 234.56,
"ar": 9.0,
"od": 8.0,
"max_combo": 315
}
]- osu!std Relax: Uses
akatsuki_pp_rs::osu_2019::OsuPP(2019 algorithm) - All other modes: Uses
rosu-ppviabeatmap.performance()
Total PP formula:
Total PP = SUM(score.pp * 0.95^index) + bonus_pp
Bonus PP = 416.6667 * (1 - 0.995^score_count)
Where index is the score's position when sorted by PP descending (top 100 scores only).