Rare issue where all datastores will wipe

Hi,
Had a look around for similar posts, but haven’t found too much similar to my situation.

I have about 3/4 datastores in my game, some longer than others, and theres a very rare bug in which every single datastore for one particular player will wipe. For reference when the game had been launched for about a day and a half, one person complained of all data resetting. The first time I experienced this issue, I looked into the problem of using SetAsync and found using UpdateAsync was better for data loss, however it didn’t yield much improvement.

Other than me possibly setting up UpdateAsync wrong (Although im pretty sure its right as data loads and saves correctly majority of the tiem), does anyone have any ideas that could be causing this? Il attach below the relevant UpdateAsync code of my level saving script.

local function SavePlayerData(player)

	local success, errormsg = pcall(function()

		local SaveData = {}

		for i, stats in pairs(player.stats:GetChildren()) do

			SaveData[stats.Name] = stats.Value
		end 
		SaveDataStore:UpdateAsync(player.UserId, function()
			return SaveData
		end)
	end)

	if not success then 
		return errormsg
	end
end

Players.PlayerRemoving:Connect(function(player)

	local errormsg = SavePlayerData(player)

	if errormsg then    
		warn(errormsg)      
	end 
end)

game:BindToClose(function()
	for i, player in pairs(Players:GetPlayers()) do 

		local errormsg = SavePlayerData(player)
		if errormsg then
			warn(errormsg)
		end         
	end
	wait(2) 
end)

Thanks in advance

3 Likes

This may be due to the loading aspect of your datastore, whereas your value instances are their default values because they were never changed. If this were to happen, the save function wouldn’t error because the values still exist. Therefore, I would recommend making sure that the loading function works first.

If UpdateAsync isn’t working, you’d be able to see the error message. If this is the case the error would be useful to put on this topic. :smiley:

It is possible that in the scenario where the player acutely stops roblox. The player object gets deleted before you can iterate through all the values of the player.

To stop the data loss, I suggest you should create variables for every property of the player you need, and store the data within the script inside a table per player. (It wouldn’t delete with the player as soon as the player leaves).
That should stop the data loss issue.

2 Likes

I think you’re right but I’ll try to give an example for the sake of the OP (because your explanation is hard to understand if you are a beginner)

let’s say a script is trying to save a level number of 26 into a DataStore_A In an obby game
And DataStore_A already had the number of 23 saved into it when the player first joined
There are two outcomes:
The script saves and the number 26 gets put into DataStore_A
or
the script errors at some point (Sometimes this is because of a script,or sometimes Roblox servers can go down), causing nothing to be saved. Since DataStore_A was never touched it still has the number 23 saved into it. When the player joins that game again, They will be put back to stage 23, even though they have beaten every stage up to stage 26. In this case, the player doesn’t lose all their data, just the data in their latest secession.

But if the datastore errors when it first gets the data

Datastore_A has a value of 23 saved in it
The Player Joins the game but the script that loads stage data breaks.
Since the script couldn’t get data from Datastore_A it puts the player at stage 1
This causes the player to start at stage 1.
If the player rage quits/goes to report the bug and leaves the game then the game will save the stage number into Datastore_A
Even though Datastore_A has data saying that the player is on stage 23, The game will save into Datastore_A that the player is on stage 1. Making it so the player is stuck on level 1, Completely resetting their data.

So since you are having problems with complete data resets, You’re probably having problems when the data first loads in. The easy solution to this is just to make so if the script errors when it tries to load in the data when the player first joins a server, to make that the game doesn’t save that incorrect value back into the system.

2 Likes

The following scenario may take place:

  1. user data fails to load.
  2. the loading part of your script handles it by setting the in-game value to 0(assuming it’s a new user)
  3. your script saves the new value when the player leaves(causing the destruction of their old data)

A good workaround is to have some sort of “DataLoaded” value inside your player in-game values and instruct the script NOT TO SAVE the user data if it’s set to false before they leave.
the way you load data should be simillar to this:
data success?
if yes and data empty > defaults, DataLoaded gets set to True
if no > DataLoaded remains false(and maybe retry loading their data after an amount of seconds)

I consider datastores a hard problem to solve, and if you feel unable to successfully manage the data of a large player-base you should instead consider using a library such as Datastore2.

4 Likes

Thanks, yeah I get what you mean by this, but what is the alternative? surely if the script errors on loading and doesnt have the correct value to save, how could you manually get it to reobtain that value?

Interesting, so worst case scenario they just get reverted back to a ‘checkpoint’ as opposed to a complete wipe. Its worth noting (Replying this to everyone) that i occasionally get that warning message of ‘DataStore request was added to queue. If request queue fills, further requests will be dropped. Try sending fewer requests.Key’ But forgot to mention this - However ive been told its quite common to get that message and not neccessarily something to worry about?

Secondly, if im correct, in this scenario, reloading the data would just be a repeat of this:


if Data then

		for i, stats in pairs(Stats:GetChildren()) do

			stats.Value = Data[stats.Name]
		end     

	else        
		print(player.Name .. " has no data.")           
	end

After a small wait, am I correct?

Thirdly, Ive heard that Datasstore2 can be more complex, is that correct?

I have also just thought, if i have multiple datastores in different scripts, how is it the case that they all fail at once? With this in mind does it change the chances of this actually being the issue?

Reloading the data would be the same using :GetAsync() on your datastore while “pasting” all the data to your different Value Objects is what you show there.

Heres a good method of loading your data:

local function LoadPlayerData(player)
	local datastore_service = game.DataStoreService
	local player_data = datastore_service:GetDataStore('PlayerData')
	local client_data do --//plain "Do" is used here for organization, it makes variable initialization easier to read
		local datastore_id = tostring(player.UserId)
		local tries = 5 --//You can set this to math.huge if you want it to try forever

		for i = 1,tries do --//Will try until the i == tries or client_data is returned
			local success = pcall(function()
				client_data = player_data:GetAsync(datastore_id)
			end)

			if (success == true) then
				if (client_data == nil) then
					--//Initial Data
					client_data = {

					}
				else
					break
				end
			end
		end
	end

	--//Setting Loaded Data
	for i, stats in pairs(Stats:GetChildren()) do
		stats.Value = client_data[stats.Name]
	end

	print('Data successfully loaded!')
end

According to GlobalDataStore | Roblox Creator Documentation, If your not saving your data on multiple servers at the same time, you don’t have to use “UpdateAsync()” because your only saving data on the server that the player is in.

Heres a good method of saving your data:

local function SavePlayerData(player)
	local datastore_service = game.DataStoreService
	local player_data = datastore_service:GetDataStore('PlayerData')

	local client_data = {} do
		for i, stats in pairs(player.stats:GetChildren()) do
			client_data[stats.Name] = stats.Value
		end 
	end
	local datastore_id = tostring(player.UserId)
	local tries = 5 --//You can set this to math.huge if you want it to try forever

	for i = 1,tries do --//Will try until the i == tries or when client_data is saved
		success, error = pcall(function()
			player_data:SetAsync(datastore_id, client_data)
		end)

		if (success == true) then
			print('Data successfully saved!')

			break
		else
			print(error)
		end
	end
end

Datastore2 is more complex with how it handles its data, and it makes it eisier for more complicated datastores, but a simple saving and loading system doesen’t need Datastore2.

I noticed that you were using game.BindToClose and game.Players.PlayerRemoving at the same time, which may impact the saving of your data. This is the case becuase in studio, PlayerRemoving doesen’t fire when you do a single playertest, while :BindToClose() does, demonstrating that you don’t need BindToClose in-game because PlayerRemoving always fires in-game.

BindToClose, only useful to save data in Roblox Studio:

if game:GetService('RunService'):IsStudio() then
	game:BindToClose(function()
		for i, player in pairs(Players:GetPlayers()) do 

			local errormsg = SavePlayerData(player)
			if errormsg then
				warn(errormsg)
			end         
		end
		wait(2) 
	end)
end
4 Likes

Very informative response, thanks very much. Il try and make some changes according to this advice, and come back to mark as solution if its sorted!!

1 Like