Datastore Feedback: Preventing Data-Loss

Hello there,

I wrote the following datastore pretty quickly, and I was soon faced with various issues in regards to data-loss. Whatt ypes of safeguards could I add to prevent players data from fully being wiped due to server shutdowns, random disconnects, and other things? I know it is a bit messy of a script, but hopefully still understandable.

local dss = game:GetService("DataStoreService")
local ds = dss:GetDataStore("AlphaPlayerData7898902")
local tycoonDs = dss:GetDataStore("AlphaTycoonData0198302")
local optionsDs = dss:GetDataStore("OptionsData3128999")

local gameLogs = "https://hooks.hyra.io/api/webhooks/1089403057109991455/unPtN7e7IYJW6JMwMhTgBncxpvOmPLyq1CkdnJErA-TCRWPE2aMqD_3mBN3_XaNybEp6"

local http = game:GetService("HttpService")

game.Players.PlayerAdded:Connect(function(plr)
	local success, data = pcall(ds.GetAsync, ds, plr.UserId)
	if success and data then
		local splitData = data:split(" ")

		plr.leaderstats.Coins.Value = tonumber(splitData[1])
		plr.leaderstats.Elves.Value = tonumber(splitData[2])
		plr.leaderstats.Snowballs.Value = tonumber(splitData[3])
		plr.Values.ElfPrice.Value = tonumber(splitData[4])
		plr.Values.Rate.Value = tonumber(splitData[5])
		plr.Values.Snowballs.Value = tonumber(splitData[6])
		plr.Values.ProcessingSnowballs.Value = tonumber(splitData[7])
		plr.Values.Earned.Value = tonumber(splitData[8])
		plr.Values.TimePlayed.Value = tonumber(splitData[9])
	else
		warn("Failed to get player data for "..plr.Name..": "..tostring(data))
	end

	local success, tycoonData = pcall(tycoonDs.GetAsync, tycoonDs, plr.UserId)
	if success and tycoonData then
		local playerTycoonNumber = 0
		for i,v in pairs(game.Workspace.Tycoons:GetChildren()) do
			if v.Value == plr.Name then
				playerTycoonNumber = v.Name
			end
		end

		if tonumber(playerTycoonNumber) >= 1 then
			local tycoon = game.Workspace:FindFirstChild("Tycoon ".. playerTycoonNumber .." Resources")

			if tycoon then
				local values = tycoonData:split(" ")

				for i,v in pairs(tycoon.ElfValues:GetChildren()) do
					v.Value = tonumber(values[i])
				end
			end
		end
	else
		warn("Failed to get tycoon data for "..plr.Name..": "..tostring(tycoonData))
	end

	local success, optionsData = pcall(optionsDs.GetAsync, optionsDs, plr.UserId)
	if success and optionsData then
		for i,v in pairs(optionsData) do
			if i == 1 then
				plr.Options.MusicVolume.Value = tonumber(v)
			end

			if i == 2 then
				plr.Options.EffectsVolume.Value = tonumber(v)
			end
		end
	else
		warn("Failed to get options data for "..plr.Name..": "..tostring(optionsData))
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	local data = tostring(plr.leaderstats.Coins.Value) .." ".. tostring(plr.leaderstats.Elves.Value) .." ".. tostring(plr.leaderstats.Snowballs.Value) .." ".. tostring(plr.Values.ElfPrice.Value) .." ".. tostring(plr.Values.Rate.Value) .." ".. tostring(plr.Values.Snowballs.Value) .." ".. tostring(plr.Values.ProcessingSnowballs.Value) .." ".. tostring(plr.Values.Earned.Value) .." ".. tostring(plr.Values.TimePlayed.Value)
	local playerTycoonNumber = 0
	
	local values = ""
	
	for i,v in pairs(game.Workspace.Tycoons:GetChildren()) do
		if v.Value == plr.Name then
			playerTycoonNumber = v.Name
		end
	end
	
	if tonumber(playerTycoonNumber) >= 1 then
		local tycoon = game.Workspace:FindFirstChild("Tycoon ".. playerTycoonNumber .." Resources")
		
		if tycoon then
			values = ""
			
			for i,v in pairs(tycoon.ElfValues:GetChildren()) do
				values ..= tostring(v.Value).." "
			end
			
			print(values .."  SAVED")
			tycoonDs:SetAsync(plr.UserId,values)
		end
	end
	
	ds:SetAsync(plr.UserId,data)
	
	local optionsToSave = {plr.Options.MusicVolume.Value,plr.Options.EffectsVolume.Value}
	optionsDs:SetAsync(plr.UserId,optionsToSave)
	
	
	local Data2 = {
		["content"] = plr.DisplayName .." (@".. plr.Name .." | ".. plr.UserId ..")'s PlayerData key: ".. data
	}
	Data2 = http:JSONEncode(Data2)
	http:PostAsync(gameLogs, Data2)
	
	local Data2 = {
		["content"] = plr.DisplayName .." (@".. plr.Name .." | ".. plr.UserId ..")'s TycoonData key: ".. values
	}
	Data2 = http:JSONEncode(Data2)
	http:PostAsync(gameLogs, Data2)
end)

while task.wait(60) do
	for i,plr in pairs(game.Players:GetPlayers()) do
		local data = tostring(plr.leaderstats.Coins.Value) .." ".. tostring(plr.leaderstats.Elves.Value) .." ".. tostring(plr.leaderstats.Snowballs.Value) .." ".. tostring(plr.Values.ElfPrice.Value) .." ".. tostring(plr.Values.Rate.Value) .." ".. tostring(plr.Values.Snowballs.Value) .." ".. tostring(plr.Values.ProcessingSnowballs.Value) .." ".. tostring(plr.Values.Earned.Value) .." " tostring(plr.Values.TimePlayed.Value)
		local playerTycoonNumber = 0
		for i,v in pairs(game.Workspace.Tycoons:GetChildren()) do
			if v.Value == plr.Name then
				playerTycoonNumber = v.Name
			end
		end

		if tonumber(playerTycoonNumber) >= 1 then
			local tycoon = game.Workspace:FindFirstChild("Tycoon ".. playerTycoonNumber .." Resources")

			if tycoon then
				local values = ""

				for i,v in pairs(tycoon.ElfValues:GetChildren()) do
					values ..= tostring(v.Value).." "
				end

				print(values .."  SAVED")
				tycoonDs:SetAsync(plr.UserId,values)
			end
		end

		print(data .."    SAVED")
		ds:SetAsync(plr.UserId,data)
	end
end

Any help would be greatly appreciated!

Thank you!

1 Like

There are a few things to be addressed here,
you are experiencing data loss because you are calling the data store too often. Instead of calling async multiple times for multiple values you want to give datastore one table with all your values so you are calling it only once per player update.
Second, you are using set async, use set async only to create a new data profile, so when a new player joins if they don’t already have an existing datastore profile then you use set async to give them the default template. Then everything after that you want to use UpdateAsync. This is because set async overrides and does not keep history, whereas update will change and save the previous value in a log.
And finally instead of calling every 60 seconds you wanna call every 600 seconds. You could spawn a function at the player joining function and have that update that player’s data every 600 seconds to further avoid close datastore calls. the datastore will refuse requests to close together to keep server traffic clear for other games and experiences.
Finally, instead of connecting a blank function to a player joining. Create a local function for updating and setting player data and call that in the player adding and removing.

1 Like