Both Treasury and Business store overlapping player data (login times, UUID↔name mapping) in separate tables. Consolidate into one economy_players table and backfill.
Technical notes — Business owns firm_players (FirmPlayerMapper: uuid↔name cache, touched by FirmPlayerCreationEventListener on login); Treasury owns player_login_times (PlayerLoginMapper: last-login epoch). Neither stores both. Add an economy-schema migration (V<n>__economy_players.sql — schema changes go there) for a unified economy_players(player_uuid_bin, current_name, name_lower, first_seen, last_seen, last_login_epoch); point both mappers at it (Treasury owns the writer, business reads), then drop the old tables. Cross-plugin coordination required.
economy_players consolidation (prod data, 2026-06-11)Ran the row-count / overlap analysis against the prod economy DB.
| Metric | Value |
|---|---|
firm_players rows | 109,663 |
player_login_times rows | 109,236 |
| Overlap (UUID in both) | 109,206 |
Only in firm_players | 457 |
Only in player_login_times | 30 |
Distinct union (final economy_players row count) | 109,693 |
Reconciliation checks out: 109,206 + 457 + 30 = 109,693.
firm_players is effectively the superset and the richer table — it carries every identity column the unified schema needs (current_name, name_lower, first_seen, last_seen), while player_login_times only adds last_login_epoch (no name/timestamp data). So:
firm_players (covers 109,663 of 109,693 = 99.97%).last_login_epoch onto it from player_login_times for the 109,206 overlap rows.player_login_times (have a last_login_epoch but no name/first_seen/last_seen) — these need current_name/name_lower/first_seen/last_seen left NULL or sourced elsewhere (e.g. Mojang/usercache). Decide the unified schema's nullability for these 30 before migrating.firm_players — they get NULL last_login_epoch (never recorded by Treasury). The unified schema must allow last_login_epoch NULL.V<n>__economy_players.sqllast_login_epoch must be nullable (457 rows have no login epoch).current_name/name_lower/first_seen/last_seen must tolerate NULL or the 30 login-only rows must be enriched before/at backfill.player_uuid_bin is consistent across both tables (both PK on it), so no dedup logic needed beyond the UNION.economy_players)-- 1. seed from firm_players (the richer/superset table) — 109,663 rows
INSERT INTO economy_players
(player_uuid_bin, current_name, name_lower, first_seen, last_seen, last_login_epoch)
SELECT fp.player_uuid_bin, fp.current_name, fp.name_lower,
fp.first_seen, fp.last_seen, plt.last_login_epoch
FROM firm_players fp
LEFT JOIN player_login_times plt
ON plt.player_uuid_bin = fp.player_uuid_bin;
-- 2. add the 30 login-only rows (name/timestamps NULL)
INSERT INTO economy_players
(player_uuid_bin, current_name, name_lower, first_seen, last_seen, last_login_epoch)
SELECT plt.player_uuid_bin, NULL, NULL, NULL, NULL, plt.last_login_epoch
FROM player_login_times plt
LEFT JOIN firm_players fp ON fp.player_uuid_bin = plt.player_uuid_bin
WHERE fp.player_uuid_bin IS NULL;
Expected final count: 109,693 rows.
Sizing is small and clean (sub-110k rows, 99.97% covered by firm_players, only 30 edge rows) — no scaling concern, safe for a single-transaction Flyway migration. Open decision before writing the migration: whether to leave the 30 login-only rows' name/timestamp columns NULL or enrich them. Once that's settled, the V<n>__economy_players.sql migration + the two backfill statements above can be written, then re-point both mappers (Treasury writes, Business reads) and drop the old tables.
Code context — Two overlapping tables in the shared economy schema: business's
firm_players(FirmPlayerMapper: uuid↔name cache, touched byFirmPlayerCreationEventListeneron login) and Treasury'splayer_login_times(PlayerLoginMapper: last-login epoch). Neither stores both.Approach: new
economy-schemamigration (V<n>__economy_players.sql— schema changes go there) for a unifiedeconomy_players(player_uuid_bin, current_name, name_lower, first_seen, last_seen, last_login_epoch); point both mappers at it, Treasury owns the writer, business reads, then drop the old tables. Cross-plugin coordination required.