Data stores are a service offered to developers which allow them to save data and fetch it from any server in the universe (game), and are essentially structured as associative arrays.
Data stores are structured similarly to dictionaries/records. They consist of key-value pairs. Each key is unique and acts like a "header" or "identifier", if you like, and can hold a value that can be converted or serialised into JSON. This means that you cannot explicitly set a key's value to a userdata (which includes but is not limited to Instances), and can only save primitive types (numbers, booleans, strings and tables containing only these types).
- PlayerDataStore
98401192
|{Currency = 100, IsAdmin = false}
46383457
|{Currency = 238, IsAdmin = true}
The above example is a representation of a data store that holds a collection of key-value pairs which hold player data. In the context of saving player data, the key should be a unique identifier relevant to the corresponding player, hence you can use the player's UserId
.
You can fetch data from a data store using the GetAsync
method. It takes a string as it's key parameter, which will be used as a unique identifier for your data.
-
Method
variant GetAsync(string Key)
-
Sample call
local PlayerDataStore = game:GetService("DataStore"):GetDataStore("PlayerDataStore") game.Players.PlayerAdded:Connect(function(player) local playerData = PlayerDataStore:GetAsync(player.UserId) end)
You can set a key's value using the SetAsync
method. It takes the key whose value will be set, and a primitive value as the arguments.
-
Method
void SetAsync(string Key, variant Value)
-
Sample call
local PlayerDataStore = game:GetService("DataStore"):GetDataStore("PlayerDataStore") local ServerPlayerData = {} game.Players.PlayerRemoving:Connect(function(player) PlayerDataStore:SetAsync(player.UserId, ServerPlayerData[player]) end)
There are other member functions available to us which allow us to "Update" a data store field, but these are rarely needed and you can find them on the wiki here.
Data store requests are prone to errors, and to have good design you must be able to handle errors effectively whenever they happen. Something as simple as wrapping the request around a pcall()
and repeating the request after an arbitrary interval should do the trick.
Data stores are assigned a request budget. If the number of requests made exceed this budget, the data store will throttle.
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local PlayerDataStore = DataStoreService:GetDataStore("PlayerDataStore")
local RETRY_LIMIT = 3
local RETRY_INTERVAL = 5
local function GetPlayerData(userId)
local success, e = pcall(PlayerDataStore.GetAsync, PlayerDataStore, userId)
-- Return player data if the request was successful.
if success then return e end
-- Check if the data store has throttled, if so, wait until the request budget is increased.
while DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.GetAsync) < 1 do
wait(1)
warn("Data store budget depleted")
end
local currentRetries = 0
-- Repeat the request until the request is successful or the request limit is reached.
repeat
wait(RETRY_INTERVAL)
currentRetries = currentRetries + 1
success, e = pcall(PlayerDataStore.GetAsync, PlayerDataStore, userId)
if success then
return e
end
until currentRetries == RETRY_LIMIT
end
Players.PlayerAdded:Connect(function(player)
local playerData = GetPlayerData(player.UserId)
end)
You've probably found youtube videos that recommend you assign multiple keys to one specific value, even the wiki is guilty of this:
DataStore:SetAsync(player.UserId .. "_cash", player.Cash.Value)
It's simply bad design, you should be saving one dictionary per player, which contains all of the player's data. This reduces the total number of requests made, and consequently reduces the risk of throttling. This method also allows for scalability.
local PlayerDataSchema = {
General = {
Currency = 0,
Experience = 0
},
Moderation = {
AdminStatus = "Normal",
ModerationHistory = {},
ModerationStatus = "None"
},
PurchaseLog = {}
}
If you're familiar with OOP you could also save an object, do whatever is intuitive for you.
Remember to maintain good error handling, scalability and always design your data stores intuitively. If your game is in production and your data stores are designed poorly, you may have issues later down the line.