Performance

How EssentialsC avoids blocking the main thread, keeps memory lean, and stays fast under real server load.

Overview

EssentialsC is built around one principle: nothing slow runs on the main thread. Every database read/write, every disk operation, every network call is pushed to async tasks. The main thread only touches in-memory structures, which are always up to date because they're pre-loaded and cache-backed.

The result is that the plugin adds essentially zero tick overhead during normal gameplay. Players teleport, use kits, check balances, and open GUIs — the server TPS barely notices.

Async Database Layer

All five SQLite databases use the same async wrapper built into the Database class. Every query that touches disk goes through Database#async(), which opens a fresh WAL-mode connection off-thread, runs the query, then returns a CompletableFuture for the caller to act on back on the main thread.

// Every database op follows this pattern — never blocks the main thread
database.async(conn -> {
    // runs off main thread
    PreparedStatement stmt = conn.prepareStatement("SELECT ...");
    return stmt.executeQuery();
}).thenAccept(result -> {
    // result handled back on main thread
});

The connection itself opens with two pragmas applied every time:

PRAGMA journal_mode=WAL;    -- concurrent reads don't block writes
PRAGMA busy_timeout=5000;   -- waits up to 5s instead of failing hard

WAL mode is the key one. Without it, a write locks the entire file and any concurrent read has to wait. With WAL, readers and writers operate independently — important on a busy server where multiple systems (economy, homes, kits, auctions) are all doing DB work at once.

Five dedicated databases Each major system gets its own .db file: economy.db, homes.db, kits.db, nicks.db, and auction.db. No single file becomes a bottleneck, and you can back up or inspect one system without touching the others.

In-Memory Caching

Database reads are expensive relative to memory lookups. EssentialsC caches the hot data for every active player so that common operations (balance checks, kit cooldowns, home counts) never hit the disk at all during normal gameplay.

Economy

Player balances are held in a ConcurrentHashMap<UUID, BigDecimal>. When a player joins, their balance is loaded once. Every subsequent /balance, /pay, or transaction check reads from the map. The database is only touched when a balance actually changes, and that write is async.

Kit Cooldowns

Kit cooldown data is cached per player in a ConcurrentHashMap<UUID, Map<String, PlayerKitData>>. The cache is populated on join and flushed async to kits.db on quit. Mid-session kit use never opens a database connection.

Homes

A player's home list is loaded into memory on join via an async query to homes.db. The cache stays warm for the entire session. /home, /delhome, and /homes all operate on the cache; the database is updated in the background.

Nicknames

Active player nicknames are stored in a ConcurrentHashMap backed by nicks.db. Tab list and chat formatting reads always hit memory, not disk.

Thread Safety

EssentialsC uses ConcurrentHashMap across 18+ classes wherever shared state is accessed from both async database callbacks and main-thread event handlers. This avoids ConcurrentModificationException without needing synchronized blocks that would introduce lock contention.

The pattern is consistent: async callbacks populate the map, synchronous game logic reads from it. The two never race because the async side only writes completed data, never partial state.

AFK System

The AFK manager runs two scheduled tasks instead of listening to every player movement event:

checkTask every 5s
Compares each player's last activity timestamp. Marks them AFK if they've been idle past the configured threshold. Runs every 100 ticks (5 seconds).
kickTask every 60s
Only runs if AFK kick is enabled. Checks AFK duration and kicks players who have been idle too long. Runs every 1200 ticks (60 seconds).

Activity is tracked via a ConcurrentHashMap<UUID, Instant> that's updated by movement, chat, and interaction events. The check tasks just compare timestamps — they do no I/O and add negligible tick cost.

Auction House

The auction house stores listings in auction.db with all GUI rendering, item serialization, and history writes happening off the main thread. The AhGuiManager handles inventory construction asynchronously before opening the GUI to the player, so even large auction listings don't cause a visible lag spike on open.

Sound events and session tracking are kept in memory and cleared on transaction completion, so no session state leaks between players.

Modular Initialization

Systems that aren't needed don't start. Every major subsystem is guarded by a config flag and only initialized if enabled:

if (configManager.isShopEnabled())   { /* ShopManager init */ }
if (configManager.isAHEnabled())     { /* AuctionManager init */ }
if (configManager.isWarpEnabled())   { /* WarpManager init */ }
if (configManager.isAfkEnabled())    { /* AFKManager + tasks */ }
if (configManager.isRTPEnabled())    { /* RTPManager init */ }
if (configManager.isScoreboardEnabled()) { /* ScoreboardManager init */ }

A server that only needs economy and homes doesn't pay the memory or listener overhead of the auction house, RTP, or scoreboard systems. Each disabled system registers zero event listeners and allocates zero background tasks.

Scheduled Backup System

The backup system (/essc backup and optional shutdown backup) runs entirely via BackupManager#createAsync(). File I/O is dispatched to a Bukkit async task so the main thread never waits on zip operations, even during server shutdown. Failures are logged as warnings — they never block the disable sequence.

Metrics

EssentialsC sends anonymous usage statistics via two independent systems:

bStats
Plugin ID 29401. Includes standard server stats plus custom economy charts (balance distribution, transaction volume). Can be disabled in plugins/bStats/config.yml.
FastStats
Secondary lightweight metrics service initialized via BukkitMetrics. Both systems are async and have no measurable impact on server performance.

Recommendations

EssentialsC is tuned for Paper. Running on Paper (or a fork like Purpur or Leaf) gives you the best results because Paper's async event system and optimized scheduler work in EssentialsC's favor.

  • Use Paper or a Paper fork. Spigot works, but Paper's internals are faster.
  • Keep your databases/ folder on fast local storage. SQLite on a networked drive or slow HDD will bottleneck even async queries.
  • Disable subsystems you don't use. Every disabled system is zero overhead.
  • If you run a network, use EssentialsC-MySQLExpansion for kit cooldown sync instead of polling SQLite across nodes.
  • Enable debug mode (debug: true in config) temporarily if you suspect a slow query — it logs all DB operations with timings.