Data handling, players losing data "often"

So occasionally people seem to lose all their data in my game. I made a previous post but it was about the different methods…

Right now this is what I have for my code.

player joins:

game.Players.PlayerAdded:Connect(function(player)
	
	
	local success,data = pcall(function()
		return Data:GetAsync(prefix  .. tostring(player.UserId))
	end)
	if success then
		if data then
			local plrData = Data:GetAsync(prefix  .. tostring(player.UserId))
			print("loading in data")
			Coin.Value = plrData[1] or 0
			xp.Value = plrData[2] or 0
			Level.Value = plrData[3] or 1
			-- weapons
			weapons.Axe.Owns.Value = plrData[4] or 0
			weapons.Sabre.Owns.Value = plrData[5] or 0
			weapons.Rifle.Owns.Value = plrData[6] or 0
			weapons.Musket.Owns.Value = plrData[7] or 1
			weapons.Horse.Owns.Value = plrData[8] or 0
			weapons.Flintlock.Owns.Value = plrData[9] or 0
			weapons.Carbine.Owns.Value = plrData[10] or 0
			weapons.Lance.Owns.Value = plrData[11] or 0
			weapons.Bandage.Owns.Value = plrData[12] or 0
		else
			print("no data")
			Level.Value = 1
			weapons.Musket.Owns.Value = 1
			Data:SetAsync(prefix  .. tostring(player.UserId), {
		        Coin.Value,
				xp.Value,
				Level.Value,
				weapons.Axe.Owns.Value,
				weapons.Sabre.Owns.Value,
				weapons.Rifle.Owns.Value,
				weapons.Musket.Owns.Value,
				weapons.Horse.Owns.Value,
				weapons.Flintlock.Owns.Value,
				weapons.Carbine.Owns.Value,
				weapons.Lance.Owns.Value,
				weapons.Bandage.Owns.Value
		    })
		end
	else
		print("Data store working, but no data ever made for this player")
	end

	
end)

function saveData(player)
	
	local playerStats = game.ServerStorage.PlayerStats[player.UserId]
	local pfolder = game.ServerStorage.PlayerStats:FindFirstChild(player.UserId)
	local spfolder = game.ReplicatedStorage.PlayerStats:FindFirstChild(player.UserId)
	local weapons = playerStats:WaitForChild("Owned")
	
	local tries = 0	
	local success
	repeat
		tries = tries + 1
		success = pcall(function()
			--print("saved")
			Data:SetAsync(prefix  .. tostring(player.UserId), {
				playerStats.Coin.Value,
				playerStats.xp.Value,
				playerStats.Level.Value,
				weapons.Axe.Owns.Value,
				weapons.Sabre.Owns.Value,
				weapons.Rifle.Owns.Value,
				weapons.Musket.Owns.Value,
				weapons.Horse.Owns.Value,
				weapons.Flintlock.Owns.Value,
				weapons.Carbine.Owns.Value,
				weapons.Lance.Owns.Value,
				weapons.Bandage.Owns.Value
			})
			wait(2)
			if spfolder ~= nil then
				spfolder:Destroy()
			end
			if pfolder ~= nil then
				pfolder:Destroy()
			end
		end)
		if not success then wait(1) end
	until tries == 3 or success
	if not success then
		warn("Cannot save data for player!")
	end
	
	
end

game:BindToClose(function()
    for _, client in ipairs(game:GetService("Players"):GetPlayers()) do
        spawn(function()
            saveData(client)
        end)
    end
end)

game.Players.PlayerRemoving:Connect(function(player)
	saveData(player)
end)

I also have an autosave every 2 minutes, but I’m disabling that for now.

If there is a post already posted similar, sorry.

Something similar I found - Edit

1 Like

Your problem is that your datastore is maxing out the allowed save requests. I would recommend only saving when the players data changes. Also change your data to be in tables as right now each line is saving as a different datastore.

Use :UpdateAsync when updating players data.

1 Like

What do you mean different datastore each time?

I always have 240 available requests but dataloss still happens.
I feel like datastores are generally unreliable. Sometimes they just don’t work, othertimes they take over a minute to process.

1 Like

So based on my code, there really is no way on improving this to avoid data loss?

You could do two things:

  1. Kick a player if he fails to load (or atleast retry to load).
    Don’t allow a player who’s failed to load inventory join because when he leaves he’ll override his actual saved inventory and loose stuff.
  2. Use serverchat to see if a player is currently being saved on another server.
    If so, do not allow him to load until it’s saved. To be safe just kick him when he tries to load.

Using too many requests where not necessary. You fetch player data twice; once in the pcall and another time right below it. You use SetAsync when a player has no data, through an auto save loop, on BindToClose and PlayerRemoving. Most code is shoved into pcalls when you should only be having DataStore requests in the pcalls. In the first pcall, you treat a success false as “DataStore working” when that is actually the opposite of what is happening. You have a retry function despite DataStores supporting this natively.

I’d say you have quite a bit of refactoring to do.

game:BindToClose(function()
    for _, client in ipairs(game:GetService("Players"):GetPlayers()) do
        spawn(function()
            saveData(client)
        end)
    end
end)

The way you wrote this BindToClose, it will return immediately. And once the BindToClose returns, the game will shut down. That means those saveData calls in it probably aren’t happening at all. You need to wait until all of those spawned saveData calls complete before returning from the BindToClose hander.

Here is some example code on how to do that: BindToClose & Data Loss Risk - #2 by Tiffblocks, I would follow that because it’s not particularly easy to write that code correctly in a way that handles errors.

2 Likes

I would recommend you to use UpdateAsync

Alright, well I had some help from sircfenner and I wanted to break this down step by step.
First apparently, when the data would be found it would be successful, but then I looked for their data again. If there was no data, then I also saved data even if a player’s data never even loaded.

	local success,data = pcall(function()
		return Data:GetAsync(prefix  .. tostring(player.UserId))
	end)
	if success then
		if data then
			print("loading in data")
			Coin.Value = data[1] or 0
			xp.Value = data[2] or 0
			Level.Value = data[3] or 1
			-- weapons
			weapons.Axe.Owns.Value = data[4] or 0
			weapons.Sabre.Owns.Value = data[5] or 0
			weapons.Rifle.Owns.Value = data[6] or 0
			weapons.Musket.Owns.Value = data[7] or 1
			weapons.Horse.Owns.Value = data[8] or 0
			weapons.Flintlock.Owns.Value = data[9] or 0
			weapons.Carbine.Owns.Value = data[10] or 0
			weapons.Lance.Owns.Value = data[11] or 0
			weapons.Bandage.Owns.Value = data[12] or 0
		else
			print("no data")
			Level.Value = 1
			weapons.Musket.Owns.Value = 1
		end
	else
		print("Data store fail!")
	end

So I believe that part is fixed.

1 Like