ProfileService taking a long time to load players profile

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

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