Global Leaderboards using Profile Service

Hello readers

I am creating a game myself using Profile Service. I wanted to implement global leaderboads into my game, but figured out it’s not so easy as we all expected. When searching the forum I found that I wasn’t the only person with this issue.

Why is it not easy?
When making a leaderboard system, most people use Roblox’ DataStoreService to save data. When creating a leaderboard they just call the Sorted Datastore. Since Profile Service does not support this, that is not an option. Even worse, the API does not have any function to fetch e.g. the best 100 players.

What’s an alternative?
I have discovered a good strategy to tackle this problem. So I will guide you into the right direction.

You will be using MessagingService and will create a SubscribeAsync() function. Then you will create a while loop, with a desired refresh rate. This loop will check how many Coins the lowest player on the board has, if the player has a higher amount, you will create a table with the playerName and how many Coins they have. Then you will PublishAsync() using the topic and Encoded table. In the SubscribeAsync() function you will decode the table. Then you can do the rest of the logic such as adding a tag for the player, calculating which position they actually have etc.

Now you may have noticed when a new server opens, it will put the best player on the board who’s actually playing right now. That is why we create a DataStore that will hold a sorted table with the best players, and update that table after every refresh. When a new server opens, we just call that DataStore and load the leaderboard with the best players.

I would recommend setting a refresh rate between 60 and 300 seconds. To make sure the DataStore does not get overloaded.

If you had the same problem, I hope this post helped you.

3 Likes

No need, here’s a script for leaderboard I have:

local datastoreservice = game:GetService("DataStoreService")

local datastore = datastoreservice:GetDataStore("Test")

local OrderedStore = datastoreservice:GetOrderedDataStore("Ordered")

local LeaderBoard = workspace.LeaderBoard
local Names = LeaderBoard.SurfaceGui.Frame.Names:GetChildren()
local Ranks = LeaderBoard.SurfaceGui.Frame.Ranks:GetChildren()
local Values = LeaderBoard.SurfaceGui.Frame.Values:GetChildren()

local function RemoveTableValues()
	table.remove(Names, 1)
	table.remove(Ranks, 1)
	table.remove(Values, 1)
end

local function CopyData(Key, data)
	OrderedStore:SetAsync(Key.KeyName, data)
end

local function AssignRanks(index, KeyInstance)
	if Ranks[index] and Ranks[index].ClassName == "TextLabel" then
		Ranks[index].Text = index
	end
	if Names[index] and Names[index].ClassName == "TextLabel" then
		Names[index].Text = KeyInstance.key
	end
	if Values[index] and Values[index].ClassName == "TextLabel" then
		Values[index].Text = KeyInstance.value
	end
end

RemoveTableValues()

while true do
	local Pages = datastore:ListKeysAsync()
	
	local Page = Pages:GetCurrentPage()
	
	for Every, Key in Page do
		local success, data = pcall(function()
			return datastore:GetAsync(Key.KeyName)
		end)
		
		if data and success then
			pcall(CopyData(Key, data))
		end
		
	end
	local OrderedPages = OrderedStore:GetSortedAsync(false, 100)
	
	local OrderedPage = OrderedPages:GetCurrentPage()
	
	for index, KeyInstance in OrderedPage do
		AssignRanks(index, KeyInstance)
	end
	task.wait(60)
end

Profileservice uses datastoreservice, so when you make a profilestore you’re making a datastore. Just switch the datastore name to your profilestore’s name.

10 Likes

Oh, you are right, that indeed is obvious, I don’t think I did a bad job on my system tho :smiley:

Yeah it was good, i’m just saying that you could’ve done this instead. Much easier.

You know SortedMaps are a thing in MemoryStoreService, right?

Every entry can represent a player’s progress, but more importantly it can also have a sorting order.

It also has automatic expiry, meaning a player could fall off the leaderboards if their stats haven’t changed for a while.

It’s like this was designed just for leaderboards.

It has more forgiving limits than MessagingService and definitely Ordered Datastores too.

MemoryStoreService is good but I think that the data autocleanup after max 2 months is a big con tho

I have 3 different leaderboards and I want each one to resemble a stat. Is there a way to expand the current script to fit all 3 leaderboards on, or is it easier to just make 3 different scripts, 1 for each leaderboard?

I use Profile Service and Ordered Data Stores.
When a player leaves, using Profile Service, if they value I want indexed in the ordered data store has changed, I update the ODS at that point.
So I essentially maintain two data stores. The primary one with Profile Service, and a separate Ordered Data Store.

Yeah, you can put all 3 leaderboards in a folder. And then loop through them.

Also sidenote, don’t copy the data over into a different datastore like I did in my script. I figured this out the hard way. It led to throttling issues with the datastore and the data took so long to appear on the leaderboards.

I was a scripter with about 6-7 months experience back then so I wasn’t too strong on datastores and so I didn’t realise copying the data over would cause issues.

The problem is let’s say you have 100-200 keys naturally because you’ve had 100-200 people join your game in its entire lifetime. SetAsync/GetAsync calls have a limit per minute. And this is based on how many concurrent users you have. So if you only have 1 ccu but 100-200 keys of data you will reach the limit very quickly and the datastore will throttle.

The solution I found was to simply use the name of your profilestore with the method Datastoreservice:GetOrderedDataStore(“YourProfileStoreName”). Since the profileservice module still uses datastoreservice under the hood.

You’ll have to modify profileservice’s module to where it uses the method :GetOrderedDataStore instead of :GetDataStore("). I haven’t used profileservice in so long since i’ve already implemented the features the module provides. I don’t use any modules for datastores anymore I just write the code myself. just gives me alot more control.