Skip to content

Legacy Data Migration

Petrus Pradella edited this page Jul 31, 2026 · 2 revisions

Legacy Data Migration

Before 3.0, EverNifeCore stored each player in a YAML file under plugins/EverNifeCore/PlayerData/<uuid>.yml. The new storage layer keeps data in the backend the admin configures (see Storage Backends). EverNifeCore migrates the old files for you, once, on the first boot after the upgrade - no manual conversion. It also offers a runtime command to move a single section between backends later on.


First-boot YAML import

Trigger. On boot EverNifeCore looks for plugins/EverNifeCore/PlayerData/*.yml. If any exist and the migration is not yet recorded as finished, the one-time import runs on the first server tick - after every plugin has registered its sections and their legacy adapters - and only then are player logins released.

How a file is split. Each legacy per-player file is broken into independent entities:

  • The base block becomes the new PlayerData entity.
  • Every top-level (root) key claimed by a registered legacy adapter is routed by that adapter into its own section's collection and backend.

A plugin opts a section into the import by declaring a legacy adapter on its PDSection configuration:

PDSectionConfiguration.builder(ecPluginData, JobsSection.class)
    // convert the old '<rootKey>:' subtree of the per-player file into the new POJO
    .legacyYaml("Jobs", section -> {
        JobsSection jobs = new JobsSection();
        jobs.level = section.getInt("level", 0);
        jobs.job = section.getString("job", "none");
        return jobs;
    })
    .build();

Sections without a legacy adapter are simply not migrated.

Guarantees

  • Idempotent - an entity whose UUID is already present in the target collection is skipped, so a re-run never overwrites more recent data.
  • Leaving the folder means completion - a file moves out only once every root key holding data has been migrated. Whatever remains in PlayerData/ is, literally, the to-do list.
  • Never edits or deletes a YAML file - a broken one is copied to PlayerData-Failed/ for diagnosis while the original stays behind as pending.
  • A broken file never aborts the run - it is reported and the rest still imports.

Progress is tracked in playerdata-storage-migration-metadata.yml, rewritten each run with every root key it saw (including ones no adapter claims yet - those are what bring the import back on a later boot, once the owning plugin is installed).


Consolidation into __LegacyData_V2

On the boot that finally drains the folder, a one-time tidy-up gathers every artifact the migration produced into a single archive, leaving the plugin folder clean and the whole migration documented in one place:

plugins/EverNifeCore/__LegacyData_V2/
  PlayerData/                                 (the archived original .yml files - was PlayerData-Imported)
  PlayerData-Failed/                          (only if any broken files were ever copied for diagnosis)
  playerdata-storage-migration-metadata.yml   (the progress file, moved out of the plugin root)
  migration-result.log                        (a human-readable summary of the whole migration)

The archived originals are renamed back to PlayerData/ on purpose: rollback is a single move. An older EverNifeCore reads only a PlayerData/ folder, so to downgrade you move __LegacyData_V2/PlayerData/ back to the plugin folder before downgrading - otherwise every player is greeted as brand new.

A later re-migration does not mix into an existing archive: it gets its own untouched sibling (__LegacyData_V2_2, _3, …).

To force a re-migration: delete the generated artifacts (the progress file and the __LegacyData_V2 archive) and restore the PlayerData/*.yml files. The import runs again and skips whatever already reached the backend.

Notes for large servers

⚠️ The import runs on the main thread on the first tick, and logins are held on a gate that is subject to playerdata.login-timeout-seconds (default 5 s). On a server with thousands of legacy files the first migration can take much longer than that, during which connecting players may be kicked ("storage unavailable") and retry until it finishes. Run the first post-upgrade boot in a maintenance window, and consider raising the server's watchdog timeout for that boot.

📌 A root key whose owning plugin is not installed keeps its file in PlayerData/ (no adapter = not fully migrated), so the import re-scans on every boot. To finish, install the plugin that owns that data, or move the remaining .yml files out of PlayerData/ by hand.


Runtime transfer: /ecstorage transfer section

You can move a single PDSection's collection to another backend at runtime, without editing files by hand:

/ecstorage transfer section <plugin:section> <backend>

Permission node evernifecore.command.storage.transfer. It is a subcommand of [[Command Framework|Command-Framework]]'s /ecstorage, alongside the read-only /ecstorage status (see Storage Backends).

  • <plugin:section> identifies the section by its PluginName:SectionSimpleName id.
  • <backend> is a backend declared and enabled in storage.yml.

What it does: freezes the section's writes, copies the collection through EveryDatabase's StorageTransfer (keeping the source collection as a backup), cuts over to the new backend, and persists the choice into storage.yml so the new binding survives a restart. It runs asynchronously and reports the entity count and duration when done; on failure the binding stays unchanged and each error is listed.

📌 A maintenance window is recommended for a transfer - the source is kept as a backup, but a cutover under live writes is best done quietly.


See also

Clone this wiki locally