Recently I’ve been experiencing frequent (maybe every 3-5 seconds?) large lag spikes while testing my game that last ~1.5 seconds. I’ve been debugging and analyzing the microprofiler and eventually found that the lag is caused by updating values in a table that stores the player’s data. Now the table is somewhat large (not millions of indices like you’d expect for lag) at 55 indices. The indices range from cash, inventory, basedata, to status effects, passives, and base stat boosts.
Here’s what I’ve observed:
If you change any value in the table to something other than what it currently is (e.g. cash from 10 → 20) then it will lag.
Lag spike will occur ~3-5 seconds after updating any value in the table.
Lag spike lasts ~1.5 seconds.
The rate at which you update the table doesn’t affect the lag spike.
The amount of data you update the table also doesn’t affect the lag spike.
(even the smallest change like HP healing from 99 to 100 will cause lag)
I’ve also noticed in the microprofiler, when the spike occurs almost everything else is dropped except my GameCoordinator script and two other things. It appears to always be the same 3 things that pop up.
No, any changes to the table whatsoever cause lag.
E.g.
PlayerData.Cash += 10
I do have a metatable linked to the PlayerData table but I quickly learned bypassing the metatable and directly updating the table caused the same effect.
I’ll try to give more insight into this. Basically the player’s data is stored in a modulescript under the player called “Stats”. When you require the modulescript, it returns a table full of functions linked to the PlayerData via a metatable.
--code removed
Since the module returns this metatable, the only reference to the PlayerData table is held within the Stats modulescript. Meaning all changes to the table have to go through the metatable, allowing me to log them using the print and warn functions visible in the code above.
The PlayerFunctions table is the real table returned by the module, it includes 4 functions which are irrelevant except for the SetPlayerData function. (it also has all indices except the functions, humanoid, and character set to nil, so __index and __newindex are always fired.) This function loops through provided table and overrides the current indices of the PlayerData, allow you to bypass the metatable. This function is only used when loading data from datastores, and in the script below which I am using to replicate the lag spikes.
--code removed
Many scripts index the metatable a lot to check indices and values, but even when doing this three times a heartbeat I run fine on full fps. It’s only when I attempt to modify the values that the lag occurs. There should be no active listeners at the time of writing this, so expensive calculations being done every change also isn’t viable. I’ve disabled all bindable events (the listeners) just to be extra careful and cut down on the possibility of spamming bindables causing lag. Bypassing the metatable shown in the code above still lags, so it’s not expensive metamethods being called. I also tested it in game instead of studio and the same lag occurs, so it is not my roblox studio. The only difference in game is that instead of my entire screen freezing, it’s just everything that runs on the server freezing.
(Even disabling the testing code above which is used to replicate the lag spikes, I still lag when my stats naturally update like when I kill an enemy and gain loot, cash, and exp.
I also noticed upon loading in that I lag from just my data loading from the datastores.)
If needed I can provide my output (the logging) or footage of the lag.
I have a small portion of code in the game’s mainscript which heals the player every heartbeat with some math and adjustments using their playerdata. However even disabling this still lags when for example I kill an enemy and my exp & cash update.
Alright, it seems that my datastore system created a backup of the player’s data whenever it was no longer identical to the one saved in the datastore. (They were being compared as compressed JSON strings and were not connected to a listener, if the player’s data saved and then they happened to be different than then it would create a backup. Which is why I couldn’t find it connected to a listener.) This explains the reason why only when the value changed it lagged. At first I didn’t notice lag since I didn’t have a lot of backups until recently. I had >2000 backups and the string being decompressed and recompressed into the datastore was lagging the entire server. I added a sort of bandaid where it only checks to backup every 3 minutes instead of every save (dynamic saving takes place every ~3 seconds with one player). I also added other criteria which I won’t get too indepth. Now, even if everyone’s data is massive it should only lag every 3 minutes. Also data will take much much much much longer to get massive. Very strong bandaid, but I‘m not sure if it’s completely possible to completely remove the problem.