What are some ways I can improve this DataStore System?

Hi, so I am wondering what are some ways i can improve this data store?

This is just a Basic one, I made it earlier today and I never got to finish it

(Please Note that I DO NOT want DataStore2 or PlayerService, I keep getting told that I should use it but that is NOT what I want.)

Any Ideas?

DataStoreService = game:GetService("DataStoreService")
Data = DataStoreService:GetDataStore("PlayerAssets")

ReplicatedStorage = game:GetService("ReplicatedStorage")
Player = game:GetService("Players")

local Player_ = "Player_"

Player.PlayerAdded:Connect(function(p)
	
	local l, s = Instance.new("Folder", p), Instance.new("Folder", p)
	l.Name = "leaderstats"
	s.Name = "saved_stats"
	
	local Lvl = Instance.new("IntValue", l)
	Lvl.Name = "Level"
	Lvl.Value = Data:GetAsync(Player_..p.UserId.."_Level") or 1
	
	local EXP = Instance.new("IntValue", s)
	EXP.Name = "EXP"
	EXP.Value = Data:GetAsync(Player_..p.UserId.."_EXP") or 0
	
end)

Player.PlayerRemoving:Connect(function(p)
	Data:SetAsync(Player_..p.UserId.."_Level", p.leaderstats.Level.Value)
	Data:SetAsync(Player_..p.UserId.."_EXP", p.saved_stats.EXP.Value)
end)

It works how I want it to, I was wondering how to improve upon it.

You should consider making the system data-fail-safe. If a player who is level 5 loads in, and his/her data fails to load in properly (because GetAsync doesn’t work 100% of the time), then the PlayerAdded method will error, meaning the Lvl IntValue will be at its default value, 0 (because it was never properly set). Once the player leaves, his/her level will be set to 0, which is not what you want.

To combat this, you should try wrapping your Async calls in pcall functions. Personally, whenever a GetAsync call fails, I kick the player and add them to a table of players to ignore, so when the PlayerRemoving method is run, it doesn’t save the player’s data. Then, after they’re gone, I remove them from the table (just in case they join again).

There are many ways to do this, that’s just the logic for the approach I use.

While I’m sure you’ve read this article before, the Data Stores article gives some good information regarding protective calling.

Thanks, do you mind explaining a bit more in depth about it tho?

Nope, use ProfileService. It’s used by multiple big games too. To name a couple, Mad City and BedWars uses this data store module.

Yes, I am back, the one who asked @DasKairo…

He means that DataStores can fail because the service can be down, so data isn’t loaded but when it is up again the level 0 value will be saved to the player (level 5 previously) when the player leaves.

(Not actual code)

PlayerAdded:
Create Value (Default 0)
Value.value = GetData() — crashes so stays at 0

PlayerRemoving:
SaveData(Value) — Value is still 0 and this might not fail because the server is online again.

What I do is create a table of all datastores in game, when playerAdded, I run a loop to iterate each datastore, if any fails, do a few retries, when all datastores are readed correctly without failure, I let the player start to play (I cancel the starting characterLoad until all datastores are loaded) And continue with the loop until all datastores loaded, if keeps failing the player only see “Loading” on screen, until all of them loaded.

Saving all the data gotten from the datastore in a server module, in a table using the userId as the key, and what datastore that data belongs.

When playerRemoved, I check that module table using the userId key, and only save each datastore if theres real data in that module table, otherwise, save nothing.
If the datastore saving fails, retry as many times as needed until succed, only when succed remove the player entry from the module table

Some stuff that you can do :
#1.)Save data in tables rather than using different keys.This will save you some Datastore Requests
#2.)Use UpdateAsync() rather than SetAsync().It can prevent Data loss if used wisely.
#3.)make it recursive with limits. What I mean here is simply retrying to save the data just in case the Request Fails while adding a limit to it.
#4.) Make these actual functions so that you need not repeat yourself for saving or loading data in different places,you would have to edit only one function if errors arise and lastly it makes your code more readable.

1 Like

I’m not sure about this, usually when I try saving stuff under one key, it combines or overlaps the data, mind explaining how i do this?

Ok so I got This, Just wondering if this is what I need to do?

DataStoreService = game:GetService("DataStoreService")
Data = DataStoreService:GetDataStore("PlayerStuff")

ReplicatedStorage = game:GetService("ReplicatedStorage")
Player = game:GetService("Players")

local Player_ = "Player_"


DataKey = {}

Player.PlayerAdded:Connect(function(p)
	
local l, s = Instance.new("Folder", p), Instance.new("Folder", p)
	l.Name = "leaderstats"
	s.Name = "saved_stats"	
	
local Lvl = Instance.new("IntValue", l)
	Lvl.Name = "Level"	
local EXP = Instance.new("IntValue", s)
	EXP.Name = "EXP"
	
local DataCache
local Success, Data = pcall(function()
		DataKey[p.UserId] = Data:GetAsync(p.UserId, DataKey[p.UserId])
end)
	
	if Success then
		print("Loaded Player Data:", DataKey[p.UserId])
		
	else
		DataKey[p.UserId] = {
			["Level"] = 1,
			["EXP"] = 0,
			["Money"] = 500
			
		}
		
	end
	p:WaitForDataReady()
Lvl.Value = DataKey[p.UserId]["Level"]
EXP.Value = DataKey[p.UserId]["EXP"]
	
Lvl:GetPropertyChangedSignal("Value"):Connect(function()
		DataKey[p.UserId]["Level"] = Lvl.Value
end)
	
end)

Player.PlayerRemoving:Connect(function(p)
	local success, errorm = pcall(function()
	Data:SetAsync(p.UserId, DataKey[p.UserId])
	end)
	if success then
		print("Successful Save")
	else
		warn("Data Failed to Save For Player_"..p.UserId.."When Leaving")
	end
	DataKey[p.UserId] = nil
end)

Im not the best in this topic but I’ve never had issues with datastores, here’s some comments about your script

DataStoreService = game:GetService("DataStoreService")
Data = DataStoreService:GetDataStore("PlayerStuff")

ReplicatedStorage = game:GetService("ReplicatedStorage")
Player = game:GetService("Players")

local Player_ = "Player_"


DataKey = {}

Player.PlayerAdded:Connect(function(p)

	local l, s = Instance.new("Folder", p), Instance.new("Folder", p)
	l.Name = "leaderstats"
	s.Name = "saved_stats"	

	local Lvl = Instance.new("IntValue", l)
	Lvl.Name = "Level"	
	local EXP = Instance.new("IntValue", s)
	EXP.Name = "EXP"

	--local DataCache -- WHERE?
	local Success, Data = pcall(function()
		-- IF GET ASYNC FAILS WHATS DataKey[p.UserId] ?
		DataKey[p.UserId] = Data:GetAsync(p.UserId, DataKey[p.UserId])
	end)

	if Success then
		-- IF SUCCESS MEANS SUCCESSFUL CONNECTION, DOESNT MEAN PLAYER HAS DATA
		-- if Data then (do stuff) else (new player) end
		print("Loaded Player Data:", DataKey[p.UserId])

	else
		-- WHERES THE RETRIES TO READ DATASTORE BEFORE LET PLAYER TO PLAY IF GetAsync Failed?
		DataKey[p.UserId] = {
			["Level"] = 1,
			["EXP"] = 0,
			["Money"] = 500

		}

	end
	--p:WaitForDataReady() -- WHERE?
	Lvl.Value = DataKey[p.UserId]["Level"] -- OUT
	EXP.Value = DataKey[p.UserId]["EXP"] -- OUT

	Lvl:GetPropertyChangedSignal("Value"):Connect(function()
		DataKey[p.UserId]["Level"] = Lvl.Value
	end)

end)

Player.PlayerRemoving:Connect(function(p)
	-- A CHECK TO ACTUALLY KNOW THERES DATA TO SAVE DataKey[p.UserId]?
	local success, errorm = pcall(function()
		Data:SetAsync(p.UserId, DataKey[p.UserId])
	end)
	if success then
		print("Successful Save")
	else
		warn("Data Failed to Save For Player_"..p.UserId.."When Leaving")
	end
	DataKey[p.UserId] = nil
end)
DataKey[p.UserId] = {
			["Level"] = 1,
			["EXP"] = 0,
			["Money"] = 500

		}

Im Sorry?

Well, what do i do here? this is just here to i can edit the Stats so they can go to the table

I’m Pretty sure it should still save the data either way

On first try, if DSS fails you are not retrying to read it. If it succeded, you are not sure if player has real data or not.

On saving, you dont actually know if theres real data from the DSS reading, just “hardcoded” saving, if the data is not real, because you created it on playerJoin, you could lose the real player’s datastore.
Of course that will save the data either way, a possible empty data.

Read the post I made in your thread, I suggest DO NOT let the player to spawn/play before all datastores has been readed correctly, if not, retry

I see, but how to do I do this part?

This is a basic approach:

local DSS = game:GetService("DataStoreService")
local Data = DSS:GetDataStore("Data")

local function ReadDSS(p)
	local Success, Data = pcall(function()
		return Data:GetAsync(p.UserId)
	end)
	
	if not Success then
		-- Retry count
		warn("DSS roblox service failed... retry")
		local retry = 0

		-- Retry
		while retry < 21 do -- retry fixed amount of times
			warn("retry to read", retry)
			Success, Data = pcall(function()
				return Data:GetAsync(p.UserId)
			end)

			-- If error happens
			if not Success then
				retry += 1
				wait(2) -- cooldown
			else
				print("finally got a read on retry:", retry)
				break
			end
		end
	end

	if Success then
		print("Connected to DSS roblox service")
		if Data then
			print("player has data in DSS, old player")
			print("store data in server table, let player to continue")
		else
			print("player has no data, new player")
			print("store starting values in server table")
		end		
	end
end
1 Like

Hey, Sorry for the Late Response

I guess i could make this work.

Thank you

I don’t either, For some reason i dont have Data Loss with it, unless i am using it incorrectly

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.