Considering using UpdateAsync as opposed to a Get/Set combination for initial data - thoughts?

My current paradigm for handling DataStores involves using a GetAsync request when a player joins to load their data and a SetAsync request if they have no data stored. This is represented in code as the following example (I don’t really like my example and how it uses raw data):

Code sample with Get-Set combination
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local UserDataStore = DataStoreService:GetDataStore("UserDataStore")

local DataCache = {}

local function GetDataTemplate()
    return {
        A = 1,
        B = 2,
        C = 3,
    }
end

local function handlePlayer(Player)
    local success, result = pcall(UserDataStore.GetAsync, UserDataStore, Player.UserId)
    if success then
        if result then -- No ternary short circuiting for a reason
            DataCache[Player.UserId] = result
        else
            local InitialData = GetDataTemplate()
            DataCache[Player.UserId] = InitialData
            local success = pcall(UserDatastore.SetAsync, UserdataStore, Player.UserId, InitialData)
        end
    else
        -- I don't actually do this, this is terrible.
        Player:Kick("Data fetch failure. If this happens repeatedly, contact developer.")
    end
end)

Players.PlayerAdded:Connect(handlePlayer)

for _, Player in pairs(Players:GetPlayers()) do
    handlePlayer(Player)
end

Recently though, I’ve been thinking about UpdateAsync and it’s uses in my codebase though. One of the beauties of UpdateAsync is that it serves as both a Get and Set request while only spending an UpdateAsync request and the Get part of it is non-caching. So, I’ve thought about using this as opposed to my disgusting Get-Set combination.

Code sample with UpdateAsync
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local UserDataStore = DataStoreService:GetDataStore("UserDataStore")

local DataCache = {}

local function GetDataTemplate()
    return {
        A = 1,
        B = 2,
        C = 3,
    }
end

local function handlePlayer(Player)
    local success, result = pcall(UserDataStore.UpdateAsync, UserDataStore, Player.UserId, function(old)
        if old then
            return old
        end
        return GetDataTemplate()
    end)

    if success then
        DataCache[Player.UserId] = result
    else
        Player:Kick("No data") -- Bad
    end
end)

Players.PlayerAdded:Connect(handlePlayer)

for _, Player in pairs(Players:GetPlayers()) do
    handlePlayer(Player)
end

This is an almost blatant abuse and misusage of UpdateAsync, though I feel as though I get better usage by taking advantage of that non-caching old value and handle both old and new data all in one go, while also ensuring data saves after “loading”.

Any thoughts about this paradigm? I haven’t really looked into depth about potential bottlenecks, improper usages or problems with doing this.

Not sure if this post is of use, but perhaps you will learn something from section 7, Notes on UpdateAsync.

Not sure if you mentioned everything here already though, as I haven’t memorized the properties of datastore methods yet. Hope it helps!

1 Like

Okay then. Essentially, what I do makes no difference. I had a feeling this might’ve been the case. UpdateAsync needs a way to fetch the value in the first place, which is where the Get comes from. Then it writes to the DataStore, which is where the Set comes from.

In either case, I did mention that doing this would be a blatant abuse of UpdateAsync. So not only does it abuse it, but it also performs the same Get-Set combination without a mess of a codebase to look at.

I will retain the same paradigm I’ve held regarding initial data requests for now via the first code sample I linked. And perhaps this’ll serve as a note to anyone else; don’t do what I did with the second code sample and misuse UpdateAsync needlessly. :upside_down_face:

1 Like