I am a game developer that usually gets 100+ concurrent players. My main concern is that I have reports of people losing data in my game.
I sometimes played the game myself and I rarely had issues of data stores. Sometimes it’s data not saving and sometimes data is wiped completely.
Here are my codes.
local DSS = game.DataStoreService
local DataStore = DSS:GetDataStore("PlayerData")
function AddPlayerData(Plr)
local PS = script.Data:Clone()
PS.Parent = Plr
local save = DataStore:GetAsync(Plr.UserId.."_data")
if save then
for i,v in pairs(PS:GetChildren()) do
if save[v.Name] then
v.Value = save[v.Name]
end
end
end
if PS.DataVersion.Value < 1 then
PS.DataVersion.Value = 1
end
end
game.Players.PlayerAdded:Connect(AddPlayerData)
function SaveData(Plr)
local Data = Plr.Data
local tabl = {}
for i,v in pairs(Data:GetChildren()) do
tabl[v.Name] = v.Value
end
--DataStore:SetAsync(Plr.UserId.."_data",tabl)
local OldData = DataStore:GetAsync(Plr.UserId.."_data")
if tabl then
DataStore:UpdateAsync(Plr.UserId.."_data", function(OldData)
local previousData = OldData or {["DataVersion"] = 1}
if previousData["DataVersion"] == nil then
previousData["DataVersion"] = 1
end
if Data.DataVersion.Value == previousData["DataVersion"] then
Data.DataVersion.Value += 1
tabl["DataVersion"] += 1
return tabl
else
return nil
end
end)
end
return Data
end
game:BindToClose(function()
for _,plr in pairs(game.Players:GetPlayers()) do
SaveData(plr)
end
end)
game.Players.PlayerRemoving:Connect(SaveData)
-- Note: The code below isn't added before the data bug reports. I didn't have it because there is "too many" data requests.
while task.wait(300) do
for i,Player in pairs(game.Players:GetChildren()) do
if Player:FindFirstChild("Data") and Player.Data:FindFirstChild("XP") then
SaveData(Player)
end
end
end
These codes are in one single script and is pretty much all the code in my game that loads and saves data.
Am I missing something from my scripts? Feel free to inform me.
So what is the error? Also keep in mind that the script is not 100% accurate to the original bits of the original script. I watered it down for simplicity reasons.
Instead of: | local save = DataStore:GetAsync(plr.UserId.."_data") |,
you should use [[in the Load function]] → | local Protection, save = pcall(function() return Datastore:GetAsync(plr.UserId.."_data") end) if not save then return end | in one single line, and | local Protection, save = pcall(function() return Datastore:SetAsync(plr.UserId.."_data", tabl) end) if not save then return end|
THAT way, pcall will loop your datastore:Getasync() or Datastore:SetAsync() without returning an error and does not need anything else AND your datastore script must contain both SetAsync to save and GetAsync to load ONCE
DataStore may error and also you did a pretty unsafe approach.
if you want already made professional module you can use ProfileStore
There is a lot reasons why your method is unsafe:
So the data not saving is caused by datastore errors, and pcalls would fix your issue, as @hunder305 mentioned. I will also mention that this loop you added
-- Note: The code below isn't added before the data bug reports. I didn't have it because there is "too many" data requests.
while task.wait(300) do
for i,Player in pairs(game.Players:GetChildren()) do
if Player:FindFirstChild("Data") and Player.Data:FindFirstChild("XP") then
SaveData(Player)
end
end
end
Can completely stop if the SaveData function errors (due to datastore errors), so make sure to either protect it will pcalls, threads (task.spawn() or other), or by making it so the SaveData(Player) function can’t error. The too many requests errors you are getting is possibly because you are saving every player’s data at the same time, you should add a wait of a couple of seconds in between each save, and see if that fixes it
However, the data wiping is usually caused by developers using object values, not checking if the data was loaded before saving. In your case, your code seems to be safe from that, because of the DataVersion check you have in the UpdateAsync function. So I really don’t know how data wiping is occurring in your game, and adding pcalls to your script might not be enough to fix that one
-- Uses exponential backoff to aleviate the load on roblox servers, if roblox goes down
local function GetRetryDelay(Retries)
return 2^Retries
end
function DatastoreModule:GetData(Key)
local Retries = 0
while true do
local Success, Result = pcall(function()
return Datastore:GetAsync(Key)
end)
if Success then
return Result
else
warn("[GetData]: "..Result)
Retries += 1
task.wait(GetRetryDelay(Retries))
continue
end
end
end
This is a simplified version of how I do it in my games. The way pcalls work is it returns two values, the first one is whether or not the function inside the pcall errored, and the second is usually what the function inside the pcall returned (here it would be the player data), but in the case of an error, it’s the error message
Hey, just wanted to give some recommendations to you!
Your data service, while functional is incredibly vulnerable to dataloss. I recommend you look into ProfileStore which is a newer successor to ProfileService, and if you’re set on writing your data service yourself you can look at all the safety measures this module uses and try recreating them yourself
don’t worry, it also uses protected call, your datastore still remains the same, however if you don’t use protected call, i’m afraid to tell you that it might get a data break, so imo, my option seems safer as it loops until it gets your save value and not just return nil, and if it does then pcall() will loop to redo - save until it is “tabl”