If you aren’t directly mutating or using the previous data then there’s a good chance (but not a guarantee) you aren’t making use of the full benefits of UpdateAsync. UpdateAsync being used like SetAsync isn’t the exact same since they’re both implemented differently but they’re similar.
UpdateAsync essentially “puts more thought” into what it’s doing when saving data before doing so - respecting other servers’ attempts to manipulate the same key, retrying until the data is set and allow the developer to manage how data gets written, up to cancelling the write. It also returns the data you’ve written to the DataStore since it’s a callback - some systems may need this to avoid getting trapped into using GetAsync and SetAsync where not necessary.
SetAsync, on the other hand, is comparable to attempting to change the value of a key in a dictionary. It puts no thought into data management, only giving the key-value pair to the data service and then dropping right after. It attempts no validation, does not ensure your data is properly saved and it doesn’t return anything. SetAsync realistically should only be used if you need to force a change, otherwise UpdateAsync is canonical for all changes. Even the name suggests as such.
Though it’s a more complex example, ProfileService only uses UpdateAsync when interfacing with DataStores (no GetAsync, no SetAsync). Here’s the standard transform function it gives to UpdateAsync. Essentially UpdateAsync is called whenever DataStores are required and if edits are required then they’re committed to the latest data (value passed to UpdateAsync) before returning the data. It also has various settings it can read off of to help it determine what to do within the UpdateAsync. You get fetching and saving all at once (UpdateAsync can technically be used as a Get and a Set in one call, which ProfileService awesomely does).
DataStore2 also uses UpdateAsync when writing data as opposed to SetAsync, though unlike ProfileService it does not hybrid UpdateAsync as a getter and a setter so it does feature GetAsync. Check SavingMethods. This is not a bad thing however, as DataStore2 is also reputable and battle-tested in production-level games with large traffic. DataStore2 was initially the “solution” a lot of developers chose and still choose to work with data in their games.
There’s absolutely benefit in using UpdateAsync over SetAsync. I do recall it was a point of contention at one time and I too didn’t have too much knowledge on it but when you figure out some neat use cases for it it’s a pretty helpful function to apply. Generally it’ll all depend on your exact systems and needs so you don’t have to use UpdateAsync however I strongly recommend it in all cases.
That being said, your questions.
For question 1:
UpdateAsync is very flexible and can cover your use cases whether or not you’re using a dictionary, however I would more strongly recommend UpdateAsync for dictionaries and typically most data you’ll ever need to save will be dictionaries. For non-table data, I don’t really recommend UpdateAsync because you probably don’t need the whole line of benefits just to ensure a single value gets saved.
You could make your own wrapper with SetAsync but you’ll have to do a bit more work and problem solving to get that wrapper ready for good use. It’s ultimately not worth it for what you can get with UpdateAsync and this is just from talking about the developer-side, not even getting into how internally different SetAsync and UpdateAsync are.
For question 2:
Since UpdateAsync passes the latest saved value as an argument to the function, you should try and make good use of that in your transform function. You can just hard-write the data by returning the new data and doing nothing else but consider if that’s the best approach or if you need anything else. Additionally because you can cancel the write by returning nil, this further incentivises using the old data in your transform function to determine how data should get written/modified.
Never SetAsync. SetAsync doesn’t care and isn’t mindful. It takes what you give it and throws it at the data service without looking back. UpdateAsync will get your data and ask you what you want to do with it. You can then tell it that it doesn’t need to ask the data service to write anything or you can ask it to make a few changes before carefully ensuring that what you told it to save does in fact save and then even giving back the newly saved data so you can take that home to other code in your game.
Might’ve repeated a few things unintentionally but good for emphasis. Hope the last analogy of “SetAsync the uncaring” and “UpdateAsync the mindful” helped you further consider which to use.