How do popular games do leaderboards, or datastores in general?

So, recently I made a leaderboard system for my game, but I was wondering how more popular games and bigger developers do their datastores, I doubt they do it the same way a beginner’s tutorial does it. Here is the code:

local rs = game:GetService("ReplicatedStorage")
local st = rs:WaitForChild("SaveTime")
local dss = game:GetService("DataStoreService")
local leaderboardstore = dss:GetOrderedDataStore("leaderboardstore10")
local playertime  
local update  

local function waitfor(reqType)
	local cur = dss:GetRequestBudgetForRequestType(reqType)
	while cur < 1  do
		cur = dss:GetRequestBudgetForRequestType(reqType)
		wait(5)
	end
end


st.OnServerEvent:Connect(function(player, runtime) 
	playertime = tonumber(runtime)  
	print("before"..playertime)
	playertime *= 100
	playertime = math.floor(playertime + 0.5)
	print("after"..playertime)   
	local userid = player.UserId
	local key = userid
	local success, err
	repeat
		waitfor(Enum.DataStoreRequestType.UpdateAsync)
		success, err = pcall(leaderboardstore.UpdateAsync,leaderboardstore,key,function()
			return playertime
		end)
	until success
	
	
	if not success then
		print(err)
		warn(err)
	end    
end)


update = function()   
	local Data
	local success, err
	repeat
		waitfor(Enum.DataStoreRequestType.GetSortedAsync)
		success, err = pcall(function()
			Data = leaderboardstore:GetSortedAsync(true,10)
		end) 
	until success
	if not success then
		print(err)
	end


	local timepage = Data:GetCurrentPage()
	for rank, data in ipairs(timepage) do
		print(data.key)
		if tonumber(data.key) < 1 then continue end
		print(tonumber(data.key))    
		local username = game.Players:GetNameFromUserIdAsync(tonumber(data.key))
		local name = username
		local timing = data.value
		local onboard = false
		for i, v in pairs(game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder:GetChildren()) do
			if v.Player.Text == name then
				onboard = true
				break
			end
		end
		if timing and onboard == false then 
			local split = tostring(timing/100):split(".")
			local newframe = rs:WaitForChild("Template"):Clone()
			newframe.Player.Text = name
			newframe.Time.Text =  string.format("%.2d.%.2d", tonumber(split[1]), tonumber(split[2])) 
			newframe.Rank.Text = "#"..rank
			newframe.Position = UDim2.new(0,0,newframe.Position.Y.Scale + (.1 * #game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder:GetChildren()), 0)
			newframe.Parent = game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder
		end
	end
end

while true do 

	for _, frame in pairs(game.Workspace.Leaderboard.LeaderboardGui.LeaderboardHolder:GetChildren()) do
		frame:Destroy()
	end
	update()
	print("update")
	wait(10)
end

Also is this datastore secure enough? I tried adding pcalls and retries, but Im not sure if its enough.

1 Like

i honestly don’t know but using the regular data store isn’t the best idea cause it is prone to data lose and data corruption

honestly i would try to use profile service because it manages data better than roblox can

1 Like

How often does that happen though, I’m not sure if switching would be worth it since im not expecting over 100k-200k visits, thats just to be safe though in reality im expecting maybe 50k - 100k.

ehh, its probably likely thats why there’s mainly a pcall function just incase it catches for errors because there is a slight chance it might mess up and plus i think it’s just confusing overall

1 Like

Ive included pcalls and retries, so are you saying roblox itself often corrupts or loses data, and I can do nothing about it? If so do you know how often it has happened in the past? Im a new developer so I dont really know how reliable it is.

i’d say its probably reliable enough to make a simple game, but unless you’re making an ambitious project you should probably use profile service cause it solves a number of problems

1 Like

Well I would consider my game in the middle of simple and ambitious, but can you give some examples of said problems that profile service solves?

The newer major datastore update fixes a lot of the issues mentioned above with data loss and such, as versioning has been implemented. The above statements are (mostly) invalid. See: Data Stores | Roblox Creator Documentation

To answer the main question: Most well programmed systems use more complex data schema to save a lot of data at the same time as a part of their system, metadata tagging, etc. etc., (as well as better general code organization and such,) but fundamentally they are doing the same thing you are doing here. Some do, as mentioned, used pre-made services such as ProfileService or Datastore2.

There are a few issues I notice straight away, once of which within your waitfor() function, as you are setting the value of cur before performing the wait. (Use task.wait() by the way, blah blah better performance). The result of this is that if you have hit the limit, you will be waiting a minimum of 10 seconds before continuing as cur will be set to the exact same thing as it was before the start of the loop.

Another thing of note is that, assuming you continue to destroy your UI elements each update, you should be destroying your previous UI elements after you make your datastore call. This will make it appear to update without as much of a gap, though, it’d be better to update elements dynamically. This is more complex though, so I wouldn’t bother for now. Just some re-ordering can go a long way in making the UX better, though.

You are performing no validation of the data sent to the OnServerEvent call, allowing any exploiter to send any number to the remote event which will be saved in the datastore, which would set their data to whatever value they decided.

Besides this, your datastore setup itself seems to be good enough:tm: . There may be some other issues with the code that I can’t spot here, but this is what I notice in a short glance.

Happy coding!

1 Like

Thanks, I changed the wait in the waitfor function, also I was planning on adding some vaildation to protect against exploits soon, just wanted to check the actual datastore system itself for now. Anyways I didn’t know about the new datastore update, I’m most likely just going to be using the normal datastores now, so thanks for that as well.

ProfileService is not appropriate for Leaderboards, as it does not deal with OrderedDataStore.

OrderedDataStore lets you get the top scores, but then correlating that with the names to display, is the hard part. You’ll need to use GetUserInfosByUserIdsAsync to fetch the names in bulk. Because these can be expensive calls, you’ll need a module script or class that can retain the fetched information for a period of time before refreshing it.