Based on a fresh audit against the Arcadia V2 modpack (444 mods, including
Curios + Accessories + SophisticatedBackpacks/Storage + RS2 + Cosmetic
Armor Reworked). Three perf wins + two opportunistic fixes.
Perf
- Heartbeat period 10s -> 30s. Paired with the 60s staleness threshold
this keeps failure-detection latency unchanged while cutting 3x the
server_info UPDATE traffic per server.
- Per-player hash-skip for unchanged snapshots (SaveToFile + staggered
auto-save). computeSnapshotHash() rolls over inventory/equipment/
enderchest/effects/xp/health/food/mod-data; when an auto-save produces
the same hash as the last successful write, the BG task returns early
and no UPDATE hits MySQL. Idle-server reduction is >95%. Logout /
shutdown / death never use the skip and refresh the hash on success
so post-logout rejoin doesn't wrongly skip.
- Batched backpack + SS saves. saveBackpackSnapshots / saveSSSnapshots
now build one transaction via executeBatchTransaction instead of
N sequential REPLACE INTO calls. A player with 3 backpacks + 2
shulkers drops from 5 network round-trips to 1 per logout save.
Per-entry fallback preserved on transaction failure.
- Periodic-save tick short-circuits when the player list is empty —
no main-thread hop, no log line, no DB heartbeat on empty servers.
Compat notes (no code change needed)
- CosmeticArmours (modid=cosmeticarmoursmod) items are worn in vanilla
armor slots (Helmet / Chestplate / Leggings / Boots inner classes) —
already captured by the core armor[] serialization. No handler needed.
- CosmeticWeapons uses the same pattern via main hand / offhand — also
already covered by core inventory serialization.
Cleanup
- removePlayerLock now also clears the hash cache so a player who
fully logged out doesn't leave a stale hash behind.
Adds two new triggers that complement NeoForge's vanilla SaveToFile event:
PeriodicSaveService.java
- Dedicated single-thread daemon scheduler, started after server boot.
- Ticks every 'auto_save_interval_minutes' (config, default 10 min).
- On each tick: hops to main thread, snapshots every online synced
player via VanillaSync.snapshotAndQueueSave, async BG writes with full
P0 guard stack (pendingLogoutSaves + online=0 + bgLock tryLock).
- Set interval to 0 to disable.
VanillaSync.snapshotAndQueueSave(Player, String label)
- Extracted from onPlayerSaveToFile body; public entry point shared by
PeriodicSaveService, onPlayerChangeDimension, and the existing SaveToFile
event. Label flows into logs for traceability (SaveToFile / PERIODIC / DIMENSION).
VanillaSync.onPlayerChangeDimension
- New @SubscribeEvent on PlayerChangedDimensionEvent, gated by
'save_on_dimension_change' config (default false). Queues a full save
when a player teleports across dimensions, protecting against mid-
teleport crashes.
JdbcConfig
- Added AUTO_SAVE_INTERVAL_MINUTES (int, 0-1440, default 10)
- Added SAVE_ON_DIMENSION_CHANGE (bool, default false)
VanillaSync.onServerShutdown also stops PeriodicSaveService before the pool
close, same pattern as HeartbeatService.