MySQL Database Expansion
Cross-server synchronisation for economy balances, punishments, nicknames, and kit cooldowns using MySQL.
Overview
The MySQL Database Expansion connects multiple EssentialsC server instances through a shared MySQL database, keeping player data in sync across your network in near real-time. It hooks directly into EssentialsC's economy, punishment, nickname, and kit managers to push and pull changes at regular intervals.
Four synchronisation channels are provided out of the box:
| Channel | Sync Interval | Direction |
|---|---|---|
| Economy Balances | 1 second | Bidirectional |
| Network Punishments | 2 seconds | Bidirectional |
| Network Nicknames | Configurable (default 2s) | Bidirectional |
| Network Kit Cooldowns | On claim | Push on claim, pull on check |
server-id. If two servers share the same ID, sync conflicts will not be detected and data may be lost.
Installation
The MySQL expansion is a separate JAR that depends on EssentialsC being loaded first.
Install EssentialsC
Make sure EssentialsC is installed and running on every server in your network. The expansion requires EssentialsC's economy module to be enabled.
Create the MySQL database
Create a database and user on your MySQL server. The expansion will create its tables automatically on first connection - no manual schema setup is required.
Download the expansion JAR
Download MySQLDatabaseExpansion-x.x.x.jar from the GitHub Releases page.
Drop it into your plugins folder
Place the JAR into each server's /plugins directory alongside EssentialsC.
Configure your connection
Edit /plugins/EssentialsC-MySQLExpansion/config.yml with your MySQL credentials and a unique server-id for each server instance.
Restart the server
Restart all servers in your network. Check the console for the startup banner confirming each sync channel has initialised successfully.
Configuration
All settings live in /plugins/EssentialsC-MySQLExpansion/config.yml. A default file is generated on first run.
# Unique identifier for this server instance. # IMPORTANT: Each server in your network MUST have a different server-id. server-id: "server-1" mysql: host: "localhost" port: 3306 database: "essc_sync" username: "root" password: "" pool-size: 10 network-kits: enabled: true network-nicknames: enabled: true poll-interval-ticks: 40
| Key | Type | Default | Description |
|---|---|---|---|
server-id |
String | server-1 |
Unique identifier for this server. Must be different on every server in the network. |
mysql.host |
String | localhost |
MySQL server hostname or IP address. |
mysql.port |
Integer | 3306 |
MySQL server port. |
mysql.database |
String | essc_sync |
Database name for all sync tables. |
mysql.username |
String | root |
MySQL authentication username. |
mysql.password |
String | "" |
MySQL authentication password. |
mysql.pool-size |
Integer | 10 |
HikariCP connection pool size. For aggressive 1-second sync, use 10–20. |
network-kits.enabled |
Boolean | true |
Enable cross-server kit cooldown sync. |
network-nicknames.enabled |
Boolean | true |
Enable cross-server nickname sync. |
network-nicknames.poll-interval-ticks |
Integer | 40 |
Nickname poll interval in ticks (20 ticks = 1 second). |
/mysqlsync reload - no server restart needed.
Economy Sync
The economy sync engine runs a push and a pull cycle every 1 second, aggressive enough to keep balances consistent across the network even on busy servers.
Push Cycle
Every second, the push cycle iterates all players currently online on this server. For each player, it compares their current local balance to the last known balance stored in memory. If the balance has changed, it writes the new value to the economy_sync table in MySQL. Offline player balances that were modified locally are also pushed during this cycle.
Pull Cycle
Every second, the pull cycle queries the economy_sync table for rows that were updated since the last poll, excluding changes that originated from this server. For each changed player who is online on this server and not currently in a pending sync state, the local balance is updated to match the remote value.
Join & Quit Handling
When a player joins, the manager fetches their remote balance from MySQL. If a remote balance exists and differs from the local balance, the local balance is updated. If no remote record exists, the local balance is pushed to MySQL as the initial value. When a player quits, their final balance is pushed immediately.
pendingSync) are excluded from pull updates to prevent overwriting a balance that is about to change. This avoids race conditions during rapid balance updates.
Network Punishments
Bans, IP bans, and mutes are synchronised across all servers in the network. A punishment issued on any server is applied everywhere within 2 seconds.
How It Works
When a punishment is issued locally (via EssentialsC's punishment commands), the NetworkPunishmentSyncManager inserts a record into the network_punishments table. A background poll cycle runs every 2 seconds, fetching any punishments that were created or modified by other servers since the last poll. Each new remote punishment is applied locally:
- Bans - the player is banned locally and kicked if they are currently online on this server.
- IP bans - the IP address is banned locally and any matching online players are kicked.
- Mutes - the player is muted locally. An active mute blocks chat via
AsyncPlayerChatEvent.
Unban & Unmute
When a punishment is deactivated (unban, unmute), the deactivation is also pushed to MySQL and polled by other servers, which deactivate the punishment locally in turn.
On player login, the NetworkPunishmentListener checks for active bans and IP bans at AsyncPlayerPreLoginEvent (priority HIGHEST) and denies the connection with a formatted kick message if a punishment is active. Chat is intercepted at AsyncPlayerChatEvent for mute checks.
Network Nicknames
Player nicknames set on one server are propagated to all other servers in the network. The sync runs in both directions - local nickname changes are pushed immediately, and remote changes are pulled on a configurable poll interval.
Push
When a player sets or clears their nickname, the NetworkNicknameSyncManager is called by EssentialsC's nickname manager. The nickname (or null, for cleared) is upserted into the network_nicknames table along with the current timestamp and the server ID.
Pull
On the configured poll interval (default 40 ticks = 2 seconds), the manager iterates all online players and fetches the remote nickname from MySQL. If the remote nickname differs from the locally cached nickname, the local nickname is updated.
Conflict Resolution
Nickname conflicts use a last-writer-wins strategy based on the last_updated timestamp. An upsert only overwrites the existing value if the incoming timestamp is newer than the stored one, ensuring the most recent change always wins regardless of which server made it.
When a player joins any server, their nickname is also pulled immediately from the database and applied, so a nickname set on one server is visible on another as soon as the player logs in.
Network Kits
Kit cooldowns are shared across the entire network. A player who claims a kit on one server cannot claim it again on another server until the cooldown expires.
How It Works
Each kit claim records the claim timestamp in the network_kit_cooldowns table, keyed by player UUID and kit name. When EssentialsC checks whether a player can claim a kit, it queries the network table for the most recent claim timestamp. If the network cooldown is still active, the claim is denied even if the local cooldown has expired.
The cooldown value used is the maximum of the last claim time stored in the database and the local claim time, ensuring a player cannot bypass the cooldown by switching servers.
network-sync: true on the individual kit in kits.yml. Only kits with this option enabled will have their cooldowns synchronised across the network.
Commands
| Command | Description | Permission |
|---|---|---|
/mysqlsync status |
Shows the current server ID, last poll time, and poll interval for the economy sync engine. | esscmysql.admin |
/mysqlsync forcepush |
Immediately pushes the balance of every online player to MySQL. | esscmysql.admin |
/mysqlsync forcepull |
Immediately pulls the balance of every online player from MySQL. | esscmysql.admin |
/mysqlsync reload |
Reloads the config.yml file without restarting the server. |
esscmysql.admin |
Permissions
| Permission | Description | Default |
|---|---|---|
esscmysql.admin |
Access to all /mysqlsync sub-commands - status, forcepush, forcepull, and reload. |
Operators |
Database Schema
The expansion creates and manages four tables in the configured MySQL database. All tables use the InnoDB engine and are created automatically on the first connection.
economy_sync
Stores the latest known balance for every player across the network.
CREATE TABLE `economy_sync` ( `uuid` VARCHAR(36) NOT NULL, `username` VARCHAR(64), `balance` DECIMAL(20,2), `last_updated` BIGINT, `updated_by` VARCHAR(64), PRIMARY KEY (`uuid`), INDEX (`last_updated`) ) ENGINE=InnoDB;
network_punishments
Stores active and historical punishments (bans, IP bans, mutes) issued by any server.
CREATE TABLE `network_punishments` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `type` VARCHAR(10) NOT NULL, `target` VARCHAR(64) NOT NULL, `target_name` VARCHAR(64), `reason` VARCHAR(512), `punisher` VARCHAR(64), `server_id` VARCHAR(64), `time` BIGINT, `expires` BIGINT, `active` TINYINT(1), PRIMARY KEY (`id`), INDEX (`target`), INDEX (`type`, `active`), INDEX (`time`) ) ENGINE=InnoDB;
network_nicknames
Stores the latest nickname for each player, with timestamp-based conflict resolution.
CREATE TABLE `network_nicknames` ( `uuid` VARCHAR(36) NOT NULL, `nickname` VARCHAR(256), `last_updated` BIGINT, `server_id` VARCHAR(64), PRIMARY KEY (`uuid`), INDEX (`last_updated`) ) ENGINE=InnoDB;
network_kit_cooldowns
Stores the last claim timestamp for each player-kit pair across the network.
CREATE TABLE `network_kit_cooldowns` ( `uuid` VARCHAR(36) NOT NULL, `kit_name` VARCHAR(64) NOT NULL, `claimed_at` BIGINT, `server_id` VARCHAR(64), PRIMARY KEY (`uuid`, `kit_name`), INDEX (`kit_name`) ) ENGINE=InnoDB;