How to use Datastore-Service correctly and fix dataloss

Hello, developers!
I’ve been wanting to teach people on how to use Datastore-Service correctly. Everyone seems to dislike datastore service because of dataloss, ratelimits, and the savings abilities it has. However, I am here to show you how to program a datastore correctly, fixing these limitations.

In this tutorial, I will be showing you how to;

  • Stop dataloss,
  • Learn where and when to save,
  • Save custom items (In section 1, will update this post on how to save other custom items)

If you are new to datastores, no worries. The first few details are about how to create and use a datastore. Even if you aren't new, I would take a peek at it, as I try to explain the process.

SECTION 1: HOW TO MAKE A DATASTORE

Summary

Creating the Datastore

Intro

(P.S, make sure you have this button checked. You can find it after you publish your game in game settingssecurity.)
Screenshot 2022-03-04 211504

To get started, you need to first create your datastore.
Start off by calling the Datastore-service.

local Datastore = game:GetService("DataStoreService")

Then, create your datastore for your game.

local Data = Datastore:GetDataStore("gameData")

You can name “gameData” anything you would like. If you rename it, be careful. This changes the already existing one into a new datastore.

Don’t forget to add the PlayerService!

local Players = game:GetService("Players")

The next step is to create the three main functions for the datastore.

function PlayerAdded(Player)
      -- To get the data and create the leaderstats, folders, etc.
end

function PlayerLeaving(Player)
      -- To save the data
end

game:BindToClose(function()
      -- To give the server time to save
end)

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerLeaving)

In the PlayerAdded function, we need to make the leaderstats and other game aspects that we want saved.

function PlayerAdded(Player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = Player

	local Coins = Instance.new("IntValue")
	Coins.Name = "Coins"
	Coins.Value = 25
	Coins.Parent = leaderstats

	local Weapons = Instance.new("Folder")
	Weapons.Name = "Weapons"
	Weapons.Parent = Player
end

Right now we have a simple leaderstats and weapon folder script that we insert into the player.
Screenshot 2022-03-04 212444
image

Next, we have to wait for the server to save our data. In roblox studio, the game shuts down too fast for the server, making it unable to save the data. Luckily, there is an easy fix to this. Just add wait(3) to the game:BindToClose function.

game:BindToClose(function()
    wait(3) -- You can save for longer, but wait atleast 3 seconds
end)

This doesn’t mean it will save, yet.
To save the data, we first need to get the data.

Getting the Player’s Data

Body

We need to get the player’s data in order to save it!
But what if they don’t have any data? No worries. Pcall, meaning protective call, will be able to help us. Pcall will not stop the code if an error were to happen. This is good because normally code will break if an error ocours.


We can use this when calling the datastore.

local Success, Returned = pcall(function()
	return Data:GetAsync(Player.UserId) -- Returns a table with all the saved data, key excluded
end)

This might look a bit weird, but let me explain. We are making a pcall function and assigning two variables to it. The first variable is controlled by the pcall. If an error were to happen, the first variable which in our case is “Success” would return false. If no errors were to happen, it would return true. All the other variables after that is ours. So when we return the player’s data, we are returning to the second variable, which is called “returned”. If there is no player data or the success variable returns false, the returned variable would be nil because there isn’t anything saved to the datastore unless the player rejoins. Lastly, if there is an error, the returned argument will contain the error message.

Another important thing here is that we are getting the data through the player’s key. Each player has a unique key when creating an account. This is your userid. When saving and getting the player’s data, it is important to save and get the player’s key, or else everyone will have the same data!

Loading the Player’s Data

Body

How can we load the player’s data once we have it? Well, the returned value is actually a table. (Tables)

This table holds all of the player’s data, like the amount of coins they have. So how do we access it? For starters, tables have something called an index. (What is indexing? - #5 by C_Sharper)

Lets say I have a table. I want to get the first item in the table. How would I do that? With indexing. Example code:

local myTable = {"Apple", "Pear", "Banana"}
print(myTable[1]) -- Apple
print(myTable[2]) -- Pear
print(myTable[3]) -- Banana

-- Indexes start at 1

local myTable2 = {
	Cash = 50,
	Supplies = {
		"Beans",
		"Bag",
		"Boots"
	}
}

print(myTable2["Cash"]) -- 50
print(myTable2["Supplies"]) -- [1] = "Beans", [2] = "Bag", [3] = "Boots" (Only shows in the output tab)
print(myTable2["Supplies"][1]) -- "Beans"
print(myTable2[1]) -- nil

As you can see, each value in myTable has an index to represent each string.
In myTable2, the indexes aren’t represented by numbers, but by string. If a value is a variables, it won’t have a number as an index. The index will become the string’s name instead.

So you would do this:

local myTable2 = {
	Cash = 50,
	Supplies = {
		"Beans",
		"Bag",
		"Boots"
	}
}

print(myTable2["Cash"]) -- 50

This can also be achevied by doing:

print(myTable2.Cash) -- 50

Both ways work.

This is how we are going to load and set the player’s data into our leaderstats.

function PlayerAdded(Player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = Player

	local Coins = Instance.new("IntValue")
	Coins.Name = "Coins"
	Coins.Value = 25
	Coins.Parent = leaderstats

	local Weapons = Instance.new("Folder")
	Weapons.Name = "Weapons"
	Weapons.Parent = Player

	local Success, Returned = pcall(function()
		return Data:GetAsync(Player.UserId) -- Returns a table with all the saved data, key excluded
	end)
----------------------------------------------------------------------------------------------------------------------------------------
	if Success and Returned then

	else
		print(Returned) -- Prints the error, if the player is new to the game, the expected output is 'nil'
	end
end

We have to start off by making sure there are no errors and that the player has data.
If there is an error, we can just print it. So if everything passes the check, then we are set. All we have to do now is set the coins value to the saved data and load in the weapons.

if Success and Returned then
		local WeaponCount = 0

		Coins.Value = Returned["Coins"]

		for _,_ in pairs(Returned["Weapons"]) do -- Looping through the player's weapons
			WeaponCount += 1 -- If we can loop through once, we know they have more than zero weapons and can now load in their weapons. Without this, we could get an error because the player might not have any weapons
			break
		end

		if WeaponCount > 0 then -- If the player has weapons
			for _,v in pairs(Returned["Weapons"]) do -- Loops through the table again
				local i = Instance.new("BoolValue") -- Create a new instance
				i.Name = v -- Set the instance name to v; v is the 'object' or table value that is in the loop, so all we need is the name of it
				i.Parent = Weapons -- Parent the new instance into the weapons folder
			end
		else
			print(Player.Name.." has no weapons") -- If the weapon count is zero, then print
		end
	else
		print(Returned) -- Prints the error, if the player is new to the game, the expected output is 'nil'
	end

On the fourth line, you can see that we are accessing the player’s data and finding the “Coins” value through indexing. This variable’s value is a number value. Check out the comments to learn how we get the weapons from the player!

After you are finished, you should get ‘nil’ printed out as we haven’t saved the data yet.
image

Are we done now? Nope! We are just getting the player’s data, they don’t have anything saved yet.
All we have to do now is save the player’s data!

Saving the Player’s Data

Body

We are almost finished! :partying_face:
There is something you should know. Datastore-service can only hold one custom argument at a time. So in order to save all of the leaderstats, weapons, etc, we need to put them all in one big table.

function PlayerLeaving(Player)
	local PWeapons = Player:FindFirstChild("Weapons"):GetChildren()
	local PCoins = Player:FindFirstChild("leaderstats"):FindFirstChild("Coins").Value
end

Before we do that, these two lines inside the function are really important. This is where most people mess up. You have to save items, values, strings, etc to variables if you want to avoid dataloss. These should also be at the very front of the function, so we don’t have weird corruptions with data.
After you have saved these stats in variables, we need to save them to the datastore. For me, I made a weapons folder that will hold all of the player’s weapons. I need to loop through the folder and put them into a weapons table. This is because the weapons table will go inside the table we will save. This makes it more organized and clean.

function PlayerLeaving(Player)
	local PWeapons = Player:FindFirstChild("Weapons"):GetChildren()
	local PCoins = Player:FindFirstChild("leaderstats"):FindFirstChild("Coins").Value
	-- Getting the information the second the player is leaving will stop dataloss
	local WeaponTable = {}

	for i,v in ipairs(PWeapons) do
		table.insert(WeaponTable, i, v.Name) -- All we need is the name, for once the player is loaded in we can give their weapons back after the values have been inserted into the weapons folder.
	end
end

Now that we have the weapons part done, we can save all of the data into the big table we will be using to save to the datastore. We don’t need to put the coins into a ‘CoinsTable’ because its already as neat as it can be.

local PlayerTable = {
		Coins = PCoins, -- You can access this when the playerjoins by Returned["Coins"]. This will return the PCoins value.
		Weapons = WeaponTable -- This is a table inside of a table.
	}

Finally, we can save the data.

Data:SetAsync(Player.UserId, PlayerTable) -- Saves it to the player's key! Only this player will have access to their data

We also have to save the data to the BindToClose function just in case roblox servers crash.

game:BindToClose(function()
    for _,v in ipairs(Players:GetPlayers()) do
		task.spawn(function() -- This function makes it so our function below doesn't yield
			PlayerLeaving(v)
		end)
	end
	wait(3)
end)

Make sure you are putting all of your data you want to save in the PlayerTable. If not, your other values you are saving will delete themselves.

Final Product

Conclusion

After creating, getting, loading, and saving the data, it should look like this.
(If you haven’t already, please look at how I did it, or this won’t help you.)

local Datastore = game:GetService("DataStoreService")
local Data = Datastore:GetDataStore("newData")

local Players = game:GetService("Players")

function PlayerAdded(Player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = Player

	local Coins = Instance.new("IntValue")
	Coins.Name = "Coins"
	Coins.Value = 25
	Coins.Parent = leaderstats

	local Weapons = Instance.new("Folder")
	Weapons.Name = "Weapons"
	Weapons.Parent = Player

	local Success, Returned = pcall(function()
		return Data:GetAsync(Player.UserId)
	end)

	if Success and Returned then
		local WeaponCount = 0

		Coins.Value = Returned["Coins"]

		for _,_ in pairs(Returned["Weapons"]) do
			WeaponCount += 1
			break
		end

		if WeaponCount > 0 then
			for _,v in pairs(Returned["Weapons"]) do
				local i = Instance.new("BoolValue")
				i.Name = v
				i.Parent = Weapons
			end
		else
			print(Player.Name.." has no weapons")
		end
	else
		print(Returned)
	end
end

function PlayerLeaving(Player)
	local PWeapons = Player:FindFirstChild("Weapons"):GetChildren()
	local PCoins = Player:FindFirstChild("leaderstats"):FindFirstChild("Coins").Value

	local WeaponTable = {}

	for i,v in ipairs(PWeapons) do
		table.insert(WeaponTable, i, v.Name)
	end

	local PlayerTable = {
		Coins = PCoins,
		Weapons = WeaponTable
	}

	Data:SetAsync(Player.UserId, PlayerTable)
end

game:BindToClose(function()
    for _,v in ipairs(Players:GetPlayers()) do
		task.spawn(function()
			PlayerLeaving(v)
		end)
	end
	wait(3)
end)

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerLeaving)

You have learned how to use Datastore-serivce, congrats!
You can now just save about anything in 76 lines of code.
Screenshot 2022-03-05 091457
Screenshot 2022-03-05 091506

FAQ

Info
  • Q: Setting async will remove any other data you have saved, so why are you using it?
  • A: If you want to add a new value to the datastore, insert it into the table that you are using to save the other data in. I am using it because its the most simple way of saving, and one of my favorites. :wink:

  • Q: How would I save ranks, capes, hats, etc?
  • A: The same way you would save weapons, but edited so its like leaderstats.

  • Q: Why is this important?
  • A: Its very important. There are other saving methods, like Datastore 2. However, knowing how code works is better than just following a guide on how to save and get it, even if it is longer.

SECTION 2: HOW TO FIX DATALOSS

Summary

Dataloss isn’t normal. Here are some tips you can cross off to help you along the way.

  • Save the player’s data to a variable right after the PlayerRemoving event fires.

  • Do not save unless the player is leaving the game. Auto-savers usually queue up datastores and cause dataloss. Only put them in your game if really have to.

  • Did you print? Did you check the output? These are simple things that a-lot of people forget to do, they usually fix dataloss problems after doing this.

  • Comment out pcalls. Pcalls stop errors and return them. However, the error message might not make sense or it isn’t returning the error properly. Once commented, you should find out why it isn’t saving.

  • Do you have the game:BindToClose function, and are you using it correctly? Studio shuts down too fast making the server unable to save. Add wait(3) to the function and see if it works.

  • Have you tried following section one? It explains everything in detail and it might just be the fix to your dataloss.


This is my first time ever creating a tutorial. I really hope this helps a-lot of you. I hope to create more of these.

Thank you! :slight_smile:

  • DeveloperBLK
29 Likes

Good tutorial also change dataloss to datalose

Nah, dataloss sounds better.

And also amazing tutorial! But still, I’ll just give a little more of a suggestion. You can add about re-trying in the Body point. This is because I don’t think I gotta talk about roblox datastores cuz you know how they are…

And yeah, that’s pretty much why you should add it cuz of that.

1 Like

Yes I agree. I was going to do that, but it could fail and queue up the datastore.

So I thought, “Why not just stop dataloss and queueing from happening?”
I have had no issues what so ever, but I could see myself making this edit into the thread!
Thank you :slight_smile:

1 Like

Remember pcall takes a function so you can just add pcall(data.GetAsync)

1 Like

Don’t do this in production. You don’t want the server to throw a DataStore exception and break the game.

Use the new task library: task.wait(3). However, the DataStore functions are asynchronous and iirc will yield the current thread until complete. You should save the data in BindToClose, but remember to disable the save on PlayerRemoving when only in Studio to avoid rate limits.

I would not use task.wait.
It does not yield at all, which you want to yield in this case.

Also, you should remove the comments in studio. Just don’t publish the game. :man_shrugging:

Lastly, no need to save the data in the BindToClose function. It doesn’t change a thing, and just makes unnecessary code.

I have used my way of datastore service in many games of mine. Everything works perfectly, no dataloss. :slight_smile:

2 Likes

Just some things to critique here, other than that it’s a decent tutorial for beginners

You are wrong about that, there are plenty of threads that tell you why task.wait is much better than wait (also according to an admin, wait will be deprecated)
msedge_ocTqEmEVua

It is absolutely necessary to use BindToClose for data saving, why else would the top datastore modules like ProfileService and Datastore2 use BindToClose to save data? It’s because it protects against data loss if Roblox servers were to crash or the game was forcibly shut down by a developer.

4 Likes

You could, but I’ve have no problems with not saving data in BindToClose. The PlayerRemoving event takes care of any player removing situations.

I’ve also had some problems with task.wait, but if you want to switch it you could. (It doesn’t hurt at all, its just faster, lol) I just don’t see myself using task.wait unless I have a-lot of loops in my scripts. Thanks for clearing this up! :slight_smile:

(If anyone has any more questions, I have explained it in my body details above)

2 Likes

BindToClose fires when the server shutdowns, player removing does not. So if you shut down your game you will get a wave of data loss if you don’t add BindToClose. Thanks.

2 Likes

Not unless you have a softshutdown script.
If you really want to add this feature, go for it. For me personally, I don’t need to add it. :slight_smile:

oh what if Roblox servers shutdown? Oh no. No save

2 Likes

Great tutorial, although this is a great way of doing it. Datastore2 is the better way to go if you are running a game with a lot of datastores and it’s better at preventing loss than any data store that I’ve seen by far. So I would recommend making a more advanced tutorial and going into Datastore 2 for those who already know how to do datastores but want a more reliable solution to data loss. Overall 9/10 tutorial! Great job.

3 Likes

Thank you! I would also try and switch to Datastore2 if you can.

Didn’t think about that one. I will add this into the tutorial. Thanks!

There is no need to be mean about it. :confused:

Edit: added

2 Likes

sorry if I sounded harsh but I can’t see how I’m mean

anyway thanks

3 Likes

May I ask why you say ProfileService over DataStore2? I see a lot of debate and lately the consensus has been more towards ProfileService with even Berrezza recommendation of Pservice over ds2. Of course everyone has there specific cases but do you not feel ProfileService would be better for most game cases and DataStore beginners?

While you did a good job at explaining what datastores are and what they do, here are some problems with your tutorial:

what?

What if the data does not save when the player leaves? I understand that we need to save it to a variable after the player leaves, but this does not make sense. It’s better to auto save between intervals of 30-60 seconds so it won’t queue up the datastore.

who uses while (true) do datastore:SetAsync(key,data) task.wait(0.5)end anyway lmao

Do not use DataStore:SetAsync(key,data) when you’re saving important data. The function SetAsync() overwrites data. Rather, use DataStore:UpdateAsync(key,transformfunction) when saving important data such as player data as it transforms the data and does not overwrite it. DataStore:SetAsync(key,data) helps you reach the datastore limit of 4MB more fast than DataStore:UpdateAsync(key,transformfunction)
You can use UpdateAsync() like this:

local success = pcall(function()
       DataStore:UpdateAsync(key,function()
              return data 
       end)
end)

However, UpdateAsync() takes more server power than SetAsync(). But I’d still recommend it when saving important player data. @ForeverHD did a fairly good job at explaining it. Here’s the post.

3 Likes

Thanks for the feedback. I usually do make an auto-saver, but I decided not to put it in the tutorial.
I will try and use updatesync more. Thanks! :slight_smile:

1 Like