I don't understand the datastore limit

Hey,

Description

I’m currently working on an RPG fight game, where players can have a lot of differents heroes.
Do you know “Clash Royale” mobile game ?
Okay, so i was looking to do a system of heroes level and heroes number amount, like when players get 20/20 amount of an hero for exemple, we can level up it ect…


Info datastore

So here i go, to save the heroes level and number amount, i’m using 2 differents datastore, that contain an “IntValue” for each heroes.

The thing is, i don’t realy understand how the data limit work, i’m afraid about it.
Like, if there is too much heroes, lets say there is 30+ heroes in the game, that mean there will be +60 saved value in datastore only for heroes. There other things to save too, like game currency, items equiped, settings ect…


Questions

So what the datastore limit mean exactly ?
I saw somewhere that it is only for the amount of character of each values.

Does it will broke my datastore if there is too much value to save or should i don’t need to care about it ?


Scripts

Here is the “Level” datastore script:
The “Amount” datastore script is same, only the datastore name change.

game.Players.PlayerAdded:Connect(function(Player)
	local DataStore = game:GetService("DataStoreService")
	local StoreName = DataStore:GetDataStore("HeroesLevel")
	local GetData =  StoreName:GetAsync(Player.UserId)
	
	local Folder = Instance.new("Folder")
	Folder.Name = "HeroesLevel"
	Folder.Parent = Player
	
	wait(0.25)
	
	local Knight = Instance.new("IntValue")
	Knight.Name = "Knight"
	Knight.Parent = Folder
	Knight.Value = GetData[1] or 1 --unlocked
	
	local Archer = Instance.new("IntValue")
	Archer.Name = "Archer"
	Archer.Parent = Folder
	Archer.Value = GetData[2] or 1 --unlocked

	local FireMage = Instance.new("IntValue")
	FireMage.Name = "FireMage"
	FireMage.Parent = Folder
	FireMage.Value = GetData[3] or 0 --locked

	local Thor = Instance.new("IntValue")
	Thor.Name = "Thor"
	Thor.Parent = Folder
	Thor.Value = GetData[4] or 0 --locked
end)

game.Players.PlayerRemoving:Connect(function(Player)
	local DataStore = game:GetService("DataStoreService")
	local StoreName = DataStore:GetDataStore("HeroesLevel")
	local Folder = Player:FindFirstChild("HeroesLevel")
	
	if Folder ~= nil then
		local SaveData =  {}
		
		for _,Child in pairs(Folder:GetChildren())do
			table.insert(SaveData,Child.Value)
		end
		StoreName:SetAsync(Player.UserId, SaveData)
	end
end)
5 Likes

Each Key in a Datastore can hold up to 4MB of data (you have nothing to worry about). The limit you are referring to is the Throttle limit which determines the frequency of which you can make a Datastore request (GetAsync, UpdateAsync, SetAsync, etc).

Datastore systems can get very complicated and difficult to manage, instead of making your own Datastore system I recommend just using an already proven one like ProfileService.

2 Likes

There isn’t really a need for any special module for data saving as long as you aren’t doing anything weird.

You can find more info about DataStores here:
Data Stores

In short, normal DataStores are constructed as so.

local dataStoreService = game:GetService('DataStoreService') --Getting the DataStore service
local levels = dataStoreService:GetDataStore('Levels') --Requesting a specific DataStore

local Players = game:GetService('Players') --Getting the Player service
Players.PlayerAdded:Connect(function(player) --Getting each player as they join
	local userData = levels:GetAsync(player.UserId) --Getting saved data if any. Will return nil otherwise
	--Continue code
end)

Basically each DataStore can have a virtually infinite amount of keys. You first request a specific DataStore, then you request a key from that DataStore. The value of any single key is maxed at 4MB, which is essentially 4 million characters (where each character is a byte).

There is no need to worry about data limits unless your game is infinite.
However, you will need to worry about request limits. Get and Set requests each have a cooldown of 60 + (number of players in the server * 10). In addition, there’s also a 6 second cool down between set requests for the same key.

If there are too many requests, it will start a queue and will go through said queue in order as it becomes possible (automatically).
Lastly, if you do happen to hit a data limit, it will simply fail to save.

23 Likes

Alright, thank you for your answer, you explained it well !

So if i well understand, i currently don’t need to worry about the Key (6Mb) limit because my game isn’t “Infinite”.

Also for the “Get” and “Set” cooldown, it should be fine.

My game have 6 players per server, and probably no more than 10 differents datastore, so a maximum of 10 “SetAsync” and 10 “GetAsync” per players, because i’m only getting or setting all values of a datastore in a table, so it should count only for 1 and not 1 for each values.

Save script

game.Players.PlayerRemoving:Connect(function(Player)
	local DataStore = game:GetService("DataStoreService")
	local StoreName = DataStore:GetDataStore("Currency")
	local Folder = Player:FindFirstChild("Currency")
	
	if Folder ~= nil then
		local SaveData =  {}
		
		for _,Child in pairs(Folder:GetChildren())do
			table.insert(SaveData,Child.Value)
		end
		StoreName:SetAsync(Player.UserId, SaveData)
	end
end)

Load script

game.Players.PlayerAdded:Connect(function(Player)
	local DataStore = game:GetService("DataStoreService")
	local StoreName = DataStore:GetDataStore("Currency")
	local GetData =  StoreName:GetAsync(Player.UserId)
	
	local Folder = Instance.new("Folder")
	Folder.Name = "Currency"
	Folder.Parent = Player
	
	wait(0.25)
	
	local Coins = Instance.new("IntValue")
	Coins.Name = "Coins"
	Coins.Parent = Folder
	Coins.Value = GetData[1] or 0
	
	local Gems = Instance.new("IntValue")
	Gems.Name = "Gems"
	Gems.Parent = Folder
	Gems.Value = GetData[2] or 0
end)

GetData[number] should not count, only the “GetAsync” on top count.

So as i understand, the cooldown limit for me should be 60 + 6 * 10 = 120 GetAsync() and SetAsync() per minutes.

So if there is an auto save for everyone at same time, it should do 10 * 6 = 60 “SetAsync” at aproximatively same time, it is at half of the limit.

Auto-saves generally aren’t needed. Saving on player removing should be fine. However, I would recommend keeping the values stored outside of the Player object as it may remove faster than the server-script can read from it.

There are a few things to look out for when working with DataStores, however. For one, make sure a player’s data is fully loaded before allowing the game to save that player’s data on leave. This will prevent data corruption from occurring if a player joins and immediately leaves or crashes before their data loads properly.

The opposite is also true where if a player leaves a game and joins before their data has been saved, that can also create an issue. You could technically use a data locking system to prevent data from being accessed until the save has been confirmed, but all you really need is a title screen to slow them down.

DataStore v2 now also comes with data versioning, so you can use this to confirm or update data, as well as revert back to prior saves if needed with the automatic backup system it now has.

You also likely don’t actually need a bunch of DataStores, such as for currency or levels or what have you. The only reason you might want to have this separate is if using IncrementAsync or UpdateAsync, though both have restrictions. I generally just use SetAsync. Just be careful as SetAsync will completely overwrite data with no consideration for prior data.

Lastly, and completely unrelated, this is just a little tip for cleaner code:
You don’t need to use variable/object ~= nil or == nil. Instead, you can just do if variable/object then or if not variable/object then.

Example:

local part = workspace:FindFirstChild('Part')
if part then
	part.Name = 'hi'
	--Do stuff with part
end
1 Like

Auto-save every X minutes is needed. Player removing isn’t working when players get kicked for any reason, or when the game crash. So they will lost all their progression.

Okay, i will probably store the values in a folder in the replicated storage, and then destroy the folder when the save get finished after player removing.

I can add a cooldown for it.
Something like if the player isn’t in the server since at least 5 minutes, then the datastore will be not saved when leave.
Also, if the player join but there still his values in the replicated storage, then it will wait the value get saved and the old folder get destroyed, to create a new one, and reload the data.

I’m doing multiple datastore to be well organised.
In my current game, doing only 1 or 2 datastore will be annoying, because they will probably contain hundred of value on each…

PlayerRemoving does work regardless of how the player was removed, unless it’s the server itself that crashes.

However, there’s no harm in auto-saves unless you’re reaching request limits.

I’m not really sure about it.
Everytime i’m playing a game that have only a “Player Removing” save, i lost my progression each time i get kicked for “lost internet conexion”, “Server shutdown by roblox or developer”, “Being afk from +20min”

I also have make some test in my game, to shutdown the server while playing ot staying afk from +20min and it never save my stats when it happen.
That’s why i want to do an auto save every 5 or 10min, it is mostly a security if this thing happen.

I’ve personally never experienced that in my games. I’ve disconnected in all sorts of different ways and it has never failed me once. It may be your implementation.

Also remember that I said about data needing to be stored elsewhere besides inside of the Player object. The Player object may remove faster than you can read the data within. This is why data should be cached somewhere else, such as ReplicatedStorage or ServerStorage.

2 Likes