Benchmark Harness Design — Three Backends
Status (2026-08-07): three-way, topology-led (peer-to-peer: LuckPerms + LCore; master-slave: NeoCore). One rig, three real systems behind a common interface. Security is a separate analytical metric (see §Attack surface), not measured here. Artifact is NeoCore (12,495 LOC, 13 TODO markers, pure gRPC, no Redis) — replaces Atreus.
Goal: one measurement rig that drives the real synchronization code of all three systems under identical conditions, so the comparison is fair. Only the propagation mechanism differs.
Architecture
real LuckPerms + Redis messenger + shared MySQL, in Bukkit servers"] BK --> LC["LCoreBackend
real RedisHandler/Packets, push-data"] BK --> AT["NeoCoreBackend
real master + RPCClient/PubSubHelper, push-data"]
The three backends (each wraps the REAL code)
- LuckPermsBackend — real LuckPerms configured with the Redis messenger + shared MySQL storage, running inside small Bukkit test servers. Origin = trigger a permission change via LuckPerms API/command; receiver = observe when LuckPerms on another server reflects it (via its event/API). Heaviest to stand up (LuckPerms is built to live inside a server).
- LCoreBackend — real
RedisHandler+Packetspublish/subscribe (push-data), extracted headless. - NeoCoreBackend — real
rpc-master+dataloaderclient stream path (PubSubRPCbidi +PlayerListRPC), extracted headless. NeoCore has only 13 TODO markers and ran in staging, so it should start with far less resurrection work than Atreus would have.
The pluggable interface (Java-ish sketch)
interface PropagationBackend {
void start(Config cfg);
Origin connectOrigin(String serverId);
Receiver connectReceiver(String serverId, ApplyListener onApply);
void injectFailure(FailureType t); // KILL_DB / KILL_BROKER / KILL_MASTER
void close();
}
interface Origin { void commit(Mutation m); } // m has a unique correlationId
interface Receiver { } // on apply, invokes onApply(correlationId, System.nanoTime())
Instrumentation
- Origin stamps correlationId + commitNanos per mutation; receiver stamps applyNanos when the change is reflected;
latency = applyNanos - commitNanos. - Single host +
System.nanoTime()→ no clock skew. - HdrHistogram for p50/p95/p99; rolling ops counter for throughput.
- Loss = correlationIds committed but never applied within a timeout.
Metrics computed
| Metric | How |
|---|---|
| Propagation latency | applyNanos − commitNanos → HdrHistogram (p50/p95/p99) |
| Staleness / update loss | committed − applied within timeout; under steady state and injected disconnect |
| Throughput | ramp rate until backlog/latency diverges; report the knee |
| Overhead — network | bytes on wire per propagated change, per system |
| Overhead — database (FAIRNESS-CRITICAL) | DB reads/queries per propagated change. Measured 2026-08-07: LuckPerms costs 7.2 MySQL queries per change at the origin; LCore/NeoCore ~0 (they carry the data). The "+1 read per receiver" assumption was WRONG — query count was identical (144/20 changes) with one and with two servers, because LuckPerms only reloads users it is holding and the benchmark users were offline. Needs re-testing with an online player. See findings §18–19. |
| Overhead — CPU/mem | sampled at origin / broker-or-master / receiver / DB |
| Failure recovery | time from injectFailure() to first post-recovery apply; ops lost |
Attack surface (metric #7 — ANALYTICAL, not run here)
Determined by reading each architecture, not measured:
| | LuckPerms | LCore | NeoCore |
|---|---|---|---|
| Nodes holding store credentials | N | N | 1 (master) |
| Nodes able to write/propagate directly | N | N | 1 |
Honest caveat (one sentence in the paper): NeoCore's transport is currently plaintext/unauthenticated → the advantage is credential distribution; channel hardening (TLS/auth) is future work.
Config knobs (identical across backends)
N receivers · target rate (ops/s) · op-mix · duration · warmup · repeats (k).
Output schema (CSV, one row per op)
run_id, backend, N, target_rate, op_type, correlation_id, commit_ns, apply_ns, latency_ns, applied, db_reads, wire_bytes
Fairness controls
- Same host, JVM/GC flags, warmup, monotonic clock; same MySQL for all three.
- Each backend uses its real serialization/mechanism; measure wire bytes AND DB load, don't assume.
- Compare only the propagation path — acknowledge LuckPerms does far more (contexts, inheritance) so "NeoCore is faster" is not "NeoCore is better."
- Report distributions with variance over k repeats; fixed workload seeds.
LuckPerms backend — how it was actually stood up (2026-08-07)
Two Paper 1.12.2 servers (build 1620, from fill.papermc.io/v3), LuckPerms 5.5.71 bukkit-legacy,
MySQL 8 + Redis 7, sync-minutes: 0. Both in eclipse-temurin:17-jre on --network host.
- Drive it over RCON, not the console.
docker attachworks exactly once: when the attached
client disconnects the container's stdin hits EOF and the server stops reading it forever. Enable
enable-rconinserver.propertiesand use a small RCON client (harness/luckperms/rcon.py). - LuckPerms commands are asynchronous. The RCON response returns before the work is done, and is
usually empty. Verify effects by queryingluckperms_user_permissions, not by reading the reply. - Measure cost with the server counters, not by instrumenting LuckPerms:
SHOW GLOBAL STATUS LIKE 'Questions'on MySQL andINFO statson Redis, before and after a known
number of changes.
Lessons from the first runs (2026-08-07)
- Give every simulated receiver its own gRPC channel. Sixteen streams on a shared channel
measured faster than eight separate ones, because HTTP/2 multiplexes them; that hides the very
fan-out cost the sweep exists to measure. - Receivers must keep pinging. The master registers an observer only after a
ClientPubSubPacket
arrives and drops it after five quiet seconds. A silent receiver stops receiving with no error. - Read results from the CSV, not from the driver script's stdout. Backgrounded processes get
killed at command boundaries in some environments; the CSV survives. - Record
nprocand load average with every run. A smoke run on a host at load 13.77/12 cores
produced a p95/p50 ratio of ~19x for a loopback push, which measures the scheduler.
Threats to validity
- Single-host testbed removes network variance → optional
tc/netemcondition. - LuckPerms runs in real (small) servers; LCore/NeoCore paths are extracted headless → disclose the asymmetry, validate once against a full run if feasible.
- NeoCore reached staging but never production → it has been exercised end-to-end by real servers, but is not a production baseline (LuckyCore is). Disclose this asymmetry.
- Stated confound: topology and transport co-vary (both P2P systems use Redis; the master-slave system uses gRPC). The study compares whole architectures; isolating one variable is future work.