Skip to content

Storage Backends

Petrus Pradella edited this page Jul 28, 2026 · 8 revisions

Storage Backends

Where PlayerData, PDSections and account data physically live is an admin decision, made in plugins/EverNifeCore/storage.yml. Plugins advise a default; the admin has the final say. The storage engine is EveryDatabase - EverNifeCore configures and drives it.

The file is generated with a full set of commented, disabled example backends on first boot, so most admins only flip enabled and fill in credentials.


The shape of storage.yml

storage-backends:
  groupedfile:                 # the entry key is a FREE unique id (name it anything)
    enabled: true
    type: groupedfile
    path: plugins/EverNifeCore/StorageData/groupedfile
    format: yaml               # yaml | json  (file backends only)
  mysql:
    enabled: false
    type: sql
    url: "jdbc:mysql://localhost:3306/minecraft"
    user: root
    pass: ""
    pool:
      minIdle: 2
      maxSize: 10
      connectTimeoutSeconds: 5
      idleTimeoutSeconds: 30

default-backend: groupedfile   # used when nothing more specific is configured

The entry key is a free unique id, not the type. Declaring several backends of the same type is how you point different data at different servers - e.g. a mysql_economy and a mysql_points, both type: sql, on two databases.

Every backend's runtime dependency (JDBC drivers, the Mongo driver) is downloaded up front at boot, so switching type here never hits a missing dependency.


Supported backends

type: maps to one of these. Full capability matrix and per-backend setup live in the EveryDatabase wiki - the short version:

type: Engine Good for EveryDatabase page
groupedfile key-major files (one file per key, all its collections) the per-player model; the factory default Grouped Files
localfile one file per entity tiny deploys, human-readable Local Files
sql MySQL / MariaDB (HikariCP pool, JSON column) production, shared DB MySQL & MariaDB
postgresql PostgreSQL (HikariCP pool, JSON column) production, shared DB PostgreSQL
h2 H2 embedded / file / tcp embedded / dev H2
mongo MongoDB (native BSON documents) document workloads MongoDB
memory in-memory only, ephemeral tests / throwaway servers In-Memory

format: yaml | json applies to the file backends (groupedfile / localfile) only; yaml is the default and json is always written pretty/indented. For choosing among them, see Choosing a Backend.

⚠️ memory data is lost on shutdown - the parser warns when you enable it. Use it only for tests.


Routing the data

Once backends are declared, three blocks route the actual data. Each may name a storage-backend-id; naming a missing or disabled backend is a fail-fast error at boot with a message that points at the offending key.

playerdata

playerdata:
  storage-backend-id: groupedfile         # must be an enabled backend id
  collection: evernifecore_playerdata
  load-mode: ALL                          # ALL (default) | RECENT
  recent-days: 60                         # RECENT: how far back to eager-load
  login-timeout-seconds: 5                # how long a login may wait on storage before being denied
  default-idle-grace-seconds: 3600        # how long a section lingers after its player goes offline
  orphan-reaper:
    enabled: false                        # opt-in; sweeps section rows whose base is gone
    interval-minutes: 360

load-mode: ALL loads every player at startup; RECENT eager-loads only players seen in the last recent-days and lazy-loads the rest on demand.

default-idle-grace-seconds (default 3600) is how long a player's section stays in memory after that player goes offline - the server-wide value for every section whose developer did not ask for a specific one. Lower it to reclaim memory sooner (0 releases as soon as the player leaves), raise it so a reconnect does not have to read from the database again.

pdsections

pdsections:
  myplugin:                   # your plugin's name, lowercase
    jobs:                     # the section id declared at registration
      storage-backend-id: mysql
      # collection: pd_myplugin_jobs
      # idle-grace-seconds: 600                   # overrides the developer AND the global default
      # cache: { policy: TTL, ttlSeconds: 300 }   # ALWAYS | TTL

Generated automatically the first time each PDSection registers, keyed by the section's id (not its class name), so renaming the class never orphans the entry. The admin then edits storage-backend-id, collection, idle-grace-seconds and cache freely - keys are matched case-insensitively.

An admin cache: sets freshness only; when a cell enters and leaves memory is the developer's lifecycle. NOCACHE is refused for a PDSection - the cached cell is the instance the flush pipeline persists, so bypassing the cache would lose every write.

An entry no registered section claims is reported with a WARNING shortly after boot: either the plugin that owned it is gone, or its section id changed and the rows of the old collection are no longer reachable. Nothing is moved or deleted for you.

multi-platform-accounts

multi-platform-accounts:
  enabled: false
  storage-backend-id: ""      # empty = default-backend
  # idle-grace-seconds: 3600  # absent = follow playerdata.default-idle-grace-seconds

The backend hosting the whole account family (registry + account-wide sections). On a real network it must be a database shared by every instance. See Accounts.

idle-grace-seconds is how long an account row stays in memory after the last online member of that account quits. The account family has no per-section entry, so this one value covers all of it.


Multi-server cache-sync

When several instances share one database, each instance caches rows independently - a write on instance A must invalidate the same entry on instance B. That is what multi-server-cache-sync does.

multi-server-cache-sync:
  enabled: true               # default; HARMLESS no-op on a single server
  transport: auto             # auto | redis | native
  redis:
    enabled: false
    host: localhost
    port: 6379

Transport resolution:

  • auto (default) - Redis if the redis block is enabled, else the backends' native change feed when every manager's backend has one, else a silent no-op.
  • redis - force the Redis pub/sub transport.
  • native - use only the backends' native feed.

Which backends have a native feed: only MongoDB and PostgreSQL. sql (MySQL/MariaDB) and the file backends do not - for those, cross-instance coherence exists only through Redis.

📌 Current state of the Redis transport. The Redis transport lives in the optional everydatabase-manager-jedis module, which is not bundled with EverNifeCore on this version. If you configure a redis block without that runtime on the classpath, EverNifeCore logs a warning at boot and cache-sync via Redis is a no-op. The native-feed path (Mongo/PostgreSQL) is wired and works today. On a single server the whole block is a harmless no-op regardless.

For the deeper model, see Cross-Process Cache Sync.


Logging

logging:
  level: warn                 # warn (default) | info | debug | trace

Controls the EveryDatabase storage log verbosity (bind reports, conflict/flush lines).


When a backend cannot be reached

A backend declared enabled: true that does not answer at boot stops the server. This is on by default, and it is deliberate: booting without the database that holds player data would let every player join with empty data, and the first save would overwrite the real rows with those empty ones.

Every unreachable backend is reported at once, not one per restart. The report names each one with its type, the target it was pointed at (with the password redacted), which storage.yml keys route data to it, and the root cause of the failure; the full stack traces are printed above the banner.

# plugins/EverNifeCore/config.yml
Settings:
  Storage:
    STOP_SERVER_IF_STORAGE_IS_UNREACHABLE: true   # the default

Two things worth knowing before you flip it:

  • It only ever stops a BOOT. A failed reload never stops anything - the previously loaded storage is still live and serving, so the report says so and nothing is lost. Fix the config or the database and reload again.
  • false does not make the server work without a database. EverNifeCore stays disabled either way, every plugin that depends on it fails, and whatever data does get written diverges from what the database holds. The only real ways out of a boot failure are starting the database, fixing the url/user/pass, or setting enabled: false on a backend you no longer use.

Admin commands: /ecstorage

Subcommand Permission node What it does
status evernifecore.command.storage.status Prints the routing (which backend/collection each entity persists on) plus health counters - quit-flush retry backlog, adopted conflicts, last failed write.
transfer <plugin:section> <backend> evernifecore.command.storage.transfer Migrates one PDSection's collection to another backend at runtime (see below).

Moving data between backends

You are not locked in: a single PDSection's collection can be migrated to another backend at runtime without editing files by hand - see Legacy Data Migration for /ecstorage transfer. The underlying builder is EveryDatabase's Moving Data Between Backends.


See also

Clone this wiki locally