Datastores: Caching is fundamentally broken (with details on how to fix logic-wise)

Datastore caching is fundamentally broken because the cache time is set on every successful request, even if that request returns the cached value.

In short, this means that a loop with GetAsync with an interval that is shorter than the cache expiration time (= 4 seconds) can cause a key to be forever locked into its old value, unless updated on the server where this loop is running.

How to reproduce:

  1. Open a new baseplate
  2. Publish the baseplate to your profile, a place with Studio API access
  3. Play the game
  4. Run this in the developer console server-side:
local data = game:GetService("DataStoreService"):GetDataStore("Test")
data:SetAsync("TestKey", "Default")
local oldValue = "Default"
while wait(1) do -- less than 4 second cache cooldown
   local value = data:GetAsync("TestKey")
   if value ~= oldValue then
      print("changed to:", value)
      oldValue = value
   end
end
  1. Go back to Studio, and run this code after a few seconds in the command bar:
local data = game:GetService("DataStoreService"):GetDataStore("Test")
data:SetAsync("TestKey", "Another Value")

Observed behavior:

In the live instance, nothing appears in the output.

Why is this unexpected:

This is unexpected, because I updated the value through Studio. This is because of the issue I described in the first paragraph of this thread. Actually, if you run the following code (with an interval of >4 seconds) instead and repeat the steps, you will see that the change is registered correctly:

local data = game:GetService("DataStoreService"):GetDataStore("Test")
data:SetAsync("TestKey", "Default")
local oldValue = "Default"
while wait(5) do -- >= 4 second cache cooldown
   local value = data:GetAsync("TestKey")
   if value ~= oldValue then
      print("changed to:", value)
      oldValue = value
   end
end

This should also occur with the wait(1) loop, but it doesn’t, for reasons described earlier in the thread.

Expected behavior:

The value I updated through Studio should be registered, no matter how tight the interval of the loop is. The live instance should always be able to check for new values 4 seconds after the latest value was fetched from the remote data services.

How to fix this issue:

It’s simple: don’t set the cache time again when the current request is a cache hit. Only set the cache time when a remote call was performed. This way, if you have a loop with GetAsync, it will consume budget and perform a remote call once per 4 seconds (= the cache expiration time), rather than getting stuck at the old value forever.

In short: make sure the cache can be busted even in a tighter loop than the 4 second cache time interval.

17 Likes

Ouch, that’s a pretty significant bug! Good catch

2 Likes

This issue has been resolved. Thanks for bringing this to our attention.

14 Likes

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