Optimizing Datastores script

Hello, fellow developers!
I’ve been having some problems with my Datastores script, some players of my game have been reporting some data losing problems for the past few days, which has made me think i should optimize my datastores, but i really don’t know how to without affecting the data. Could i get any help?
Script:

local PhysicsService = game:GetService("PhysicsService")
local DataStoreService = game:GetService("DataStoreService")

local unlockedTowersData = DataStoreService:GetDataStore("UnlockedTowers")

game.Players.PlayerAdded:Connect(function(player)

	local data = DataStoreService:GetDataStore("DataStore"..player.UserId)


	local unlockedTowers = Instance.new("Folder")
	unlockedTowers.Name = "UnlockedTowers"
	unlockedTowers.Parent = player

	local GoldData = data:GetAsync("GoldData")	
	local towerData = data:GetAsync("TowerData")	
	local loadOutData = data:GetAsync("EquippedTowerData")

	local towers = game.ReplicatedStorage.Towers

	local success, errormessage = pcall(function()

		if towerData then
			for i, tower in pairs(towers:GetChildren()) do
				for i2 = 1, #towerData do
					if tower.Name == string.sub(towerData[i2],1,#tower.Name) and unlockedTowers:FindFirstChild(tower.Name) == nil then
						local value = Instance.new("BoolValue")
						value.Name = tower.Name
						value.Parent = unlockedTowers

						value.Value = string.sub(towerData[i2],#tower.Name+2) == "true"
					end
				end
			end
		end

		for i, tower in pairs(towers:GetChildren()) do
			if unlockedTowers:FindFirstChild(tower.Name) == nil then
				local value = Instance.new("BoolValue")
				value.Name = tower.Name
				value.Value = false
				value.Parent = unlockedTowers
			end
		end

		unlockedTowers.Recruit.Value = true
		unlockedTowers.Sniper.Value = true
		
		--if game.MarketplaceService:UserOwnsGamePassAsync(player.UserId, 39670714) then
		--	unlockedTowers.Bomber.Value = true
		--	print("Player owns gamepass, awarded Bomber")
		--end

		local statsFolder = Instance.new("Folder")
		statsFolder.Name = "Stats"
		statsFolder.Parent = player

		local playerGold = Instance.new("NumberValue")
		playerGold.Name = "PlayerGold"
		playerGold.Parent = statsFolder

		--[[local miniPassBought = Instance.new("BoolValue")
		miniPassBought.Name = "BoughtMinipass"
		miniPassBought.Parent = statsFolder
		miniPassBought.Value = false
		
		local smallPassBought = Instance.new("BoolValue")
		smallPassBought.Name = "BoughtSmallpass"
		smallPassBought.Parent = statsFolder
		smallPassBought.Value = false]]--


		if GoldData then
			playerGold.Value = GoldData
		else
			playerGold.Value = 250
		end


		local loadOutFolder = Instance.new("Folder")
		loadOutFolder.Name = "LoadOut"


		if loadOutData then
			for i = 1, 5 do
				local value = Instance.new("StringValue")
				value.Name = i
				value.Value = loadOutData[i]
				value.Parent = loadOutFolder
			end
		end
		for i = 1, 5 do
			if loadOutFolder:FindFirstChild(i) == nil then
				local value = Instance.new("StringValue")
				value.Name = i
				value.Value = "None"
				value.Parent = loadOutFolder
			end
		end
		loadOutFolder.Parent = player

		if loadOutData == nil then
			loadOutData["1"].Value = "Recruit"
		end
	end)



	if success then
		print(player.Name .. "'s Data have been loaded!")
	else
		warn(player.Name .. "'s Data couldn't been loaded!")
		error(errormessage)
	end


end)

game.Players.PlayerRemoving:Connect(function(player)
	local data = DataStoreService:GetDataStore("DataStore"..player.UserId)

	local playerUserId = "User_" .. player.UserId

	local success, errormessage = pcall(function()

	end)

	local towerData = {}
	local loadOutData = {}

	local towerFolder = player.UnlockedTowers
	local loadOutFolder = player.LoadOut

	local success, errormessage = pcall(function()
		for i, tower in pairs(towerFolder:GetChildren()) do
			table.insert(towerData, tower.Name.."_"..tostring(tower.Value))
			--	print(tower.Name.."_"..tostring(tower.Value))
		end

		for i = 1, 5 do
			local value = loadOutFolder:FindFirstChild(i)
			table.insert(loadOutData, value.Value)
		end

		data:SetAsync("TowerData", towerData)
		data:SetAsync("EquippedTowerData", loadOutData)
		data:SetAsync("GoldData", player.Stats.PlayerGold.Value)
		--print("fr saved")
	end)

	if success then
		print(player.Name .. "'s Data have been saved!")
	else
		warn(player.Name .. "'s Data couldn't been saved!")
		error(errormessage)
	end
end)

Hello, It seems like you aren’t waiting for the data to be saved if a server shutdown, that could be the reason why you are getting data loses.

You should try to wait for the server to save the data by using the BindToClose function

Here is some pseudo code

local savingDatas = {};

game.Players.PlayerRemoving:Connect(function(player)
    table.insert(savingDatas, player);

	local success, errormessage = pcall(function()
		for i, tower in pairs(towerFolder:GetChildren()) do
			table.insert(towerData, tower.Name.."_"..tostring(tower.Value))
			--	print(tower.Name.."_"..tostring(tower.Value))
		end

		for i = 1, 5 do
			local value = loadOutFolder:FindFirstChild(i)
			table.insert(loadOutData, value.Value)
		end

		data:SetAsync("TowerData", towerData)
		data:SetAsync("EquippedTowerData", loadOutData)
		data:SetAsync("GoldData", player.Stats.PlayerGold.Value)
		--print("fr saved")
	end)

    table.remove(savingDatas, table.find(savingDatas, player));

	if success then
		print(player.Name .. "'s Data have been saved!")
	else
		warn(player.Name .. "'s Data couldn't been saved!")
		error(errormessage)
	end
end)

game:BindToClose(function()
    repeat
        task.wait(0.1);
    until #savingDatas == 0;
end);

Hope this can help you.

PS: You should also consider implementing auto save incase the server crashes and is unable to save the data.

Just tried this script out and data seems to be saving & loading without an issue, thanks a lot! I’ll work on auto saving, thanks for the advice too :smiley: