GetAsync causing Server Lag

I have been trying to fix server lag on my game, I did some tracing and came to the result of the :GetAsync call is actually causing the server to skip about 14 heartbeats/frames, to test this fully I disabled every script on the Server except for my script which detects Server Lag, and my Datastore Script, which still shows the same result. this was tested on the ROBLOX game, not in studio.

I’m unsure if this is just a me issue which I dont think it is, I’ve asked 3 other developers who have also looked into it and believe its the same issue, the size of the Datastores are only about 1,300 characters

GetData function (some stuff wont run because of the testing I’ve been doing to this function, ignore the random bits that dont run)

each time a player joins, the server skips about 11 Heartbeats/Frames

image

function module:GetData(User)
	if typeof(User) == 'Instance' then
		User = User.UserId
	end
	print("STRACEBACK", debug.traceback("PlayerData:GetData"))
	local data
	if true then
		local start = os.clock()
		local C = DataStores:GetAsync(User)
		local Finish = os.clock()
		print("PlayerData GetAsync", Finish-start)
		if C then
			data = httpService:JSONDecode(C)
		end
		
	return true,data
	end
	local start2 = os.clock()
	local data
	--local success,err = pcall(function()
	local start = os.clock()
		local Temp = DataStores:GetAsync(User)
	print("PlayerData GetAsync", os.clock()-start)
		--warn('PlayerData: getting data at index', User, typeof(User), ':', Temp)
		if Temp then
			data = httpService:JSONDecode(Temp)
			local totalChar = 0
			for i,v in data do 
			local t = string.len(httpService:JSONEncode(v))
				warn(i,t)
				totalChar += t
			end
			print(totalChar)
			if tonumber(User) then	
				--data = fixData(data,User)
			end
		end
	--end)
	print("PlayerData:GetData", os.clock() - start2)
	if true then
		return true, data
	end
	if not success then
		warn('PlayerData ran into a problem while Getting Data from ' .. User .. ' error: ' .. err) 
		return success,nil
	else
		return true,data
	end
end

Core Datastore call:

function module:LoadData(User:Player)
	local Start = tick()
	local LastData = {}
	local Data
	local Success
	local NewPlayer
	local C1 = tick()
	local succ,err = pcall(function()
		Success,Data = PData:GetData(User)
	end)
	if true then return end

Lag Detector

local Data = {
	CurrentTick = 0,
	lastTickTime = tick(),
	started = false
}

local function nextGameTick()
	local StartTime = tick()
	local ExpectTimeDifference = 1/60
	local Difference = ExpectTimeDifference + (ExpectTimeDifference * 0) -- adds a minor gap to allow for simple processing on the Server
	if StartTime - Data.lastTickTime >= Difference and Data.started then
		local LagAmount = (StartTime - Data.lastTickTime)/Difference
		if LagAmount >= 2 then
			warn('Server Lag Detected, Skipped ' .. math.floor(LagAmount) .. ' heartbeats')
			
			local LagTime = StartTime - Data.lastTickTime
		
			
			warn('LagTime: ' .. LagTime)
		end
	end


	if not Data.started then
		Data.started = true
	end
	Data.CurrentTick += 1
	
	
	
	Data.lastTickTime = tick()
end

game:GetService('RunService').Heartbeat:Connect(function()
	nextGameTick()
end)

So each player’s data is saved as a string?

Why is it 1,300 characters? And how are you saving it? ,etc

Also, I have refactored your code and fixed some issues, like there being a double GetAsync for no apparent reason.

local DEBUG_MODE = true -- Toggle this to enable/disable debug prints

function module:GetData(User)
	if typeof(User) == 'Instance' then
		User = User.UserId
	end

	if DEBUG_MODE then
		print("STRACEBACK", debug.traceback("PlayerData:GetData"))
	end

	local data
	local startTime = os.clock()

	local success, result = pcall(function()
		return DataStores:GetAsync(User)
	end)

	local duration = os.clock() - startTime
	if DEBUG_MODE then
		print("PlayerData:GetAsync Duration:", duration)
	end

	if success and result then
		local decodeSuccess, decoded = pcall(function()
			return httpService:JSONDecode(result)
		end)

		if decodeSuccess then
			data = decoded

			if DEBUG_MODE and typeof(data) == "table" then
				local totalChar = 0
				for key, value in pairs(data) do
					local encoded = httpService:JSONEncode(value)
					local length = string.len(encoded)
					warn("Key:", key, "Size:", length)
					totalChar += length
				end
				print("Total Character Size:", totalChar)
			end
		else
			warn("JSONDecode failed for user", User)
		end
	else
		warn("GetAsync failed for user", User, result)
		return false, nil
	end

	return true, data
end

Enable/Disable debug mode when needed; it should still work with the rest of your code

You don’t need to wrap this in a pcall because of the pcall inside module:GetData

local Success, Data = PData:GetData(User)

Is enough

I did say “GetData function (some stuff wont run because of the testing I’ve been doing to this function, ignore the random bits that dont run)” so that stuff was intended for testing purposes. so far none of this really solves the issue of the GetAsync itself causing server lag

Can you show us the data you’re requesting so I can try replicate your issue

liek yo uwant the DataTemplate of the data it gets?

Player.UserId whatever roblox stores it as

example yes, it’s odd that GetAsync itself causing server lag because it’s just a network call

local module = {
	Primary = 'AKM',
	Secondary = 'Glock 17',
	Grenade = 'M67',
	hasVIP = false,
	Level = 1,
	XP = 0,
	BattlePassLevel = 1,
	BXP = 0,
	hasWW2Pack = false,
	Cash = 500,
	GoldenAirsoftTokens = 100,
	RedeemedCodes = {},
	Missions = {}, --GetMissions(),
	MissionResetTime = {
		Daily = 0,
		Weekly = 0,
		Monthly = 0,
		BattlePass = 0,
	},
	Career = {
		Kills = 0,
		Deaths = 0,
		TrackedStats = {
			Kills = 0,
			HKills = 0,
			LKills = 0,
			Deaths = 0,
			Wins = 0,
			Losses = 0
		},
	},
	Inventory = {
		Guns = {},
		Grenades = {"M67"},
		Skins = {},
		Attachments = {},
		Crates = {},
		Banners = {"Black"},
		Boosters = {},
		ItemInventory = {},
	},
	Settings = {
		["FOV"] = 80,
		["HipSens"] = 1,
		["AimSens"] = 1,
		["AimToggle"] = false,
		["SprintToggle"] = false,
		["Quote"] = "Welcome to Airsoft Center 2!",
		['EnrollAntiCheat'] = false,
		Keybinds = {} --require(game.ReplicatedStorage:WaitForChild("Modules"):WaitForChild("KeybindController"):WaitForChild('default')).Inputs,
	},


	Banner = {
		["Selected"] = "Black",
	},

	
	Loadouts = {
		{}
	},
	
	Logs = {},
	Fingerprints = {},
	LatestFingerprint = '',
	Banned = 0,
	BannedA = 0,
	BannedR = '',
	BannedRef = '',
	PlayerStats = {
		Kills = 0,
		Deaths = 0,
		ExtraData = {},
	}
}

It’s this. I even disabled every script on the server except for my data stuff so unsure what it is. data is only about, 1,300 characters after running through the code on the server

  20:20:57.569  STRACEBACK PlayerData:GetData
ReplicatedStorage.ModuleScript:116 function GetData
ServerScriptService.Script:9
  -  Server - ModuleScript:116
  20:20:57.689  PlayerData:GetAsync Duration: 0.12041660000249976  -  Server - ModuleScript:128
  20:20:57.690  Key: BXP Size: 1  -  Server - ModuleScript:144
  20:20:57.690  Key: MissionResetTime Size: 49  -  Server - ModuleScript:144
  20:20:57.690  Key: BattlePassLevel Size: 1  -  Server - ModuleScript:144
  20:20:57.690  Key: GoldenAirsoftTokens Size: 3  -  Server - ModuleScript:144
  20:20:57.690  Key: BannedR Size: 2  -  Server - ModuleScript:144
  20:20:57.690  Key: hasVIP Size: 5  -  Server - ModuleScript:144
  20:20:57.690  Key: Logs Size: 2  -  Server - ModuleScript:144
  20:20:57.690  Key: Loadouts Size: 4  -  Server - ModuleScript:144
  20:20:57.690  Key: Career Size: 102  -  Server - ModuleScript:144
  20:20:57.690  Key: Level Size: 1  -  Server - ModuleScript:144
  20:20:57.690  Key: XP Size: 1  -  Server - ModuleScript:144
  20:20:57.690  Key: Grenade Size: 5  -  Server - ModuleScript:144
  20:20:57.690  Key: Secondary Size: 10  -  Server - ModuleScript:144
  20:20:57.690  Key: PlayerStats Size: 37  -  Server - ModuleScript:144
  20:20:57.690  Key: BannedRef Size: 2  -  Server - ModuleScript:144
  20:20:57.690  Key: RedeemedCodes Size: 2  -  Server - ModuleScript:144
  20:20:57.690  Key: Primary Size: 5  -  Server - ModuleScript:144
  20:20:57.690  Key: Missions Size: 2  -  Server - ModuleScript:144
  20:20:57.690  Key: Inventory Size: 123  -  Server - ModuleScript:144
  20:20:57.690  Key: hasWW2Pack Size: 5  -  Server - ModuleScript:144
  20:20:57.690  Key: Settings Size: 150  -  Server - ModuleScript:144
  20:20:57.690  Key: Banner Size: 20  -  Server - ModuleScript:144
  20:20:57.690  Key: Fingerprints Size: 2  -  Server - ModuleScript:144
  20:20:57.690  Key: LatestFingerprint Size: 2  -  Server - ModuleScript:144
  20:20:57.690  Key: Banned Size: 1  -  Server - ModuleScript:144
  20:20:57.690  Key: BannedA Size: 1  -  Server - ModuleScript:144
  20:20:57.690  Key: Cash Size: 3  -  Server - ModuleScript:144
  20:20:57.691  Total Character Size: 541  -  Server - ModuleScript:147

I am unable to recreate your issue, I also changed the lag script you have.

local RunService = game:GetService("RunService")

local LAG_DETECTOR_SETTINGS = {
	EXPECTED_FPS = 60,
	SENSITIVITY_MULTIPLIER = 1.5 -- Warn if frame time is > 1.5x the expected time
}

local Data = {
	CurrentTick = 0,
	lastTickTime = os.clock() -- Initialize with current time using os.clock()
}

local EXPECTED_DELTA = 1 / LAG_DETECTOR_SETTINGS.EXPECTED_FPS
local THRESHOLD_DELTA = EXPECTED_DELTA * LAG_DETECTOR_SETTINGS.SENSITIVITY_MULTIPLIER

local function nextGameTick()
	local currentTime = os.clock() -- Use os.clock() for wall-clock time
	local actualDelta = currentTime - Data.lastTickTime

	-- Check if the actual time elapsed exceeds the threshold
	if actualDelta > THRESHOLD_DELTA then
		local lagFactor = actualDelta / EXPECTED_DELTA
		-- Round skippedFrames more intuitively
		local skippedFrames = math.max(0, math.floor(lagFactor - 1))

		warn(string.format(
			"Server Lag Detected! Frame took %.4fs (Expected %.4fs, Threshold %.4fs). Factor: %.2fx. Approx Skipped Frames: %d",
			actualDelta,
			EXPECTED_DELTA,
			THRESHOLD_DELTA,
			lagFactor,
			skippedFrames
			))
	end

	-- Update counters and last time
	Data.CurrentTick += 1
	Data.lastTickTime = currentTime -- Update for the next check
end

-- Connect to Heartbeat AFTER initializing lastTickTime
RunService.Heartbeat:Connect(nextGameTick)

Place File, Make sure you enable HTTPS and API
DataStore.rbxl (58.8 KB)

this bug may be place-specific im unsure currently. using my own Data storage method has no lag attached whatsoever, so may need to begin doing that until ROBLOX fixes this issue.

1 Like