Anti-Duplication Multi-Server Roblox DataStore
Ideal for games with economies, trading, inventories, pets, gacha, progression, or any other high-value persistent system. Just paste your Explorer context, your RemoteEvents, and the current data format to receive complete, commented scripts ready to adapt in Roblox Studio.
Act as a senior Roblox developer, specialized in Luau, DataStoreService, MemoryStoreService, and distributed persistence systems. Create a production-ready implementation that prevents data duplication and overwriting when the same UserId tries to play on multiple servers, reconnects very quickly, or joins during an unexpected shutdown. Before generating the code, consider the context below. If any item is empty, use safe names and clearly state your assumptions without blocking the solution: --- MY GAME CONTEXT (FILL IN) --- - Name of the main DataStore: - Player data structure (coins, inventory, pets, progression, etc.): - Existing objects and folders in the Explorer, with full paths: - Existing RemoteEvents/RemoteFunctions and their respective paths: - Current system that grants coins, items, or changes inventory: - Is there a trade, drop, gacha, purchase, teleport, or gift system? Describe it: - Am I already using ProfileService, DataStore2, or any external library?: - Desired autosave interval: - Desired policy if an old session is stuck (deny entry, wait, or kick): - Current schema version, if any: --- END OF CONTEXT --- Design a native solution, without relying on external libraries, using DataStoreService and MemoryStoreService. Generate exactly two complete files: (1) a ModuleScript named `PlayerDataSessionService`, placed in `ServerScriptService/Modules/PlayerDataSessionService`; and (2) a Script named `PlayerDataBootstrap`, placed in `ServerScriptService/PlayerDataBootstrap`. If the paths provided in the context are different, adapt them and explain the change. The ModuleScript must expose a safe API to get data in read-only mode, change data exclusively on the server through a controlled mutation function, save, end a session, and query the loading state. The bootstrap Script must connect Players.PlayerAdded, Players.PlayerRemoving, and game:BindToClose. The implementation must prevent two servers from having authorization to write the same player's data at the same time. Use a distributed lock strategy by UserId in MemoryStoreService, with a unique server/session identifier based on `game.JobId` (include a safe fallback for Studio), configurable TTL/lease, periodic lease renewal, and explicit release on exit. The lock must be acquired before loading or allowing the player into the game. If the lock already belongs to another active server, do not load the data in parallel: implement retries with backoff and a configurable limit; if the limit is exceeded, kick the player with a friendly message and do not reveal internal details. Use DataStoreService:UpdateAsync for all persistent writes and never use SetAsync to replace the entire record. Save a data envelope containing at least `Data`, `SchemaVersion`, `LastSave`, `SessionId`, and metadata needed to detect the session owner. Include schema normalization/migration and deep default data, avoiding shared references between players. Explain in the code that MemoryStore is the operational authority for the lock while DataStore maintains an audit and recovery marker; do not treat DataStore as an instant lock. Protect all DataStore and MemoryStore calls with `pcall`, apply controlled retries for transient errors, and never delete or overwrite valid data in case of failure. Implement in-memory cache only on the session-owning server. Autosave must occur at a configurable interval, only for profiles marked dirty and only while the lease is valid. Before each critical write and before autosave, confirm that the session still owns the lock; if ownership is lost, block new mutations, log a detailed warn on the server, and remove/kick the player safely to avoid duplication. During PlayerRemoving and BindToClose, stop mutations, attempt to save within a time budget, release the lock only after the save attempt, and handle the fact that BindToClose has a limited time budget. Mandatory security: the client can never decide coins, damage, inventory, price, quantity, reward, or content to save. If I listed RemoteEvents, show an example of a strictly server-side handler that validates types, limits, player state, ownership, and cooldown before calling the mutation API. Do not accept inventory tables sent by the client as the source of truth. Do not expose mutable cache references to the client or other scripts without a safe copy. Include validation for finite numbers, balance/quantity limits, and protection against calls while the profile is not loaded or is shutting down. Deliver first a short section called `Architecture and assumptions`, followed by an Explorer tree with the two files and dependencies. Then provide the two files in full, each in its own markdown ` ```lua ` block, without pseudocode, without omitted snippets, and with clear comments in Portuguese. The code must be Luau-compatible with Roblox Studio, use `--!strict` when feasible, and declare easy-to-configure constants. At the end, include a section `How to test in Roblox Studio` with steps to enable API Services, simulate two servers via publishing/teleport or an appropriate test, test fast reconnection, active lock, lease expiration, DataStore failure, and shutdown. Also include an objective list of real limitations and production recommendations, without promising absolute guarantee against all distributed scenarios.