My DataStore randomly fails why? (New Developer)

I’ve been trying for several months to create a DataStore that won’t randomly fail, yet it seems like it’s impossible. Why?

Randomly peoples stats will reset or they’ll lose some of their credits (in-game currency) with out spending them.

I tried to revise the code but nothing seems to be work! I really need any advice you guys can give me because credits are important to the players and a couple of them have been getting mad at me & the game!

Here’s my leaderstat & DataStore system:

local DataBaseStore = game:GetService("DataStoreService"):GetDataStore("Saved")
local StatSholder = script.Stats


game.Players.PlayerAdded:Connect(function(NewPlayer)
 
 local Key = "Player-ID:" .. NewPlayer.userId
 local GetSave = DataBaseStore:GetAsync(Key)
 
 local DataBaseFolder = Instance.new("Folder", NewPlayer)
 DataBaseFolder.Name = "leaderstats"
 for i, Stats in pairs(StatSholder:GetChildren())do
  local NewStats = Instance.new(Stats.ClassName)
  NewStats.Name = Stats.Name
  NewStats.Value = Stats.Value
  NewStats.Parent = DataBaseFolder
 end
 if GetSave then
  for i, Stats in pairs(script.Stats:GetChildren())do
   DataBaseFolder[Stats.Name].Value = GetSave[i]
   print (i, Stats.Name, Stats.Value)
  end
  
 else
  
  local StatsToSave = {}
  
  for i, Stats in pairs(DataBaseFolder:GetChildren()) do
   table.insert(StatsToSave, Stats.Value)
   print(i, Stats.Name, Stats.Value)
  end
  DataBaseStore:SetAsync(Key, StatsToSave)
 end
 while wait(30) do
  local StatsToSave = {}
  
  for i, Stats in pairs(DataBaseFolder:GetChildren()) do
   table.insert(StatsToSave, Stats.Value)
   print(i, Stats.Name, Stats.Value)
  end
  DataBaseStore:SetAsync(Key, StatsToSave)
 end
game.Players.PlayerRemoving:Connect(function(OldPlayer)
 local Key = "Player-ID" .. OldPlayer.userId
 local StatsToSave = {}
  
  for i, Stats in pairs(DataBaseFolder:GetChildren()) do
   table.insert(StatsToSave, Stats.Value)
   print(i, Stats.Name, Stats.Value)
  end
  DataBaseStore:SetAsync(Key, StatsToSave)
end)
end)

More Info:
There’s a folder in the script named “Stats” and inside that folder there is a NumberValue named “Credits” (Not sure if this information would be helpful or not!)

2 Likes

You’re doing userId instead of UserId when you’re removing the player.

1 Like

This doesn’t matter. userId is a deprecated version of UserId for the sake of name convention consistency, but they still point towards the same value. userId should not be used but it will not cause a problem for saving, nor will anything change by going from camelCase to PascalCase.

1 Like

Sometimes, Datastore fails to save data for no reason (data loss). You should use DataStosre2 instead.

1 Like

I can see two major problems, which you probably overlooked due to poor indentation or some other kind of oversight. The first is that your entire code is wrapped inside the function listening to PlayerAdded and the second is that you have a non-terminating while loop (utilising bad practice) before you have a function listen to PlayerRemoving, so that function is never actually connected.

You need to separate all three of these out and send the autosave loop to its own thread. Spawn is generally fine to use in this scenario since you only need to have this run once and you aren’t performing any major operations with it.

Might as well take this time not only to revise your code, but to update it with some good practice. For example, when working with DataStores, you’ll want to strive to put your PlayerAdded function into a variable, connect it to PlayerAdded and then run it for all existing players. I’ve also removed redundancy for the most part.

local Players = game:GetService("Players")
local SaveStorage = game:GetService("DataStoreService"):GetDataStore("Saved")
local StatsHolder = script.Stats

-- You can just use a raw UserId as a key
local KEY = "Player-ID:%d"

-- Don't save data for players experiencing data issues
local blockSaving = {}

local function saveDataFolder(userId, folder)
    if not blockSaving[userId] then
        local saveData = {}
        local playerKey = KEY:format(userId)

        for _, data in ipairs(folder:GetChildren()) do
            saveData[data.Name] = data.Value
        end

        local success, ret = pcall(function ()
            return SaveStorage:SetAsync(playerKey, saveData)
        end

        if not success then
            warn(("Player data not saved for key \"%s\""):format(playerKey))
        end
    end
end

local function autosaveLoop()
    while true do
        for _, player in ipairs(Players:GetPlayers()) do
            local playerDataFolder = player:FindFirstChild("leaderstats")
            if playerDataFolder then
                saveDataFolder(player.UserId, playerDataFolder)
            end
        end
        wait(30) -- Haha, I forgot to add an interval to this loop
    end
end

local function onPlayerAdded(player)
    -- You really shouldn't use leaderstats as a data holder
    local dataFolder = Instance.new("Folder") -- Do not parent, do that last
    dataFolder.Name = "leaderstats"
    -- Removing unnecessary overhead by using holder as template
    for _, stat in ipairs(StatsHolder:GetChildren()) do
        local newStat = stat:Clone()
        newStat.Parent = dataFolder
    end

    local playerKey = KEY:format(player.UserId)
    -- You must use pcall to catch errors
    local success, data = pcall(function ()
        return SaveStorage:GetAsync(playerKey)
    end)

    if success then
        if data then
            for _, stat in ipairs(dataFolder:GetChildren()) do
                stat.Value = data[stat.Name]
            end
        else
            saveDataFolder(player.UserId, dataFolder)
        end
    else
        blockSaving[player.UserId] = true
    end

    dataFolder.Parent = player -- Forgot to parent this too
end)

local function onPlayerRemoving(player)
    local playerDataFolder = player:FindFirstChild("leaderstats")
    if playerDataFolder then
        saveDataFolder(player.UserId, playerDataFolder)
    end

    blockSaving[player.UserId] = nil
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
for _, player in ipairs(Players:GetPlayers()) do
    onPlayerAdded(player)
end

spawn(autosaveLoop)

Yes, against my will, I did provide you code but that’s only because you put in your own effort to try and write something that you believed would work. This is some fixed code for you to use. Obviously I have not written you a full DataStore handler; there are many missing features, things not accounted for and so on. This is all up to you to modify, break down, salvage and apply as you desire.


DataStores will never fail for no reason. There is always a causation for data failures: a majority of cases come from improper developer code, while the remaining cases come from endpoints going down or other outages on Roblox’s end.

DataStore2’s power, however, lies in the ability to fetch from backups. This is an ideal power. My code just blocks saving if the user’s data failed to load as a contingency. Not worth going into depth about backups or anything for primitive code.

3 Likes