UpdateAsync very rarely calls the transformFunction twice on different versions of stored data

Issue Type: Other
Impact: High
Frequency: Very Rare
Date First Experienced: 2021-03-16 00:03:00 (-04:00)
Date Last Experienced: 2021-03-25 00:03:00 (-04:00)

Reproduction Steps:
This is an incredibly rare but potentially severe bug which has no reliable reproduction that I have found.

Expected Behavior:
UpdateAsync should only call the transformFunction multiple times if data is being written to a key simultaneously from multiple sources.

Actual Behavior:
I am aware that UpdateAsync can call the transformFunction multiple times if the data is being written to from multiple sources simultaneously; that is NOT what I am attempting to describe here.

When saving player data, I detect if it is “stale” by comparing against a save ID which increments by one each time the player’s data is saved. In the very rare situation that the save ID does not match what is expected, the player is kicked from the game. Over the past few days, my players have been reporting an issue where they will go into a “stale data loop”; about one player per day will have an issue where they continually get kicked for stale data every time they join the game.

I managed to catch it while it was happening this time, and figuring out what was going on was a confusing mess. However, I came to the conclusion that every time the game would call UpdateAsync on this player’s key, it would call the transformFunction twice. I believe it was calling it on two different versions of the data; first it would call it on the version that GetAsync returned, and then it would call it using a “hidden” version that I had no way of reliably accessing. This “hidden” version of the data had an unexpected save ID which is what caused the stale data loop to occur.

While trying to debug in my live game, I decided to call UpdateAsync on the user’s data to see if I could get it to return both values. I tried with a return value of nil in the transformFunction, like so, so that no data would be overwritten:

dataStore:UpdateAsync(key, function(oldData)
	print(oldData.SaveId)
	return nil
end)

but this only returned the expected SaveId. Then, I tried returning oldData itself from the function, using the following code:

dataStore:UpdateAsync(key, function(oldData)
	print("hello")
	return oldData
end)

I do not know what possessed me to only print hello, but that led to the following result in the server output:


please ignore the timestamps

which, as you can see, called the transformFunction twice, though we cannot be certain on what data it was called. Unfortunately, performing this call to UpdateAsync fixed the issue, and all subsequent calls to the key have been performing as-expected, so I was unable to get any more info on what stored data was being used with each call.

A very confusing issue with not too much to go off of, I know, but if there were any changes made to DataStoreService in the past ~week, they could be responsible for this issue. I tried as hard as I possibly could to rule out any fault on my end, though there always remains the possibility that something I am doing is the culprit.

In addition, this is probably somewhat ephemeral. All other instances of stale data loops in my game have cleared up after a short period of time (less than an hour) without my intervention.

Workaround:

5 Likes

Thanks for the report! We’ve filed a ticket to our internal database and we’ll follow up when we have an update for you.

1 Like

Is this issue still happening? We have made various fixes, but since it’s hard to reproduce so please let us know if this is happens.

1 Like