> For the complete documentation index, see [llms.txt](https://edseries-plugins.gitbook.io/p/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://edseries-plugins.gitbook.io/p/pinnaduels/getting-started/storage.md).

# Storage (YAML / MySQL)

PinnaDuels saves every player's duel data (ELO, peak, wins / losses / draws, streaks, placement, Duel Tokens and token progress, weekly wins, cooldowns, pending season rewards) and the season (number and start). Two storages:

| `storage.type`   |                                                              |
| ---------------- | ------------------------------------------------------------ |
| `yaml` (default) | local files: `data/players.yml` and `data/season.yml`        |
| `mysql`          | a MySQL or MariaDB database, which several servers can share |

Nothing changes for existing servers: without a `storage` section the files are used, as before.

## Setting up MySQL

```yaml
storage:
  type: mysql
  mysql:
    host: 'localhost'
    port: 3306
    database: 'minecraft'
    username: 'root'
    password: ''
    table-prefix: 'pinnaduels_'
    use-ssl: false
    properties: 'useUnicode=true&characterEncoding=utf8&serverTimezone=UTC'
    ranking-refresh: 30
    top-cache: 300
    join-timeout: 10
    recheck-after-join: 2
```

| Option                                             |                                                                                                         |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `host`, `port`, `database`, `username`, `password` | the database login (the database must exist; the tables are created by PinnaDuels)                      |
| `table-prefix`                                     | put in front of the table names (letters, digits and `_`)                                               |
| `use-ssl`                                          | encrypted connection                                                                                    |
| `properties`                                       | extra JDBC URL parameters. MySQL 8 without SSL may need `&allowPublicKeyRetrieval=true`                 |
| `ranking-refresh`                                  | seconds between two reads of the ranking from the database                                              |
| `top-cache`                                        | how many of the best players are kept with all their stats and skin (top menu heads, API, placeholders) |
| `join-timeout`                                     | seconds a joining player's login waits for their data at most; a slower read is used once it arrives    |
| `recheck-after-join`                               | seconds after a join to read the data once more (see *Several servers*); `0` = off                      |

The storage is chosen when the server starts: `/duel admin reload` does not switch it, restart the server. The driver that comes with Paper / Spigot is used (MySQL Connector/J, or the MariaDB driver when only that one is there); nothing else needs installing.

**If the database can't be reached at startup**, the console shows a clear error with the host, port and database (never the password) and PinnaDuels uses the local files instead, so the server keeps running. Fix the database and restart. If the connection drops while running, it is reopened on the next save; failed saves are kept and written again.

### Tables

* `<prefix>players`: one row per player, keyed by the UUID, one column per value (InnoDB, utf8mb4).
* `<prefix>data`: small key / value table, holds the season.

## How data is handled

* **yaml**: everyone's data is in memory (a few hundred bytes per player), read once in the background at startup.
* **mysql**: only the players online on this server are in memory. A player's data is read while they log in (on the IO thread, never the main thread) and saved when they leave; changes are also saved once a minute. The ranking (`/duel top`, the leagues menu, the top placeholders, Master / Legend positions) is a light list of every ranked player read from the database every `ranking-refresh` seconds, with the live values of the players online here on top.
* Both: every database / file access runs on one background thread, in order. Everything is saved on shutdown.

## Several servers

With MySQL, servers that share the database share the duel data:

* A player's data is read when they join, never taken from an old copy, and read once more `recheck-after-join` seconds later: a proxy switch lets the player into the new server before the old one saved them, so that second read picks up the old server's last save.
* **The same player should not be online on two servers at once.**
* The first server that reaches the end of the season ends it for every stored player (rewards by best league, soft reset); the other servers notice it within a minute and announce it too. Data that was saved for an older season (a player who was online somewhere else meanwhile) catches up when it is loaded, rewards included.
* The daily ELO decay runs in the database once per day, whichever server does it first.
* Each server keeps its own ghost recordings (`ghosts/*.ghost`, see below).
* `/duel stats <player>` and the admin commands work for offline players: they are read from the database. Editing an offline player while they are online on another server is overwritten when that server saves them.

## Moving data: /duel admin migrate

`/duel admin migrate <from> <to>` copies every stored player and the season from one storage to the other, in the background, and reports how many players were copied. The players online right now are saved first. Records that already exist in the target are replaced; others there are kept.

Switching a server from files to MySQL:

1. Fill in `storage.mysql` (keep `type: yaml` for now) and run `/duel admin migrate yaml mysql`.
2. Set `storage.type: mysql` and restart.

Back from MySQL to files: `/duel admin migrate mysql yaml`, set `type: yaml`, restart. Copying into the storage in use (for example `migrate yaml mysql` on a server already on MySQL) also replaces the copied players' data in memory.

## Ghost recordings

Recordings stay local files in `ghosts/` on every server (they are large binary replays and are only read when picked as an opponent). With several servers, each one replays the duels recorded on it.
