You should consider making the system data-fail-safe. If a player who is level 5 loads in, and his/her data fails to load in properly (because GetAsync doesn’t work 100% of the time), then the PlayerAdded method will error, meaning the Lvl IntValue will be at its default value, 0 (because it was never properly set). Once the player leaves, his/her level will be set to 0, which is not what you want.
To combat this, you should try wrapping your Async calls in pcall functions. Personally, whenever a GetAsync call fails, I kick the player and add them to a table of players to ignore, so when the PlayerRemoving method is run, it doesn’t save the player’s data. Then, after they’re gone, I remove them from the table (just in case they join again).
There are many ways to do this, that’s just the logic for the approach I use.
While I’m sure you’ve read this article before, the Data Stores article gives some good information regarding protective calling.
He means that DataStores can fail because the service can be down, so data isn’t loaded but when it is up again the level 0 value will be saved to the player (level 5 previously) when the player leaves.
(Not actual code)
PlayerAdded:
Create Value (Default 0)
Value.value = GetData() — crashes so stays at 0
PlayerRemoving:
SaveData(Value) — Value is still 0 and this might not fail because the server is online again.
What I do is create a table of all datastores in game, when playerAdded, I run a loop to iterate each datastore, if any fails, do a few retries, when all datastores are readed correctly without failure, I let the player start to play (I cancel the starting characterLoad until all datastores are loaded) And continue with the loop until all datastores loaded, if keeps failing the player only see “Loading” on screen, until all of them loaded.
Saving all the data gotten from the datastore in a server module, in a table using the userId as the key, and what datastore that data belongs.
When playerRemoved, I check that module table using the userId key, and only save each datastore if theres real data in that module table, otherwise, save nothing.
If the datastore saving fails, retry as many times as needed until succed, only when succed remove the player entry from the module table
Some stuff that you can do : #1.)Save data in tables rather than using different keys.This will save you some Datastore Requests #2.)Use UpdateAsync() rather than SetAsync().It can prevent Data loss if used wisely. #3.)make it recursive with limits. What I mean here is simply retrying to save the data just in case the Request Fails while adding a limit to it. #4.) Make these actual functions so that you need not repeat yourself for saving or loading data in different places,you would have to edit only one function if errors arise and lastly it makes your code more readable.
Im not the best in this topic but I’ve never had issues with datastores, here’s some comments about your script
DataStoreService = game:GetService("DataStoreService")
Data = DataStoreService:GetDataStore("PlayerStuff")
ReplicatedStorage = game:GetService("ReplicatedStorage")
Player = game:GetService("Players")
local Player_ = "Player_"
DataKey = {}
Player.PlayerAdded:Connect(function(p)
local l, s = Instance.new("Folder", p), Instance.new("Folder", p)
l.Name = "leaderstats"
s.Name = "saved_stats"
local Lvl = Instance.new("IntValue", l)
Lvl.Name = "Level"
local EXP = Instance.new("IntValue", s)
EXP.Name = "EXP"
--local DataCache -- WHERE?
local Success, Data = pcall(function()
-- IF GET ASYNC FAILS WHATS DataKey[p.UserId] ?
DataKey[p.UserId] = Data:GetAsync(p.UserId, DataKey[p.UserId])
end)
if Success then
-- IF SUCCESS MEANS SUCCESSFUL CONNECTION, DOESNT MEAN PLAYER HAS DATA
-- if Data then (do stuff) else (new player) end
print("Loaded Player Data:", DataKey[p.UserId])
else
-- WHERES THE RETRIES TO READ DATASTORE BEFORE LET PLAYER TO PLAY IF GetAsync Failed?
DataKey[p.UserId] = {
["Level"] = 1,
["EXP"] = 0,
["Money"] = 500
}
end
--p:WaitForDataReady() -- WHERE?
Lvl.Value = DataKey[p.UserId]["Level"] -- OUT
EXP.Value = DataKey[p.UserId]["EXP"] -- OUT
Lvl:GetPropertyChangedSignal("Value"):Connect(function()
DataKey[p.UserId]["Level"] = Lvl.Value
end)
end)
Player.PlayerRemoving:Connect(function(p)
-- A CHECK TO ACTUALLY KNOW THERES DATA TO SAVE DataKey[p.UserId]?
local success, errorm = pcall(function()
Data:SetAsync(p.UserId, DataKey[p.UserId])
end)
if success then
print("Successful Save")
else
warn("Data Failed to Save For Player_"..p.UserId.."When Leaving")
end
DataKey[p.UserId] = nil
end)
On first try, if DSS fails you are not retrying to read it. If it succeded, you are not sure if player has real data or not.
On saving, you dont actually know if theres real data from the DSS reading, just “hardcoded” saving, if the data is not real, because you created it on playerJoin, you could lose the real player’s datastore.
Of course that will save the data either way, a possible empty data.
Read the post I made in your thread, I suggest DO NOT let the player to spawn/play before all datastores has been readed correctly, if not, retry
local DSS = game:GetService("DataStoreService")
local Data = DSS:GetDataStore("Data")
local function ReadDSS(p)
local Success, Data = pcall(function()
return Data:GetAsync(p.UserId)
end)
if not Success then
-- Retry count
warn("DSS roblox service failed... retry")
local retry = 0
-- Retry
while retry < 21 do -- retry fixed amount of times
warn("retry to read", retry)
Success, Data = pcall(function()
return Data:GetAsync(p.UserId)
end)
-- If error happens
if not Success then
retry += 1
wait(2) -- cooldown
else
print("finally got a read on retry:", retry)
break
end
end
end
if Success then
print("Connected to DSS roblox service")
if Data then
print("player has data in DSS, old player")
print("store data in server table, let player to continue")
else
print("player has no data, new player")
print("store starting values in server table")
end
end
end