IncrementAsync() on the same key is not always atomic, causing DATA LOSS

In My Movie, we have a global “videoId” counter that is incremented 1 to assign to a video ID. We do this so that we can play random videos by picking a number 1 through videoId.

However, we are getting a bug where this :IncrementAsync(1) call will sometimes return the same value, dropping the in-between.

This means that people’s videos get completely erased, as the ID that they were pointing to is now clobbered by something else.

Expected behavior

I expect IncrementAsync to be atomic, thus meaning :IncrementAsync(1) always returns 1 next.

21 Likes

Hi there,

When did this issue start to happen? Assume you are incrementing this key across many game servers, how do you handle failures from IncrementAsync? Feel free to DM me with more info if there are information you don’t want to share publicly.

Thanks!

This started happening over the past few days, but I can’t say it’s a regression–our game just recently released and we have had explosive growth, and thus more chances for this to occur.

I am not sure what you mean about handling failures from IncrementAsync. If you mean errors, we stop the video upload and ask the user to try again. If you mean these atomic inconsistencies, I unfortunately don’t have an answer–we are just losing the data because two videos upload to the same ID.

In the most recent update I have replaced IncrementAsync with UpdateAsync, and I haven’t gotten reports of it happening yet.

2 Likes

Congrats on the growth for new game!

Let us know if this issue still happens to you with UpdateAsync. At that point it would be very helpful if you can share some scripts of how your game uses the DataStores APIs.

Update: I removed the part mentioning that IncrementAsync is similar to SetAsync. After discussing within the team, we believe that’s indeed a bug and working on a fix right now. After the fix is live, IncrementAsync will become atomic (again).

1 Like

If IncrementAsync is not meant to be atomic, I would definitely put that in the documentation! Everyone I’ve described this to has assumed that this is the case.

The code itself is wrapped up in some abstractions, but it essentially logically boils down to:

local success, problem = pcall(function()
    local nextVideoId = videoIdStore:IncrementAsync(1)
    videoStore:SetAsync(nextVideoId, video)
end)

It is now (essentially):

local success, problem = pcall(function()
    local nextVideoId
    videoIdStore:UpdateAsync(function(currentVideoId)
        local finalVideoId = currentVideo + 1
        nextVideoId = finalVideoId
        return finalVideoId
    end)

    videoStore:SetAsync(nextVideoId, video)
end)
5 Likes

I always assumed it was atomic as well, and am glad I never used it! The way most people have used it over the years (to my knowledge) seems like it was meant to be atomic, but never actually was. It doesn’t seem useful for much outside of being a simple way to increment something atomically, so the documentation should definitely be changed.

6 Likes

Thanks for the feedback. We will look into how we can make our documentation more clear.

3 Likes

Hi all,

I made an update to my earlier post. We believe IncrementAsync should be atomic and are working on a fix. In addition to that, the exact behavior will be documented after the fix is live.

8 Likes

The fix for IncrementAsync is live and you can expect each IncrementAsync call will return a different value. However, if multiple IncrementAsync is invoked and processed at the same time, there is no guarantee on the order of processing.

6 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.