Hello! i’m bullet, currently i’ve been running into an issue with optimizing a datastore that’s key to the game i’m working on, as it needs your tools to be saved for when you rejoin, it works but the thing is you can only have 10 tools before it goes over the datastore request queue and overall the script just needs some dire help.
Script
local toolsDS = dss:GetDataStore("ToolsData")
local plr
local debounce = true
local toolsFolder = game.ServerStorage.ToolsFolder
game.Players.PlayerAdded:Connect(function(player)
plr = player
local toolsSaved = toolsDS:GetAsync(plr.UserId .. "-tools") or {}
for i, toolSaved in pairs(toolsSaved) do
local char = plr.Character or plr.CharacterAdded:Wait()
if plr.Backpack:FindFirstChild(toolSaved) == nil and char:FindFirstChild(toolSaved) == nil and toolsFolder[toolSaved] ~= nil then
toolsFolder[toolSaved]:Clone().Parent = plr.Backpack
toolsFolder[toolSaved]:Clone().Parent = plr.StarterGear
end
end
plr.CharacterRemoving:Connect(function(char)
char.Humanoid:UnequipTools()
end)
end)
game.Players.PlayerRemoving:Connect(function(plr)
local toolsOwned = {}
for i, toolInBackpack in pairs(plr.Backpack:GetChildren()) do
table.insert(toolsOwned, toolInBackpack.Name)
end
local success, errormsg = pcall(function()
toolsDS:SetAsync(plr.UserId .. "-tools", toolsOwned)
end)
if errormsg then warn(errormsg) end
end)
while wait(0.1) do
if plr ~= nil and debounce == true then
debounce = false
plr:WaitForChild("BoughtSomething").Changed:Connect(function()
if plr:WaitForChild("BoughtSomething").Value == true then
local char = plr.Character or plr.CharacterAdded:Wait()
char.Humanoid:UnequipTools()
local toolsOwned = {}
for i, toolInBackpack in pairs(plr.Backpack:GetChildren()) do
table.insert(toolsOwned, toolInBackpack.Name)
end
local success, errormsg = pcall(function()
toolsDS:SetAsync(plr.UserId .. "-tools", toolsOwned)
end)
if errormsg then warn(errormsg) end
task.wait()
plr:WaitForChild("BoughtSomething").Value = false
end
end)
task.wait()
wait(1)
debounce = true
end
end
You’re saving to the data store way too frequently (1/10 of a second in that while loop).
There’s a 6 second cooldown between write requests for the same key, so it’s throttling the data store usage, causing requests to be placed in a queue until it can process it. If the queue limit is exceeded (30 requests), the request is dropped entirely and that’s how data loss happens.
Page that shows all the data store limits:
Instead of auto saving every 0.1 seconds, you could try something like 5 minutes (300 seconds), so then you wouldn’t throttle the data usage.
A more optimized version might look something like this:
local Players = game:GetService("Players")
local toolsDS = dss:GetDataStore("ToolsData")
local AUTOSAVE_INTERVAL = 5 * 60 -- How frequently it auto saves (5 * 60 = 5 minutes)
-- You'll want a table to store all the player's data rather than
-- updating a single plr variable
local sessionData = {}
local toolsFolder = game.ServerStorage.ToolsFolder
local function onBoughtSomething(player)
local boughtSomething = player:FindFirstChild("BoughtSomething")
if boughtSomething and boughtSomething.Value == true then
local character = player.Character or player.CharacterAdded:Wait()
local playerDataKey = player.UserId .. "-tools"
character.Humanoid:UnequipTools()
local toolsOwned = {}
for i, toolInBackpack in ipairs(player.Backpack:GetChildren()) do
table.insert(toolsOwned, toolInBackpack.Name)
end
sessionData[playerDataKey] = toolsOwned
boughtSomething.Value = false
end
end
local function onPlayerAdded(player)
local playerDataKey = player.UserId .. "-tools"
local savedData = nil
sessionData[playerDataKey] = {}
local success, errorMessage = pcall(function()
savedData = toolsDS:GetAsync(playerDataKey)
end)
if success then
if savedData then
local character = player.Character or player.CharacterAdded:Wait()
local backpack = player:FindFirstChild("Backpack")
local starterGear = player:FindFirstChild("StarterGear")
for i, toolName in ipairs(savedData) do
local isInBackpack = backpack and backpack:FindFirstChild(toolName)
local isInCharacter = character:FindFirstChild(toolName)
local tool = toolsFolder:FindFirstChild(toolName)
if not isInBackpack and not isInCharacter and toolToClone then
if backpack then
tool:Clone().Parent = backpack
end
if starterGear then
tool:Clone().Parent = starterGear
end
end
end
end
sessionData[playerDataKey] = savedData
else
warn(errorMessage)
end
player.CharacterRemoving:Connect(function(character)
local humanoid = character:FindFirstChildWhichIsA("Humanoid")
if humanoid then
humanoid:UnequipTools()
end
end)
-- I don't know when you add the BoolValue to the player, so this could be changed
-- to your preference depending on it
local boughtSomething = player:WaitForChild("BoughtSomething")
local debounce = false
boughtSomething.Changed:Connect(function()
if not debounce then
debounce = true
onBoughtSomething(player)
debounce = false
end
end)
end
local function saveData(playerDataKey)
local toolsOwned = sessionData[playerDataKey]
if toolsOwned then
local success, errorMessage = pcall(function()
toolsDS:SetAsync(playerDataKey, toolsOwned)
end)
if errorMessage then
warn(errorMessage)
end
end
end
local function onPlayerRemoving(player)
local playerDataKey = player.UserId .. "-tools"
saveData(playerDataKey)
sessionData[playerDataKey] = nil
end
local function autoSave()
while true do
for _, player in ipairs(Players:GetPlayers()) do
local playerDataKey = player.UserId .. "-tools"
saveData(playerDataKey)
end
task.wait(AUTOSAVE_INTERVAL)
end
end
-- Resumes the auto save thread
local autosaveRoutine = coroutine.create(autoSave)
local success = coroutine.resume(autosaveRoutine)
if not success then
warn("Could not resume auto save routine")
end
-- In case the player joins before the script fully loads, we call onPlayerAdded
-- for players currently in game
for _, player in ipairs(Players:GetPlayers()) do
onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
Edit: fixed the typo on the GetAsync call. Thanks, @MysteriousVagabond, for pointing that out.
Isn’t there a better way to do this without looping that is more responsive, can’t you just detect whenever a child is added to a player’s backpack and detect whenever it’s removed as well instead of using a while loop? Just go through a for loop of all players backpack and create a connection with childadded and childremoved, whenever a player joins just make a new connection for them too.
items are inserted into a table, looks for " toolsOwned " in the script and you’ll find it
it’s under the bought something function and the save data function
When I run @Coderius’s code, everything seems to work fine (although the GetAsync call has a typo); however, I noticed you mentioned that it only loads 10 tools. The built-in toolbar is limited to 10 tools, but if you press the tilde key, it’ll open up your backpack and show you any other tools you may have. Using Coderius’s code, I was able to save and load 32 tools without any problem.
I ran it in a test server in studio, added tools to my backpack, toggled the “BoughtSomething” BoolValue, left the server, then closed the server. When I restarted the test server, it loaded all the tools I had put in my backpack.