Is there a way to make this datastore script even MORE reliable?

A friend of mine made this, and im worried about its ability to reliably save and get data. Ive had issues with it and im scared to launch the game with this broken datastore. is there anyway i can make it more reliable? (Idk how to use datastore 2)

local RS = game:GetService("ReplicatedStorage")

local SendsAndGets = RS:WaitForChild("SetVals")

local DSS = game:GetService("DataStoreService")
local SaveToDS = DSS:GetDataStore("InvisLeaderstatsDS1")

local ValsToSaveAndRetrieve = {"OverheadName", "PlayerFindable", "NotifLocal"}

local function GetData(player)

	local invisleaderstats = Instance.new("Folder")
	invisleaderstats.Name = "invisleaderstats"
	invisleaderstats.Parent = player

	local OverheadName = Instance.new("StringValue")
	OverheadName.Name = "OverheadName"
	OverheadName.Value = ""
	OverheadName.Parent = invisleaderstats
	
	local PlayerFindable = Instance.new("BoolValue")
	PlayerFindable.Name = "PlayerFindable"
	PlayerFindable.Value = false
	PlayerFindable.Parent = invisleaderstats
	
	local notif = Instance.new("StringValue")
	notif.Name = "NotifLocal"
	notif.Value = "BR"
	notif.Parent = invisleaderstats
	
	

	local data = nil
	local success, errormessage = pcall(function()

		data = SaveToDS:GetAsync(player.UserId.." | ILSdataVAL")

		if data ~= nil then
			print(data)
		else
			print("No data there welp!")
		end

	end)


	if success then

		for i = 1, #ValsToSaveAndRetrieve do

			if typeof(data) == 'table' then

				if data[ValsToSaveAndRetrieve[i]] ~= nil then

					player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]).Value = data[ValsToSaveAndRetrieve[i]]
					print("ILS - "..player.Name.." ("..player.DisplayName..", "..player.UserId.."): The data of "..ValsToSaveAndRetrieve[i].." was retrieved!")

				else

					warn("ILS - "..player.Name.." ("..player.DisplayName..", "..player.UserId.."): The data of "..ValsToSaveAndRetrieve[i].." was not retrieved!")
					
					if player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("IntValue") then

						player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]).Value = 0

					elseif player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("StringValue") then

						player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]).Value = ""

					elseif player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("BoolValue") then

						player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]).Value = false

					end

				end

			else

				warn("ILS - "..player.Name.." ("..player.DisplayName..", "..player.UserId.."): The data of "..ValsToSaveAndRetrieve[i].." was not retrieved!")
				
				if player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("IntValue") then

					player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]).Value = 0

				elseif player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("StringValue") then

					player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]).Value = ""

				elseif player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("BoolValue") then

					player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]).Value = false

				end

			end

		end

	else

		print("ILS - "..player.Name.." ("..player.DisplayName..", "..player.UserId.."): The data was not retrieved!")
		warn("ILS - "..errormessage)

		player:Kick("ILS - Player data failed to load or is corrupted. Please rejoin!")
	end



	for i = 1, #ValsToSaveAndRetrieve do

		if player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("IntValue") then

			local Newval = Instance.new("IntValue")
			Newval.Parent = RS
			Newval.Name = ValsToSaveAndRetrieve[i].."val | "..player.UserId

		elseif player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("StringValue") then

			local Newval = Instance.new("StringValue")
			Newval.Parent = RS
			Newval.Name = ValsToSaveAndRetrieve[i].."val | "..player.UserId

		elseif player:WaitForChild("invisleaderstats"):WaitForChild(ValsToSaveAndRetrieve[i]):IsA("BoolValue") then

			local Newval = Instance.new("BoolValue")
			Newval.Parent = RS
			Newval.Name = ValsToSaveAndRetrieve[i].."val | "..player.UserId

		end

	end

end


local function SaveData(player)
	
	local SavedData = {}

	for i = 1, #ValsToSaveAndRetrieve do

		SavedData[ValsToSaveAndRetrieve[i]] = RS:WaitForChild(ValsToSaveAndRetrieve[i].."val | "..player.UserId).Value

		print(ValsToSaveAndRetrieve[i].. " = ".. tostring(RS:WaitForChild(ValsToSaveAndRetrieve[i].."val | "..player.UserId).Value))

	end

	print(SavedData)

	local success, errormessage = pcall(function()

		SaveToDS:SetAsync(player.UserId.." | ILSdataVAL", SavedData)

	end)	

	if success then

		local RSChildren = RS:GetChildren()

		print("ILS - "..player.Name.." ("..player.DisplayName..", "..player.UserId..") has saved their data!")
		for i = 1, #RSChildren do

			if string.find(RSChildren[i].Name, player.UserId) then

				RSChildren[i]:Destroy()
				print("killed "..RSChildren[i].Name)
			end	

		end

	else

		print("ILS - There was nothing saved!")
		warn(errormessage)

	end
		
end

game:GetService("Players").PlayerAdded:Connect(GetData)


game:GetService("Players").PlayerRemoving:Connect(SaveData)
game:BindToClose(function()
	for _, v in pairs(game:GetService("Players"):GetPlayers()) do
		SaveData(v)
	end
end)

local event = game.ReplicatedStorage.Geodude

event.OnServerEvent:Connect(function(plr)
	GetData(plr)
end)

Thank you if you can help me learn to fix this, i dont know how to use datastore, and i dont want to make the creator sad by basically saying “Your datastore is bad, fix it”

3 Likes

Your implementation looks fine. There’s a couple of notes I want to mention.

  1. I’d avoid kicking the player unless it’s absolutely necessary. If your :GetAsync fails, you should implement a block of code to keep trying until it succeeds. While this is happening, have your player sit in a “loading” screen. Once the data is properly loaded and passes all the checks, you can complete the loading screen and let the player play.
local function tryGetUntilSuccess(player, saveKey)
	local MAX_TRIES = 100
	
	while player.Parent do
		local success, value = pcall(function()
			return game:GetService("DataStoreService"):GetDataStore("PlayerDataStore"):GetAsync(saveKey)
		end)
		if success then
			return value
		end
		wait(2)
	end
end
  1. Do the same thing as 1 for :SetAsync. You need to keep trying to save until the bind to close limit is reached or your save file is successful. In your code you try only once and give up with a warning message if it fails. The player’s data is valuable! They will not be happy after spending hours playing and losing progress!
    If you do a game where your player manually clicks to leave the session, like Deepwoken. You can have the server not teleport the player until the data saves successfully, similar to loading in.
  1. Data validation is important. What happens when you join a new server then return back to an older server? The data structure might’ve changed or it could become corrupted. You need to figure out how you want to deal with issues like this.
    Some games apply aggressive server version checking and kick players, while others simply shut down all old servers. Regardless, always assume the data is corrupted until you’ve checked.

  2. Datastore2 is great if you’re a beginner and just want to use it and not worry about things. If you’re doing your own custom datastore system, you need to be aware of the big thing that DataStore2 is trying to solve: **two servers trying to save your data at the same time. **
    This is an extremely rare scenario if you implement points #1 and #2 in a game with rapid user rejoins. I haven’t had reports of this ever happening in my games but it’s possible. However, at worst, it just rolls back your data to the last X seconds or so, where X is how long the roblox data servers have been down for. I don’t recommend it, but if you’re paranoid, you could switch to DataStore2 and implement everything I’ve mentioned here today or build your own system (see below). If you’re interested in more info about it. This is a distributed systems problem called transaction concurrency control.

2 Likes

I never used datastore2, i tried to, but idk how to code really at all. thats why i wanted to fix this, because a friend made it and its been set up to be used with the game. id happily use datastore2, but i could not firgure out how to use it at ALL. How would i properly implement the snippets you gave me to improve the current script? Also, i DO have a loading screen, the data loads during, and reload after. im not sure if that makes it more reliable. Thanks tho mans

1 Like

Replace your GetAsync section with this:

local data = nil

local success, errormessage = false, nil
while not success and player.Parent do
	success, errormessage = pcall(function()
		data = SaveToDS:GetAsync(player.UserId.." | ILSdataVAL")
		if data ~= nil then
			print(data)
		else
			print("No data there welp!")
		end
	end)
	task.wait(1)
end

Replace your SetAsync section with this:

print(SavedData)

local success, errormessage = false, nil
while not success and player.Parent do
	success, errormessage = pcall(function()
		SaveToDS:SetAsync(player.UserId.." | ILSdataVAL", SavedData)
	end)	
	task.wait(1)
end

Good luck!

1 Like

Wait, where exactly? sorry im not very smart lol… can you tell me where the thing i should be replacing is?
Sorry again

1 Like

OHH i figured it out, THANK YOU i hope this works better mans!
Also, is the whole thing where i load the data 2 times a waste of time, or is it making the script more reliable?

For specifics, the proccess is like this:

Player joins
Load Player Data
Loading screen finishes (Load data again)

Don’t load the data 2 times, 1 is enough. Just load it the first time and remove the 2nd.

Do:
Player joins
Load Player Data
Loading screen finishes

1 Like

Prolly wont respond, but im working on adding mesages in the loading screen. decided to add you for your help mans

1 Like