Architecture: State & data¶
Principles¶
- Server owns canonical state.
- Persistence is versioned and migratable.
- All grants are idempotent.
Data types¶
- Durable (persistent): player progression, inventory, MMR, punishments
- Ephemeral (short-lived): matchmaking queues, temporary tokens, rate limit counters
- Observability: events/logs, match summaries, moderation evidence links
Storage strategy¶
Roblox-side:
- DataStore: durable player profile and ledgers (careful with budgets)
- MemoryStore: queues/tokens/state caches (TTL, best effort)
- MessagingService: fanout invalidations (best effort)
Web-side (dashboard):
- MySQL/MariaDB for audit logs and config history
- Optional: Redis for queues/caching
DataStore reliability patterns¶
Session locking¶
To prevent data corruption from multiple servers writing to the same player profile:
- Use session locking pattern: one server "owns" a player's data at a time
- On join: acquire lock (write server ID + timestamp to profile metadata)
- On leave: release lock and save final state
- On conflict: newer session wins after grace period
Consider using @rbxts/profileservice or similar battle-tested library that implements this pattern.
Retry policy¶
DataStore operations can fail due to throttling or transient errors:
const RETRY_CONFIG = {
maxAttempts: 3,
baseDelayMs: 1000,
maxDelayMs: 8000,
jitter: true, // ±20% randomization to avoid thundering herd
};
// Exponential backoff: 1s → 2s → 4s (with jitter)
Budget management¶
DataStore has strict rate limits (60 + 10×players requests/min for GetAsync, etc.):
- Batch reads on player join (single GetAsync for full profile)
- Debounce writes (queue changes, flush periodically or on leave)
- On budget exhaustion: queue writes in-memory, flush on player leave
- Critical writes (purchases): retry with higher priority, alert if persistently failing
Write-behind queue¶
interface WriteQueue {
pending: Map<string, ProfileData>;
flushIntervalMs: 30000; // flush every 30s
flushOnLeave: true;
maxQueueSize: 100; // per server
}
If a player leaves before flush completes, attempt synchronous save with timeout.
Corruption recovery¶
- Store
schemaVersionandlastWriteAtin every profile - On load, validate schema; if corrupt, load from backup or quarantine
- See
docs/runbooks/data-corruption.mdfor recovery procedures
Profile schema¶
Implemented across @broblox/data, @broblox/progression, @broblox/inventory, @broblox/rewards, and @broblox/moderation:
schemaVersionprogression(xp, level, prestige) —@broblox/progressionmmr(per mode) —@broblox/matchmakinginventory(owned items, equipped loadouts) —@broblox/inventorymoderation(ban state, mutes, trust score) —@broblox/moderationreceipts/grants(idempotency keys) —@broblox/rewardsquests(active quests, objective progress) —@broblox/questspets(owned pets, equipped slots, XP) —@broblox/petscosmetics(owned cosmetics, equipped slots) —@broblox/cosmeticsbattlePass(season, tier, XP, claims) —@broblox/battle-passcodes(redeemed code IDs) —@broblox/codesmarketplace(receipt dedup keys, pass ownership cache) —@broblox/marketplace
Idempotency (non-negotiable)¶
Every mutation that can be retried must have a unique id:
- purchases: receipt id
- rewards: claim id
- admin actions: action id
- match results: match id + version
Migration model¶
- Each document stores
schemaVersion. - On load:
- migrate to current
- write back only when safe
Schema versioning (BasePlayerStore)¶
All player stores that extend BasePlayerStore must use TData extends VersionedData. This enforces:
- Default data must include
__version: 1. - When any stored field is added, renamed, or removed, increment
schemaVersion()and implementmigrate(data, fromVersion). - On
load(), if the stored version is older thanschemaVersion(), the store callsmigrate(), stamps the new version, and marks dirty to trigger a save. - Data saved with no
__versionfield is treated as version 0.
See ADR-0010 for the full rationale.
Competitive integrity¶
- Match results are computed server-side.
- Rewards are granted via a ledger-like process.
- Any detected corruption triggers containment (disable grants, require review).