ProfileService taking a long time to load players profile

It’s going to be pretty hard for anyone to provide assistance without seeing examples of how you’re using ProfileService. Could you post some snippets showing how you’re interacting with the module?

1 Like

Only thing I’m noticing is this, which I posted another DevForum post about, but would it really delay time getting Profile data that much?

Which looking at these errors, it’s only happening to certain people and not everyone, which doesn’t really make sense, you can see one each one, it’s a different ID but it shows that it’s 1.8k each time. Unless it’s stacking ALL of them together and just displaying that one ID.

1 Like

I am using PSManager to manage the ProfileService, but even with people not using this, I’ve seen the same posts with just using ProfileSevice.

This is how the data is being done when player is loading

local function loadPlayer(plr)
	if (tfind(loadedPlayers,plr)) then return end
	tinsert(loadedPlayers,plr)
	
	for storeName, storeDefaults in pairs(PROFILE_STORE_DEFAULTS) do
		local store = ProfileStores[storeName]
		if (not store) then
			store = loadStore(storeName)
		end
		
		local profileKey = processProfileKey(plr,storeDefaults._playerProfileKey)
		local profile = store:LoadProfileAsync(
			profileKey,
			DEFAULT_NRH
		)

		if profile ~= nil then
			profile:Reconcile()
			profile:ListenToRelease(function()
				store[plr] = nil
				plr:Kick("Profile released.")
			end)
			if plr:IsDescendantOf(PLRS) == true then
				local dummy = {
					Data = {}
				}
				local callbacks = {}
				
				dummy.Data.__index = function(t,i)
					return profile.Data[i]
				end
				dummy.Data.__newindex = function(t,i,v)
					profile.Data[i] = v
					
					if (not callbacks[i]) or (callbacks[i] and #callbacks[i] < 1) then return end
					for _ , callback in ipairs(callbacks[i]) do
						spawn(function()
							callback(v)
						end)
					end
				end
				
				dummy.__index = function(t,i)
					if (i ~= "Data") then
						return profile[i]
					end
				end
				dummy.__newindex = function(t,i,v)
					profile[i] = v
				end
				
				setmetatable(dummy.Data,dummy.Data)
				setmetatable(dummy,dummy)
				
				function dummy:OnDataValueChanged(dataIndex,callback)
					if (not callbacks[dataIndex]) then
						callbacks[dataIndex] = {}
					end
					table.insert(callbacks[dataIndex],callback)
					
					local Connection = {}
					function Connection:Disconnect()
						table.remove(callbacks[dataIndex],table.find(callbacks[dataIndex],callback))
					end
					return Connection
				end
				
				task.spawn(function()
					local globalUpdates = dummy.GlobalUpdates
					local activeCount = 0
					for index, update in globalUpdates:GetActiveUpdates() do
						activeCount += 1
						globalUpdates:LockActiveUpdate(update[1])
					end
					local lockedCount = 0
					for index, update in globalUpdates:GetLockedUpdates() do
						lockedCount += 1
						handleLockedUpdate(globalUpdates, update, profile)
					end
					globalUpdates:ListenToNewActiveUpdate(function(id, data)
						globalUpdates:LockActiveUpdate(id)
					end)
					globalUpdates:ListenToNewLockedUpdate(function(id, data)
						handleLockedUpdate(globalUpdates, {id, data}, profile)
					end)
				end)
				
				store[plr] = dummy
			else
				profile:Release()
			end
		else
			plr:Kick("Couldn't get data. Please try again later.") 
		end
	end
	
	
	
	tinsert(fullyLoadedPlayers,plr)
end

And when the player is leaving

PLRS.PlayerRemoving:Connect(function(plr: Player)
	if (not tfind(loadedPlayers, plr)) then return end
	tremove(loadedPlayers, tfind(loadedPlayers, plr))

	if (tfind(fullyLoadedPlayers, plr)) then
		tremove(fullyLoadedPlayers, tfind(fullyLoadedPlayers, plr))
	end
	
	for storeName, store in pairs(ProfileStores) do
		if typeof(store) ~= "table" or ( typeof(store) == "table" and not store[plr] ) then continue end
		
		store[plr]:Release()
		store[plr] = nil
		tremove(store, tfind(store,plr))
	end
end)
1 Like

Looking at your .PlayerRemoving and .PlayerAdded event, the code looks fine and doesn’t seem to have issues.

However, I strongly advise you not to use PSManager, as it is not updated and prone to bugs and issues. I use profile service in my game and I haven’t had an issue like this yet. I suggest you update the ProfileService module to the latest version and also use ReplicaService over PSManager as it is developed by the same person who made ProfileService.

1 Like

If you can, please link all of these sources. Especially the new version of ProfileService and ReplicaService

1 Like

Ofcourse,

Latest version of ProfileService: https://github.com/MadStudioRoblox/ProfileService/blob/master/ProfileService.lua

Profile Service Common Issues:
https://madstudioroblox.github.io/ProfileService/troubleshooting/

Replica Service: Replicate your states with ReplicaService! (Networking system)

Replica Service wiki: ReplicaService

2 Likes

Thank you, I’ll look into these and if it works, I’ll mark as solution.

1 Like

Just to give you a heads up about ReplicaService, its used with player data from profile service, it’s really neat because it has events like :ListenToNewKey, :ListenToWrite, :ListenToChange and more you can check all of these events in the wiki link I sent you.

Also, this model has examples on how it’s used with profile service and by itself:

2 Likes

How does ListenToChange work? I’m trying to get it to work but it won’t seem to want to work

(completely recoded PSManager by the way for ReplicaService, etc)

local function PlayerAdded(player : Player)
	local playerProfile = PSManager:GetFullProfile(player)
	local profile = playerProfile.Profile
	
	pcall(function()
		table.insert(profile.Data.Joins, os.time())
	end)
	
	for i, v in PlayerTemplate do
		pcall(function()
			playerProfile.Replica:ListenToChange({i}, function(new)
				print(i, new)
				DataService["Stat Changed"]:Fire(player, i, new)
				DataService.Client["Stat Changed"]:Fire(player, i, new)
			end)
		end)
	end
end

It just doesn’t print anything but the value is changing

This is how the code was written before changing PSManager

local function PlayerAdded(player : Player)
	local data = GetProfile(player)
	pcall(function()
		table.insert(data.Data.Joins, os.time())
	end)
	
	for i, v in PlayerTemplate do
		pcall(function()
			data:OnDataValueChanged(i, function(new)
				DataService["Stat Changed"]:Fire(player, i, new)
				DataService.Client["Stat Changed"]:Fire(player, i, new)
			end)
		end)
	end
end

I forgot I had it wrapped in a pcall and wasn’t seeing the error, this is the error
image

2 Likes

ListenToChange is a event that can be accessed on the client side of replicaservice.

On the server you would use the mutators to change variables in data and on the client you would listen to these changes via these functions

Basically replica service removes the need to use remote events. So wherever you’re making changes to data you need to replace them with the mutator functions. For example:

Server:

players.PlayerAdded:Connect(function()
	local dataClassToken = replicaService.NewClassToken('PlayerData')
	local newReplica = replicaService.NewReplica({
		ClassToken = dataClassToken, -- important for the client side as this is how you will access the data on the client
		Data = {["Coins"] = 0}, -- the profile you load from profile service
		Replication = selectedPlayer -- only replicate the player's data to the player
	)

	newReplica:SetValue({"Coins"}, newReplica.Data.Coins + 10)
end)

Client:

local replicaController = require(replicatedStorage.ReplicaController)

replicaController.ReplicaOfClassCreated("PlayerData", function(playerDataReplica)
	playerDataReplica:ListenToChange({"Coins"}, function(newValue, previousValue)
		print("Coins has been updated to: "..newValue.." from: "..previousValue)
	end)
end)

This example the server is using the mutator “SetValue” in the path “Coins” and the client is listening to this change by using ListenToChange

2 Likes

So there’s no way to figure out if value changed through the server? To be honest, I feel like it’s too much just to use replicaController to see when the value changes, the event makes it 10x easier and I won’t have to access a whole new module I’ll only use once. Being able to just access it through the server and know when the value is changed, it’ll help alot more. If it’s not possible, I guess I’d just have to do it that way which sucks (not literally but yk what I mean). And some of the server code also uses the bindable event on the server to know when a value is changed, especially for something like settings, etc.

Yeah, to put it short, there is no way to get change detections on the server with replica service. But for a good reason, however if you feel you’ve gone too far with your current server code to switch to ReplicaService, then it’s fine. You’d save time deep diving into your source code to analyze the problem instead of rewriting your entire network.

I mean everything is working great already with ReplicaService, it’s just that you cannot access value changes through on the server, kinda of a dumb decision for the developers to not do, but that’s not something I’d go more into.

image

You can see here, I mainly use it on the Server than the client.

1 Like

Yeah, loleris, the developer, likely didnt make this because wherever you’re implementing the modification, you can use the server mutators. These mutators are specifically made to alert the client through listeners. To put it differently, ReplicaService is structured so that whenever you modify a value via the API, it automatically notifies the client.

1 Like

I see, well thank you, I’ll keep digging into it and see what else I can do with it.

2 Likes

Yeah of course, if you have any questions you can PM me too, I use replica service in my main project so I may be able to help you with it. Otherwise good luck!

1 Like

Did you ever find a fix for this? I’ve been using ProfileService for months, but just recently my load times have spiked to 60 seconds minimum and it’s killing my retention.

Yes, I switched to using EasyProfile for my Profile Service Manager. You can see my post here asking about value changes. It seems to work pretty well but with the occasional reports of it being slow every so often but that’s with any game

The documentation seems to be broken so here is my code to manage it, just to save you some time:

local DataErrorMessage = "Could not load data, try again shortly. If issue persists, open a ticket!"
local LoadType = "ForceLoad" --"Steal"


local gameProperties, srv = _G.GameProperties, _G.GameServices


local profileService = require(script:WaitForChild("EasyProfile"))
local playerTemplate = require(script:WaitForChild("PlayerTemplate"))

local DataKey = gameProperties.DataKey
local ProfileStore = profileService.CreateProfileStore(
	DataKey,
	playerTemplate,
	"<default_key>-<userid>"
)



local PSManager = {
	Profiles = {}
}


local function HandleGlobalKey(globalKey, profile) --> Value, Key, KeyId
	local value, key, keyId = globalKey.Value, globalKey.Key, globalKey.KeyId
	if not keyId then
		return
	end

	if key == "Warning" then
		if value == "reset" then
			profile.Data.Warnings = {}
		elseif value == "remove" then
			local warning = value.warning and tonumber(value.warning)
			if not warning or not profile.Data.Warnings[warning] then
				return
			end
			table.remove(profile.Data.Warnings, warning)
		elseif typeof(value) == "table" then
			table.insert(profile.Data.Warnings, {
				admin = value.admin,
				reason = value.reason,
				warnedAt = value.sendTime
			})
		end
	end
end

local function PlayerAdded(player : Player)
	local start_time, timeJoined = tick(), os.time()
	ProfileStore:LoadProfileAsync(player, true, LoadType):After(function(success, playerProfile, ...)
		--print(tick() - start_time.."s")
		if not success then
			player:Kick(DataErrorMessage)
			return
		end
		
		local metaData = playerProfile:GetMetaData()
		if metaData.ProfileActiveSession.jobId ~= game.JobId then
			player:Kick("Profile is already loaded in another server, please wait before rejoining! If this continues, open a ticket!")
			return
		end
		
		--/ Create leaderstats
		playerProfile:CreateProfileLeaderstats(player, {"KOs", "WOs", "Cash"})
		
		local profile = playerProfile.Profile
		profile.TimeJoined = timeJoined
		
		local dummy = {
			Data = {
				TimeJoined = profile.TimeJoined
			}
		}
		local callbacks = {}

		--/ Value Changing
		dummy.Data.__index = function(_,index)
			return profile.Data[index]
		end
		dummy.__index = function(_,index)
			if (index ~= "Data") then
				return profile[index]
			end
		end

		dummy.Data.__newindex = function(_,index,value)
			profile.Data[index] = value

			local callback = callbacks[index]
			if not callback or typeof(callback) ~= "table" then return end

			for _, callback in callback do
				coroutine.wrap(callback)(value)
			end
		end
		dummy.__newindex = function(_,index,value)
			profile[index] = value
		end

		setmetatable(dummy.Data,dummy.Data)
		setmetatable(dummy,dummy)

		function dummy:OnDataValueChanged(index, callback)
			if (not callbacks[index]) then
				callbacks[index] = {}
			end
			table.insert(callbacks[index],callback)

			local Connection = {}
			function Connection:Disconnect()
				table.remove(callbacks[index],table.find(callbacks[index],callback))
			end
			return Connection
		end
		
		
		--/ Global Keys
		for _, globalKey in playerProfile:GetGlobalKeys() do
			coroutine.wrap(HandleGlobalKey)(globalKey, profile)
		end
		playerProfile.GlobalKeyAdded:Connect(function(globalKey)
			HandleGlobalKey(globalKey, profile)
		end)
		
		PSManager.Profiles[player] = dummy
	end)
end
for _, player in srv.players:GetPlayers() do
	coroutine.wrap(PlayerAdded)(player)
end
srv.players.PlayerAdded:Connect(PlayerAdded)

srv.players.PlayerRemoving:Connect(function(player : Player)
	if PSManager.Profiles[player] ~= nil then
		PSManager.Profiles[player] = nil
		ProfileStore:UnclaimSessionLock(player, nil)
	end
end)




function PSManager:WaitForPlayerLoaded(player : Player) : Profile
	local profile = self.Profiles[player] if profile then return profile end
	repeat task.wait() profile = self.Profiles[player] until profile return profile
end

function PSManager:FetchProfile(...) : Profile
	return self:WaitForPlayerLoaded(...)
end

function PSManager:GetProfileStore()
	return ProfileStore
end

function PSManager:GetPlayerTemplate()
	return playerTemplate
end

function PSManager:GetOfflinePlayerStore(userId : number) : Profile
	if not userId or not tonumber(userId) then
		return
	end
	userId = tonumber(userId)
	return ProfileStore:GetProfileAsync(
		profileService.IsProfileKeyValid(userId, ProfileStore)
	):Await()
end

return PSManager

Hope that helps!

2 Likes

It might help, but for now it actually makes things harder sadly lol

Thank you anyway though, you’re the first person I’ve gotten any sort of info from so far. Unfortunately my game is already live and switching Data templates right now could be catastrophic. I’ll tinker with Easy Profile and see if I can come up with a viable way to transition over though, thanks again!

My game was published aswell when I did the change, as long as you continue using ProfileService you shouldn’t have any issues. EasyProfile uses ProfileService so it should be alright. I would just try and play around with it and look at my code to see how I did it

1 Like