Docs › Storia Cluster

Plugin API (shared data)

Data and messages shared by every worker of a Storia Cluster, for plugin developers. Also works on a single server.

In a Storia Cluster, every Storia Worker runs its own copy of each plugin, so a plugin's files and memory are separate on each worker. Storia's plugin API gives plugins what must be the same everywhere:

  • a key-value store per plugin, kept by Storia Relay, with compare-and-set, counters and change notifications on every worker;
  • messages to the same plugin on every worker.

On a single Storia server (no cluster) the same API works locally: data is kept in storia-shared/ in the server folder and messages reach this server. A plugin needs no second code path.

Player data (inventory, position, effects, PersistentDataContainer on players), advancements, statistics, maps and the scoreboard are already shared by the cluster; use the API for the plugin's own data.

Add the API#

Download storia-api-26.2-5.jar from the release and add it as a compile-only dependency (the classes are part of the Storia server at runtime):

// build.gradle.kts
dependencies {
    compileOnly("io.papermc.paper:paper-api:26.2.build.+")   // or the Folia API
    compileOnly(files("libs/storia-api-26.2-5.jar"))
}

Set folia-supported: true in plugin.yml, as for any Folia plugin.

Shared data#

import dev.storia.api.SharedStore;
import dev.storia.api.StoriaShared;

SharedStore coins = StoriaShared.get().store("myplugin");   // one namespace per plugin

coins.increment("coins." + uuid, 50)                        // atomic on every worker
     .thenAccept(total -> player.getScheduler().run(this,
         task -> player.sendMessage("You have " + total + " coins"), null));

coins.setString("motd", "Welcome!");
coins.getString("motd").thenAccept(text -> getLogger().info(text));
Method What it does
get(key) / getString(key) Reads a value (null if there is none).
set(key, value) / setString(key, text) Stores a value; null deletes the key.
delete(key) Deletes the key.
compareAndSet(key, expected, value) Changes the key only if it holds expected (null = absent). Only one worker can win.
increment(key, delta) Adds to a counter kept as decimal text (missing = 0) and returns the new value.
keys(prefix) The keys starting with prefix, sorted.
listen(listener) Called whenever a key of this namespace changes on any worker, including this one.
  • Namespaces: 1 to 64 characters of a-z 0-9 _ . -. Use your plugin's name.
  • Keys: 1 to 100 bytes of UTF-8. Values: up to 1 MiB.
  • Changes are applied in the order the relay receives them, and every worker sees the notifications in that order.

A lock that only one worker gets#

store.compareAndSet("event-running", null, nodeName.getBytes())
     .thenAccept(won -> { if (won) startEvent(); });
// ... later
store.delete("event-running");

Messages#

StoriaShared shared = StoriaShared.get();
shared.subscribe("myplugin:announce", message ->
    Bukkit.getGlobalRegionScheduler().run(this, task ->
        Bukkit.broadcast(Component.text(message.text()))));

shared.publish("myplugin:announce", "The event starts in 5 minutes!");

A message reaches every subscriber of the channel on every worker, including the one that sent it, in the order the relay received it. Channels: 1 to 64 characters of a-z 0-9 _ . : -. message.node() is the sender.

Threads#

Every call returns a CompletableFuture and never blocks. Results, listeners and message handlers run on a Storia thread, not on a region thread: to touch the world or a player, schedule the work on the right region (player.getScheduler(), Bukkit.getRegionScheduler() or Bukkit.getGlobalRegionScheduler()). Never call join() on a region thread.

An invalid namespace, key or value throws IllegalArgumentException at once. If the relay cannot be reached, the future completes exceptionally; nothing is queued, so handle the failure (retry, or tell the player).

Where the data is#

Setup Stored in
Cluster cluster-world/storia-shared/<namespace>/ on Storia Relay (back it up with the world).
Single server storia-shared/<namespace>/ in the server folder.

Each key is one file whose name is the key in hex. To move a single server's data into a cluster, copy its storia-shared/ folder into the relay's cluster-world/.