I am having an issue with data loss in my upcoming game. I received one report of data randomly being reset to zero from one of my QA testers. In addition to this, I joined my game once to test and suddenly realized that I had zero coins and my data had been completely reset.
The Code:
local DataStoreService = game:GetService("DataStoreService")
local statsStore = DataStoreService:GetDataStore("-stats2")
---------------------
game.Players.PlayerAdded:Connect(function(Player)
local leaderFolder = Instance.new("Folder")
leaderFolder.Name = "leaderstats"
leaderFolder.Parent = Player
local statsValue = Instance.new("NumberValue")
statsValue.Value = StartingAmt
statsValue.Name = StatsName
statsValue.Parent = leaderFolder
local lastEquipped = Instance.new("StringValue")
lastEquipped.Value = "nil"
lastEquipped.Parent = Player
lastEquipped.Name = "equippedTrail"
local oldData
local foundData,newplr = pcall(function()
oldData = statsStore:GetAsync(Player.UserId)
end)
if foundData and oldData ~= nil then
statsValue.Value = oldData.Stat
lastEquipped.Value = oldData.equip
end
end)
game.Players.PlayerRemoving:Connect(function(Player)
statsStore:SetAsync(Player.UserId,{
Stat = Player.leaderstats:FindFirstChild(StatsName).Value,
equip = Player.equippedTrail.Value,
})
end)
game:BindToClose(function()
if RunService:IsStudio() then
wait(1)
else
for _, Player in pairs(PlayersService:GetPlayers()) do
statsStore:SetAsync(Player.UserId,{
Stat = Player.leaderstats:FindFirstChild(StatsName).Value,
equip = Player.equippedTrail.Value
})
end
end
end)
Your code isn’t handling API errors, it just ignores them. An API error can be something out of your control, for example internal server error or datastores being down. This is how you should handle errors:
local success, response = pcall(function()
return statsStore:GetAsync(Player.UserId)
end)
if success then
if response then
print("The user has data:", response) --proceed as usual
else
print("The user joined for the first time!") --create their default data
end
else
print("An error occured:", response)
--if this condition is true, you need to handle it, not just ignore it
--you can handle it in many ways, for example remake the API call until success or kick the player
--if you kick them you should ensure their blank data wont override their actual one
end
Also the data might be set to zero if the player leaves too fast after joining, before their data loads. If the values(such as equippedTrail and Stat) are parented before their values are assigned, the SetAsync call of PlayerRemoving will replace player data with the default data of those values(for example 0).
There’re more issues when dealing with player data, but I wont mention them to avoid overcomplicating the current topic. If you feel you may be handling data unsafely, its a good idea to use community datastore libraries that handle those issues in the background for you(such as ProfileService).
Thank you! Do you think I can be confident that there won’t be any data loss in the future if I do everything you said besides switching to a community datastore library? I don’t really want to use one of these, but I will if you think it’s necessary. I can’t really test the data loss at all because it only happened twice randomly out of the thousands of times the game has been played.
It’s never guaranteed, servers can crash. However, you can minimize it by adding fallback methods.
For example, when data fails to load due to DataStore being down or being throttled, people will implement a retry logic and perhaps budgeting checks. If it doesn’t load after an arbitrary amount of time/tries, people will tend to kick or allow the player to play on a new, non saveable file.
The same thing applies for saving data. If it ends up failing, it is a good idea to let the player know that the data failed to save and to stay a little longer until it is successful. People implement auto saving at interval and on server shutdown. However, race conditions can happen when the player joins another server before the previous server finishes saving or a newer save call gets overrided by an older, retried save call, causing data discrepancy and potential data duplication, so some people implement what is called session locking and save queueing.
All of these features minimize the amount of failure and handles in case of one, which is what DataStore modules tend to address so you don’t have to write it yourself.
You could also add a autosave say every minute but only if that players data has changed since the last save. (This reduces calls to the datastore) You can do this with a simple Boolean for when data change occurs. Then check that in the autosave loop and also check on player exit of the game.
You should add a retry system where if the datastore fails to fetch the data it will tre-ry for x amounts then when that also fails you could do something like kick the player.
Edit: Also add a bindtoclose function for when the server shuts down with players remaining because player removing wont fire
Me and my buddies use these modules so we don’t have to worry about this like that: ProfileService or Suphi’sDS. I highly recommend the second one because it’s more performant and it can do more than profile service.
Why should you even use any of these? Well they specifically have session locking which is important. Session locking enables your datastore to be accessed by one server at a time meaning funny things like item dupes wont happen. Also, the biggest games on the platform use em (mainly profile service). There’s way more features im not mentioning you should research both