Architecture
This page explains how LeonsDB is built and why. Sections marked (planned) describe the design for parts that are not built yet; they are written down now so that early decisions don’t block them later.
The pieces
Minecraft servers (Paper) LeonsDB server (Go)
┌──────────────────────────┐ gRPC / TLS ┌──────────────────────────┐
│ your plugin │◄───────────────►│ API layer (gRPC) │
│ └ LeonsDB API │ token per │ └ engine: rules, ops, │
│ LeonsDB connector │ server │ leases, indexes │
│ ├ local player cache │ │ └ storage: Pebble │
│ └ write merger │ │ (WAL + LSM) │
└──────────────────────────┘ └──────────────────────────┘
- Server: one Go binary (or Docker image). It stores data with Pebble, the storage engine behind CockroachDB. Pebble handles the write-ahead log, crash recovery and compaction. Everything above it (data model, operations, leases, indexes, permissions) is LeonsDB’s own code.
- Connector: a Java 21 library plus a Paper plugin. Plugins talk to the connector’s API; the connector owns the network connection, the local cache and the write merging.
- Protocol: gRPC with Protocol Buffers, defined in
/proto. The server keeps all protocol code in one thin layer (server/internal/api) so a custom binary protocol could be added later without touching the engine.
Data model
Data is organized as namespace → collection → record. A namespace
usually belongs to one plugin (practicepvp), and a collection holds one
kind of data (stats).
- Player collections hold one record per player UUID, loaded and cached as a unit.
- Global collections are keyed by any string: bans, clans, settings.
Names are 1–64 characters, start with a lowercase letter and contain only lowercase letters, digits and underscores. The connector checks this at startup, and from Phase 1 the server enforces it too.
Records have typed fields: int, long, double, bool, string, UUID, timestamp, list and map, plus Minecraft types (items, inventories, locations).
Items are stored in Minecraft’s own format. Since Minecraft 1.20.5 the
item format (“data components”) changes in almost every release. Instead
of inventing an encoding that would break on every update, LeonsDB stores
the bytes from Paper’s ItemStack.serializeAsBytes() together with the
Minecraft data version. When an old item is loaded on a newer server, Paper
upgrades it with Mojang’s own data fixers. It is compact binary, not JSON.
Updates are operations, not overwrites: set, increment, append, remove. The connector merges operations on the same field before sending them (40 × “kills +1” becomes “kills +40”), and the server applies all operations of a request atomically.
Storage layout (Phase 1)
All data lives in one Pebble database inside the data directory. Keys are binary and order-preserving, so related data sits together on disk and range scans are cheap:
[key type][namespace id][collection id][record key]
Namespace and collection names are mapped to short numeric IDs, keeping keys small. Index entries (Phase 4) use the same scheme, so Pebble’s sort order gives sorted indexes for free.
Player ownership and fencing (Phase 3)
Only one Minecraft server may write a player’s data at a time. A server acquires a lease when the player joins, renews it with heartbeats, and releases it when the player leaves. If a server crashes, its leases expire.
Leases alone are not enough. A server can freeze (a long garbage collection pause, a lag spike) for longer than its lease, wake up, and write stale data after another server has already taken over. To stop that, every lease acquisition returns an epoch, a number that only ever increases. Every write carries the epoch it was made under, and the server rejects writes with an epoch older than the current one. A frozen server’s late writes are refused, no matter how late they arrive.
The epoch is part of the write path from Phase 1 on, so adding leases does not change the protocol.
Fields that must never be lost (Phase 2)
The connector batches normal writes and sends them about once per second.
Fields marked @Transactional, typically money, skip that buffer: each
change goes to the server immediately (still without blocking the main
thread) and only counts as done once the server has stored it durably.
Sorted indexes (Phase 4)
A numeric field marked @Indexed is kept in a sorted index on every write.
Top-N queries read the index in order. “Rank of player X” needs to count
the entries above a value, which an LSM store like Pebble cannot do
quickly. LeonsDB keeps a counting skip list in memory per index, which
answers rank queries in O(log n), and rebuilds it from the on-disk index on
startup.
Security defaults
- The server listens on localhost only unless configured otherwise.
- Listening on a network address requires TLS, unless the operator opts
out explicitly with
insecure_allow_plaintext. - There are no default passwords. (Phase 1) Every Minecraft server gets its own token; tokens are stored only as hashes.