What better ways are there to storing user data in a DataStore?

I have been trying to improve my security with user data recently, and have been trying to make a sort of ‘player customization’ for my game. When a player makes changes to their character, it opens the store, changes values, and resaves all without yielding like so:

local Data = game:GetService("DataStoreService"):GetDataStore("Example"):GetAsync(Player.UserId)
--Get the user's data
table.insert(Data["Hat"], ItemName)
--Change the "Hat" value to ItemName, something that was initialized prior
game:GetService("DataStoreService"):GetDataStore("Example"):SetAsync(Player.UserId, Data)
--Save changes

I’m not very experienced yet, but from my understanding something like this should be nigh impossible to exploit unless I have an unsecure remote sitting around somewhere. My issue arises when users call functions that do this multiple times in a short period. I get warnings of the datastore having too much queued up. I assume it takes time for the server to communicate to however Roblox stores data, but I don’t know how to store data securely WITHOUT doing this. Do any of you know any cleaner or faster ways to handle and store data?

Also, first time posting, so I apologize for any formatting mistakes.

So, for one thing, I recommend the datastore2 module found here:

As you could probably tell from the description, this module makes sure that player-specific datastores never lose data, and has all kinds of cool functions for setting/saving the data itself.

Answering your question, datastores have throttles to how often you can save something to a datastore. There’s a page on the dev wiki for this. You can either make a custom throttle that denies players from editing the datastore too often, in line with the datastore’s limits, or you could probably save this data to a table that is kept track of in-game, doing both is even better.

1 Like

You may read this, I find it very useful if you want to save the data. SetAsync is bad, use UpdateAsync.

Correct, the client can’t mess around with your datastore directly. Having unsecured remotes could allow them to manipulate it indirectly but that’s unrelated to datastore.

The trick is that you should only be calling GetAsync one time and caching that data. When the client wants to update their data, you can just change the cached data instead of updating it in your datastore. For SetAsync (you should be using UpdateAsync), you can use that when the user leaves, when DataModel:BindToClose() fires, and also generally used with an auto-save interval. If you keep this in mind it should be much more difficult to fill up the datastore queue.

2 Likes

Thanks for the quick responses! I’m not free at the moment, but I will give a look at :UpdateAsync() and the threads posted when I return home.

Caching is absolutely your best friend in an instance where you need to modify data frequently.

Retrieve the information you have in a DataStore and hold it in a ModuleScript designed to keep track of player data. If clients need access, use remotes to interact with it. Allow the module to expose methods for getting, setting and updating data for the server. Modify data in said ModuleScript as opposed to constantly using DataStore methods - there are very few cases where you need to upload data to the DataStore (saving, loading, after purchases, players leaving, autosaving, manual saving, etc).

Also, there is quite an informative discussion that occurred in the above thread that Operatik linked. Make sure to read that carefully - simply switching SetAsync to UpdateAsync isn’t enough. Both are still useful but it’s important to know when and the distinction between how they work.

1 Like

There’s nothing wrong with using SetAsync. If you don’t need to compare the new value to the old value, it’s perfectly fine to just set the value. You should only ever use UpdateAsync if your new value depends on the current value. An example of where to use UpdateAsync would be if you’re incrementing the value and would otherwise have to GetAsync immediately before your SetAsync.

I have been experimenting a bit today, and so far I’ve found that caching the user’s data in a series of DataValues within some folders in ServerStorage have helped tremendously with altering data rapidly. I do not however see any inherent advantages to using UpdateAsync over SetAsync at the moment, as these DataValues are uploaded as a table to the DataStore every minute or so, and upon leaving/shutdown. I have yet to test out the datastore2 module linked above, because honestly modules are still pretty beyond me, but it sounds very reliable in regards to avoiding data loss, so I will continue to research that in the meanwhile.

I fought a lot for the usefulness and merit of UpdateAsync in the thread that @Operatik posted.

While there’s a lot to talk about in regards to the use of either function, one key difference I can pull out is that SetAsync may potentially be unreliable. GetAsync caches, so that means that what you push from SetAsync may not necessarily be what comes out from a GetAsync call. UpdateAsync does not cache and provides a live value to be used as the sole argument in transformFunction.

This all being said, you should probably be fine the way you’re using SetAsync and your current data set. I don’t think you need the previous value and hopefully you aren’t calling GetAsync too frequently either (or really at all aside from first load or where necessary).

I really do encourage you at some point in the future to move over to storing data via ModuleScripts as instances can become costly once attached to the DataModel. Modules are very handy once you get the hang of how to implement them. Writing them is not hard, but determining best practices on a case-by-case scenario is where you’ll find most effort will be put into.

On wiki it states that SetAsync can be hazardous because it overwrites data, UpdateAsync is a much safer method as it updates not overwrites data, personally I’ve experienced a ton of dataloss issues with SetAsync and I’ve just recently switched to UpdateAsync.

For player data, when the player leaves, the data could be completely different (and usually will be). I don’t see any reason to compare new and old values when the new value could be completely different. Of course, you should do checks to make sure all your data’s there before you save, but you don’t have to use UpdateAsync to make sure what you’re saving isn’t nil.

Well you see, the whole point of UpdateAsync isnt about comparing 2 values, thats just a bonus, what it’s really about is updating data not over writting it, much harder to lose data that way.

1 Like

From what I understand, UpdateAsync is basically like a version history system. What @Complextic is saying is that UpdateAsync will basically cache the old value. If there is no old value, the code won’t fail, there will just be no old value to cache. SetAsync doesn’t do this, it only overwrites the value. The reason UpdateAsync is important is because if it were to fail, it can always fall back on the old value which prevents complete data loss. Having 90 points on the leaderboard instead of 100 points is better than losing all of your points.

Yeah thats basically the entire point behind UpdateAsync is if it cant make a new save instead of losing all of the data it will have the previous data it can fallback on.

Not necessarily. UpdateAsync isn’t remotely like a version history. It simply passes a non-caching value to transformFunction, that value being what the DataStore key currently has entered.

Code can still fail, regardless of whether you have a value assigned to a key or not. Either the data assigned to a key will be passed as oldValue or nil in the case of no value.

Depending on how you use UpdateAsync, you can experience the exact same data loss that you do with SetAsync.

Also, surprisingly, some tests last night after a discussion on UpdateAsync revealed interesting behaviour. It depends on what kind of failure you’re talking about.

As far as transformFunction goes, if you pcall UpdateAsync and transformFunction fails, transformFunction will throw an error but pcall will actually return success and nil. Depending on if you use the return from UpdateAsync, this could be detrimental.

You are way more likely to experience dataloss with SetAsync than you are with UpdateAsync and I speak from experience.

I’m not sure how it’s “much harder to lose data” with UpdateAsync. The only benefit I can see with UpdateAsync is if you’re saving the old data to another place or don’t check if the value you’re setting is nil. If there’s any other benefits that make it easier not to lose data, I’d be much more interested in using it. I just don’t see any major benefits.

I mean it literally says it in the screenshot above that SetAsync() can be hazardous.

Yes, but it doesn’t explain at all why. In the case of user data, you don’t necessary need to consider the old value; if the user’s in the server, the values you have been using in that server should be correct anyway.

It is hazardous because it overwrites data, meaning it completely erases the old data that was there previously.