Best way to enforce data save on server shutdowns

Hey there, so this is our current script that handles our datastore’s and most of our leaderstats + values:

local Players = game:GetService("Players")
local data = Instance.new("Folder",game.ServerStorage)
data.Name = "Data"
local saves = game:GetService("DataStoreService"):GetDataStore("newmoneyz2")
local jail = game:GetService("DataStoreService"):GetDataStore("jailstuff")
local ribbonsave2 = game:GetService("DataStoreService"):GetDataStore("ribbonz")
local experience = game:GetService("DataStoreService"):GetDataStore("experience2")
local medalsave = game:GetService("DataStoreService"):GetDataStore("medalz")
local DataStoreService = game:GetService("DataStoreService")

local function createLeaderStats(plr)
	repeat wait() until plr
	local leaderstat = Instance.new("Model")
	leaderstat.Name = "leaderstats"
	leaderstat.Parent = plr

	local exp = Instance.new("IntValue")
	exp.Name = "EXP"
	exp.Parent = leaderstat

	local jailtime = Instance.new("IntValue")
	jailtime.Name = "Prison Time"
	jailtime.Parent = leaderstat
	
	local medals = Instance.new("IntValue")
	medals.Name = "Medal"
	medals.Parent = plr
	
	local ribbon = Instance.new("Folder")
	ribbon.Name = "Ribbons"
	ribbon.Parent = plr
	
	local honorableservice = Instance.new("BoolValue")
	honorableservice.Name = "honorableservice"
	honorableservice.Parent = ribbon
	
	local loyalservice = Instance.new("BoolValue")
	loyalservice.Name = "loyalservice"
	loyalservice.Parent = ribbon
	
	local adeptservice = Instance.new("BoolValue")
	adeptservice.Name = "adeptservice"
	adeptservice.Parent = ribbon
	
	local outstandingservice = Instance.new("BoolValue")
	outstandingservice.Name = "outstandingservice"
	outstandingservice.Parent = ribbon
	
	local commandservice = Instance.new("BoolValue")
	commandservice.Name = "commandservice"
	commandservice.Parent = ribbon
	
	
	local fines = Instance.new("IntValue")
	fines.Name = "fine"
	fines.Parent = plr

	local lastSave = 0 
	local cash = Instance.new("IntValue", data)
	cash.Name = plr.Name.."Cash"

	local money = Instance.new("IntValue", data)
	money.Name = "money"
	money.Parent = plr

	local jt = jail:GetAsync(plr.userId.."PrisonTime")
	if jt ~= nil and jt > 0 then
		jailtime.Value = jt
	end
	
	wait()

	local exp1 = experience:GetAsync(plr.userId)
	if exp1 ~= nil and exp1 > 0 then
		exp.Value = exp1
	end
	
	local med1 = medalsave:GetAsync(plr.userId)
	if med1 ~= nil then
		medals.Value = med1
	end
	
	
	wait()
	
	local ribdata
	local success, errormessage = pcall(function()
		ribdata = ribbonsave2:GetAsync(plr.userId)
	end)
	
	if success and ribdata then
		wait(2)
		plr.Ribbons.honorableservice.Value = ribdata[1]
		plr.Ribbons.loyalservice.Value = ribdata[2]
		plr.Ribbons.adeptservice.Value = ribdata[3]
		plr.Ribbons.commandservice.Value = ribdata[4]
		plr.Ribbons.outstandingservice.Value = ribdata[5]
		end
	
	cash.Changed:connect(function()
		wait()
		money.Value = cash.Value
	end)
	
	if saves:GetAsync(plr.userId) ~= nil then
		cash.Value = saves:GetAsync(plr.userId)
	end


	if plr:GetRankInGroup(4219097) > 50 then
		local function enableExpSystem()
			plr.PlayerGui:WaitForChild("expsystem").Enabled = true
		end
		plr.CharacterAdded:Connect(function()
			enableExpSystem()
		end)
		enableExpSystem()
	end
	
	if plr:GetRankInGroup(4219097) >= 235 then
		local function enablemedalribbons()
			plr.PlayerGui:WaitForChild("medalsystem").Enabled = true
			plr.PlayerGui:WaitForChild("ribbonssystem").Enabled = true
		end
		plr.CharacterAdded:Connect(function()
			enablemedalribbons()
		end)
		enablemedalribbons()
	end
	
	
	while wait(60*3) do

		if cash then
			cash.Value = cash.Value + 125
		end
		if plr:IsInGroup(4219097) then
			exp.Value = exp.Value + 3
		end
	end
end

local function savingribbons(plr)
	
	local hs = plr.Ribbons.honorableservice.Value
	local ls = plr.Ribbons.loyalservice.Value
	local as = plr.Ribbons.adeptservice.Value
	local cs = plr.Ribbons.commandservice.Value
	local ous = plr.Ribbons.outstandingservice.Value

	ribbonsave2:SetAsync(plr.userId, {
		hs, 
		ls, 
		as,
		cs,
		ous
	})
	
	print("Data saved?")
end


Players.PlayerAdded:Connect(createLeaderStats)--for new players after script connection runs

for _,player in pairs(Players:GetPlayers()) do -- for players who already joined
	spawn(function()createLeaderStats(player)end)
end

Players.PlayerRemoving:connect(function(plr)
	
	local pt = plr.leaderstats["Prison Time"].Value
	local ex = plr.leaderstats["EXP"].Value
	
	savingribbons(plr)
	wait()
	medalsave:SetAsync(plr.userId, plr.Medal.Value)
	wait()
	
	if data and data[plr.Name.."Cash"] then
		print(plr.Name.." Leaving")
		saves:SetAsync(plr.userId,game.ServerStorage.Data[plr.Name.."Cash"].Value)
		wait()
		data[plr.Name.."Cash"]:Destroy()
	end
	wait()
	if ex > 0 then
		experience:SetAsync(plr.userId, ex)
	end
	
	if pt > 0 then
		jail:SetAsync(plr.userId.."PrisonTime", pt)
		jail:SetAsync(plr.userId.."InJail", true)
	else
		jail:SetAsync(plr.userId.."PrisonTime", 0)
		jail:SetAsync(plr.userId.."InJail", false)
		print("All data saved for"..plr.Name)
	end

end)

However, as you can assume, most players data is not saved upon server shutdowns. (I was able to pin point all this from the print functions you can see.)

I am wondering if there is some sort of way to slow-down the shutdown process until all players data has saved first?

I don’t think there is a point on saving player data on server shutdown. Instead, save data on Players.PlayerRemoving and it will do it instead.

You could do this:

game:BindToClose(function()
   for _, player in pairs(players:GetPlayers()) do
      -- call function to save player data
   end
end)
1 Like
Players.PlayerRemoving:connect(function(plr)
	
	local pt = plr.leaderstats["Prison Time"].Value
	local ex = plr.leaderstats["EXP"].Value
	
	savingribbons(plr)
	wait()
	medalsave:SetAsync(plr.userId, plr.Medal.Value)
	wait()
	
	if data and data[plr.Name.."Cash"] then
		print(plr.Name.." Leaving")
		saves:SetAsync(plr.userId,game.ServerStorage.Data[plr.Name.."Cash"].Value)
		wait()
		data[plr.Name.."Cash"]:Destroy()
	end
	wait()
	if ex > 0 then
		experience:SetAsync(plr.userId, ex)
	end
	
	if pt > 0 then
		jail:SetAsync(plr.userId.."PrisonTime", pt)
		jail:SetAsync(plr.userId.."InJail", true)
	else
		jail:SetAsync(plr.userId.."PrisonTime", 0)
		jail:SetAsync(plr.userId.."InJail", false)
		print("All data saved for"..plr.Name)
	end

end)

It already does that, but will not save every players data if I have to shutdown for an update. I want to avoid players losing data during shutdowns.

And that should save everybody’s data before it is able to shutdown?

Yes: DataModel:BindToClose

1 Like

There is a way to detect when the game updates. Check How do I check if the place was updated and make all players rejoin? on how to do that.

1 Like

Roblox Servers have a 30 (i think) second time limit after the last person leaves where all the serverscripts are able to finish up. Including all your datastorescripts. Wrap it in a PCall and if it fails repeat it. If you really want to enforce data then implement an autocorrect

1 Like

Hey there, just wanted to run this by you and make sure that this would be the most efficient way of approaching the datasave upon server shutdown:

local Players = game:GetService("Players")
local data = Instance.new("Folder",game.ServerStorage)
data.Name = "Data"
local saves = game:GetService("DataStoreService"):GetDataStore("newmoneyz2")
local jail = game:GetService("DataStoreService"):GetDataStore("jailstuff")
local ribbonsave2 = game:GetService("DataStoreService"):GetDataStore("ribbonz")
local experience = game:GetService("DataStoreService"):GetDataStore("experience2")
local medalsave = game:GetService("DataStoreService"):GetDataStore("medalz")
local DataStoreService = game:GetService("DataStoreService")

local function createLeaderStats(plr)
	repeat wait() until plr
	local leaderstat = Instance.new("Model")
	leaderstat.Name = "leaderstats"
	leaderstat.Parent = plr

	local exp = Instance.new("IntValue")
	exp.Name = "EXP"
	exp.Parent = leaderstat

	local jailtime = Instance.new("IntValue")
	jailtime.Name = "Prison Time"
	jailtime.Parent = leaderstat
	
	local medals = Instance.new("IntValue")
	medals.Name = "Medal"
	medals.Parent = plr
	
	local ribbon = Instance.new("Folder")
	ribbon.Name = "Ribbons"
	ribbon.Parent = plr
	
	local honorableservice = Instance.new("BoolValue")
	honorableservice.Name = "honorableservice"
	honorableservice.Parent = ribbon
	
	local loyalservice = Instance.new("BoolValue")
	loyalservice.Name = "loyalservice"
	loyalservice.Parent = ribbon
	
	local adeptservice = Instance.new("BoolValue")
	adeptservice.Name = "adeptservice"
	adeptservice.Parent = ribbon
	
	local outstandingservice = Instance.new("BoolValue")
	outstandingservice.Name = "outstandingservice"
	outstandingservice.Parent = ribbon
	
	local commandservice = Instance.new("BoolValue")
	commandservice.Name = "commandservice"
	commandservice.Parent = ribbon
	
	
	local fines = Instance.new("IntValue")
	fines.Name = "fine"
	fines.Parent = plr

	local lastSave = 0 
	local cash = Instance.new("IntValue", data)
	cash.Name = plr.Name.."Cash"

	local money = Instance.new("IntValue", data)
	money.Name = "money"
	money.Parent = plr

	local jt = jail:GetAsync(plr.userId.."PrisonTime")
	if jt ~= nil and jt > 0 then
		jailtime.Value = jt
	end
	
	wait()

	local exp1 = experience:GetAsync(plr.userId)
	if exp1 ~= nil and exp1 > 0 then
		exp.Value = exp1
	end
	
	local med1 = medalsave:GetAsync(plr.userId)
	if med1 ~= nil then
		medals.Value = med1
	end
	
	
	wait()
	
	local ribdata
	local success, errormessage = pcall(function()
		ribdata = ribbonsave2:GetAsync(plr.userId)
	end)
	
	if success and ribdata then
		wait(2)
		plr.Ribbons.honorableservice.Value = ribdata[1]
		plr.Ribbons.loyalservice.Value = ribdata[2]
		plr.Ribbons.adeptservice.Value = ribdata[3]
		plr.Ribbons.commandservice.Value = ribdata[4]
		plr.Ribbons.outstandingservice.Value = ribdata[5]
		end
	
	cash.Changed:connect(function()
		wait()
		money.Value = cash.Value
	end)
	
	if saves:GetAsync(plr.userId) ~= nil then
		cash.Value = saves:GetAsync(plr.userId)
	end


	if plr:GetRankInGroup(4219097) > 50 then
		local function enableExpSystem()
			plr.PlayerGui:WaitForChild("expsystem").Enabled = true
		end
		plr.CharacterAdded:Connect(function()
			enableExpSystem()
		end)
		enableExpSystem()
	end
	
	if plr:GetRankInGroup(4219097) >= 235 then
		local function enablemedalribbons()
			plr.PlayerGui:WaitForChild("medalsystem").Enabled = true
			plr.PlayerGui:WaitForChild("ribbonssystem").Enabled = true
		end
		plr.CharacterAdded:Connect(function()
			enablemedalribbons()
		end)
		enablemedalribbons()
	end
	
	
	while wait(60*3) do

		if cash then
			cash.Value = cash.Value + 125
		end
		if plr:IsInGroup(4219097) then
			exp.Value = exp.Value + 3
		end
	end
end

local function savedata(plr)
	
	local hs = plr.Ribbons.honorableservice.Value
	local ls = plr.Ribbons.loyalservice.Value
	local as = plr.Ribbons.adeptservice.Value
	local cs = plr.Ribbons.commandservice.Value
	local ous = plr.Ribbons.outstandingservice.Value
	
	local pt = plr.leaderstats["Prison Time"].Value
	local ex = plr.leaderstats["EXP"].Value

	ribbonsave2:SetAsync(plr.userId, {
		hs, 
		ls, 
		as,
		cs,
		ous
	})
	
	wait()
	medalsave:SetAsync(plr.userId, plr.Medal.Value)
	wait()

	if data and data[plr.Name.."Cash"] then
		print(plr.Name.." Leaving")
		saves:SetAsync(plr.userId,game.ServerStorage.Data[plr.Name.."Cash"].Value)
		wait()
		data[plr.Name.."Cash"]:Destroy()
	end
	wait()
	if ex > 0 then
		experience:SetAsync(plr.userId, ex)
	end

	if pt > 0 then
		jail:SetAsync(plr.userId.."PrisonTime", pt)
		jail:SetAsync(plr.userId.."InJail", true)
	else
		jail:SetAsync(plr.userId.."PrisonTime", 0)
		jail:SetAsync(plr.userId.."InJail", false)
		print("All data saved for"..plr.Name)
	end
end


Players.PlayerAdded:Connect(createLeaderStats)--for new players after script connection runs

for _,player in pairs(Players:GetPlayers()) do -- for players who already joined
	spawn(function()createLeaderStats(player)end)
end

Players.PlayerRemoving:connect(function(plr)
	savedata(plr)
end)

game:BindToClose(function()
	for _, player in pairs(Players:GetPlayers()) do
		savedata(player)
	end
end)
1 Like

Yes, that perfect! But one thing I’d say is wrapping all the GetAsyncs and SetAsyncs in a pcall because if Roblox’s data stores are down, it’ll break the script

Similar to how you did this: