Two targeted fixes based on the 2026-04-22 06:26+ production log run.
(1) RS2 disk writes: one batched transaction instead of N sequential REPLACE INTOs
Every logout [perf-logout] line showed the same pattern:
core=72ms backpacks=6ms ss=5ms rs2=523ms total=606ms
core=56ms backpacks=4ms ss=1ms rs2=391ms total=452ms
core=77ms backpacks=3ms ss=1ms rs2=409ms total=490ms
RS2 dominated the save path. Backpacks + SS were already batched via
saveBackpackSnapshots since Phase 7, but saveRS2DisksByLevel still
looped saveStorageContents (one REPLACE INTO per disk).
Fix: collect every disk's NBT into Map<UUID, CompoundTag> first, then
delegate to saveBackpackSnapshots (same table, same batched transaction
path with per-entry fallback on failure). Expected ~10x reduction in
rs2= duration for players with 3-4 disks.
(2) Ghost-session force-claim: absolute 15s cap instead of stale-heartbeat-only
Fresh field logs showed the exact scenario Phase 10 left unsolved:
06:26:43 RESTORE started for 95d0db86
06:27:44 RESTORE completed in 60627ms (full poll timeout)
06:58:16 RESTORE started for 5d582bbc
06:59:17 RESTORE completed in 61630ms (full poll timeout)
The peer's heartbeat was always fresh (age 2-28s, well under the 60s
stale threshold), so Phase 11's 'only force-claim if stale' gate never
fired — the loop ran the full 120 attempts. Meanwhile [perf-logout]
proves real saves commit in < 1s, so a peer that hasn't flushed after
15s is a ghost session (player disconnected uncleanly, flag stuck at
online=1). Waiting another 45s for a save that isn't coming is pure
UX cost.
Fix: after join_peer_alive_max_wait_seconds (default raised from 5 to
15), force-claim unconditionally. Safe because:
- 15s is 15x the max observed save time — real saves are always
committed to DB by then.
- Phase 2's last_server guard already blocks any late write from
the ghost session (the guard logs [GUARD] on the peer's side).
- Phase 10 duplication scenario (force-claim before peer's async
save commits) can no longer happen with this safer threshold.
Peer-truly-stale short-circuit (heartbeat > 60s old) still triggers
instantly via the isPeerServerStale() check at the top of the loop —
only the 'peer alive but player ghost' path changed semantics.
|
||
|---|---|---|
| .github | ||
| compat-mods | ||
| gradle/wrapper | ||
| src/main | ||
| .gitattributes | ||
| .gitignore | ||
| build.gradle | ||
| CHANGELOG.md | ||
| docker-compose.yml | ||
| ERROR_LOG.md | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| LICENSE | ||
| README.md | ||
| settings.gradle | ||
| TEST_PROCEDURE_v2.1.5.html | ||
PlayerSync
PlayerSync is a Minecraft Forge mod that synchronizes player data across multiple servers using a MySQL backend. It allows players to maintain their inventory, equipment, experience, advancements, and more when moving between servers in a network.
Mod Support
Any other mods support is also possible.
Development Setup
Database Setup (Docker)
A docker-compose.yml file is provided for easily setting up a MariaDB database instance for development testing.
- Make sure Docker is installed.
- Inside your work directory run:
This will download the MariaDB image (if not already present) and start a database container in the background.docker compose up -d - Stoppinng the Database
docker compose down
Data Persistence: The database uses a Docker volume, ensuring your data persists even if you stop and restart the containers.
Database Management Tool
The docker-compose.yml also includes an Adminer service, a lightweight database management tool.
- Access Adminer in your web browser at http://localhost:8080.
- Log in using the server with
- username:
playersync - database:
playersync - password: see docker-compose.yml
- username:
For debugging purposes, you can enable use_legacy_serialization to have readable database fields. This can cause crashes and unintended side-effects. Do not enable this on a production server if not absolutely necessary!
Running the Mod
The project uses Gradle for building and running. Use the provided Gradle wrapper (gradlew for Linux/macOS, gradlew.bat for Windows).
- Make sure that the MySQL database you configured is running.
- Run the Server
or on Windows:./gradlew runServer
This task compiles the mod and starts a dedicated Minecraft server instance with the mod loaded in the.\gradlew.bat runServerrundirectory. - Run the Client
or on Windows:./gradlew runClient.\gradlew.bat runClient