Stop using SetAsync() to save player data

Lots of people talking about the downsides of GetAsync, but has anyone mentioned that GetAsync caches values yet? That could introduce some seriously bad bugs if mishandled.

3 Likes

I personally suggest utilizing a system that is able to detect if datastore had issues loading data so you can make sure data doesn’t overwrite if somebody’s data fails to load. I had this issue in a game awhile back where my data failed to load in and when I left and came back a day later, I found my entire game to be reset back to the beginning even with all my purchases made.

I cannot fret how important it is to make sure that you don’t accidentally overwrite data. I genuinely ended up leaving that game in a storm of frustration. The developer was also unreachable for contact.

3 Likes

Never realized how dangerous my code really was! Great tutorial, definitely going to go back and rewrite my datastores!

1 Like

Don’t really see how this could work within my code frame, as I only use SetAsync when a player leaves. I don’t want to have to use UpdateAsync on every single piece of data that may need updating (levels, exp, currency, items they own, etc. Seems much much easier to just go

PlayerDataStore:SetAsync(player.UserId, PlayerData[player.UserId])

Never had problems with it before

Do you know an dev desire this mate? Obviously no.
Can’t agree more.

Can’t agree more mate this is truly the biggest worst what a game can have mate.

I am bringing this topic back up because I want to know what is DataId? And what do we have to set it to in the playersData table (Assuming playersData is a table)?

DataId is just a value that increments to show that your data has changed since you last saved it to the DataStore to prevent overwriting new data with old data. Notice how in the :UpdateAsync() callback, you ONLY return playersData if their DataIds are equal.

1 Like

How can I use this with multiple different data points?

return {
	-- Main Data
	Cash = 0,
	
	FavouriteColor = {83, 209, 255},
	
	Codes = {},
	
	-- House Data
	House = 'Default',
	
	Houses = {
		['Default'] = {
			Exterior = {},
			Interior = {},
			Furniture = {}
		},
	},
	
	Furniture = {},
	
	-- Character Data
	Character = {
		Name = '',
		Age = 'Adult',
		HatAccessories = {},
		HairAccessories = {},
		FaceAccesssories = {},
		NeckAccessories = {},
		ShoulderAccessories = {},
		BackAccesssories = {},
		WaistAccesssories = {},
		Face = '',
		Shirt = '',
		Pants = '',
		Color = 'Light orange',
	},
}

Cause then I’d have to do this…

function DataManager.UpdateCash(player, amount)
	local User = PlayerData[player.UserId]
	if not User then return end
	
	if User then
		PlayerDataStore:UpdateAsync(player.UserId, function(oldValue)
			local previousData = oldValue or {Cash = 0}
			if User.Cash == previousData.Cash then
				User.Cash = User.Cash + amount
				
				return User
			else
				return nil
			end
		end)
	end
end

function DataManager.UpdateFavouriteColor(player, newColor)
	local User = PlayerData[player.UserId]
	if not User then return end
	
	if User then
		PlayerDataStore:UpdateAsync(player.UserId, function(oldValue)
			local previousData = oldValue or {FavouriteColor = {0, 0, 0}}
			if User.FavouriteColor == previousData.FavouriteColor then
				User.FavouriteColor = newColor
				
				return User
			else
				return nil
			end
		end)
	end
end

function DataManager.UpdateHouse(player, newHouse)
	local User = PlayerData[player.UserId]
	if not User then return end
	
	if User then
		PlayerDataStore:UpdateAsync(player.UserId, function(oldValue)
			local previousData = oldValue or {House = 'Default'}
			if User.House == previousData.House then
				User.House = newHouse
				
				return User
			else
				return nil
			end
		end)
	end
end
-- and etc..... forever and ever and ever.....

Right now I’m EXTREMLY confused how do I make it work with table values, it feels like a labyrinth and I can barely understand anything in UpdateAsync() method.

3 Likes

Hi, one point I am missing out is most of the times GetAsync() is used when a player joins the game. If it fails or delivers outdated data, you are claiming UpdateAsync() is a good way to compare it with current DataId. The problem is, if the player has just joined the game, there is simply no DataId for him. What I don’t understand is what do you compare the DataId returned by GetAsync() with?

3 Likes

(I might be really late) Well, in the method he used, there is a compare thing that it checks if there is already a data

So in this one it checks if there is already a saved data and if there ain’t a saved data it adds the DataId = 0 in the previous data.

Therefore it simply saves it as the first data.

1 Like

For playerdata, UpdateAsync isn’t as important to use then real time systems. For example, in a clan system if you want to add members to a clan you would use updateasync, if you used setasync you would need to get the existing members first using getAsync, and not to mention getAsync cahes so you would probbaly be overiding members and causing disasters.

However I think for playerdata setAsync is a better choice.

UpdateAsync is absolutely important to use in real time systems and should not be dismissed for SetAsync. ProfileService is a good example of a rising library that works with DataStores. It takes advantage of the fact that UpdateAsync is both a getter and a setter to perform some of its internal work, especially with regards to locking data to sessions.

Adding or removing members to a clan would be a matter of adding or removing someone from a collection, so it’s held in the same vein as incrementing and decrementing data: UpdateAsync is good to use here so that you’re actually mutating the data. SetAsync is candidate for failure in this workflow because you now have to account for other servers and determine which holds the true value for the DataStore. Determining this point of truth can be extremely tedious.

SetAsync is not a better choice for player data and UpdateAsync would be equally as bad if you use it like SetAsync. Use UpdateAsync for what it’s good for, which is more than just overwriting values. It allows you to modify and update it, as its name suggests.

4 Likes

im confused : ( so do i still use getasync() ??

That’s not how you’re suppose to use that…

1 Like

Now your making two api requests…

What’s the point of doing that whatever you return in the updateasync callback becomes the new value of the datastore key.

DataStore:UpdateAsync(playerId, function(newValue)
return true – sets the key as the true
end)

Remember UpdateAsync is a callback takes a callback as an argument, and usually Callbacks allow you to return values which the function will use (the return value), while running your own code.

This is why callbacks are super powerful, and yah in all my modules I make privately for my own use callbacks.

1 Like

I don’t think things like this are very efficient, imagine every time you buy something in a shop the server performs a datastore update request. That would hit the data limit soon if your having a game where players frequently gain points and buy items.

Instead what I would recommend is local data manipulation. For example, when a player buys an item, the data should be manipulated locally to avoid datastore limits. Saving would be periodically and on the player leaving. Here come’s the question now though, how useful would UpdateAsync actually be if we save the local manipulated data periodically and on the player leaving?

Since all we’re doing is setting a value to the key to the datastore (the local data) aren’t we supposed to use SetAsync(), what’s the point of using UpdateAsync() here, we don’t care about the old data, if we did though could you tell me why we have to?.

In this case UpdateAsync() only seems useful to compare data when the player joins, as an alternative for GetAsync() not SetAsync().

So I’m confused at the example part, how are you supposed to use this .DataId? Where do you get it from on players data?

Oops sorry for the bump! But still, are you supposed to have theirs dataVersion gotten when they join the game?

Edit: for anyone coming from my UpdateAsync post please. Don’t. Care. About. This. I swear I know what I’m talking about! I have learned a lot in a small time frame for many reasons.

Hey, just a question for anyone that bumps on here, would this be an example of saving data properly?

local function Save(Player)
	local Stats = ServerStorage:WaitForChild("Save_" .. Player.UserId);
	local TableToSave = {DataId = 0}
	local Key = "Player_" .. Player.UserId
	for _, v in pairs(Stats:GetChildren()) do
		local KeyName = "_" .. v.Name
		local KeyValue = v.Value
		TableToSave[KeyName] = KeyValue
	end
	Store:UpdateAsync(Key, function(OldData)
		local DataId = OldData or {DataId = 0}
		if OldData.DataId == DataId.DataId then
			TableToSave["DataId"] += 1
			return TableToSave; else return nil;
		end
	end)
end

Thanks. It would be a disaster for my friends if they’d see their data missing a number often.