Details on DataStoreService for Advanced Developers

This is quite possibly the best guide I’ve ever read on anything related to ROBLOX.
Awesome job, I wish I had this years ago, it would’ve saved me a lot of time.

4 Likes

Heads up, there’s a bug going around right now related to potential data loss if you try to call the same key in succession within a short period of time (<4s between attempts), more info here.

4 Likes

Are you sure that set requests do not set the read cache? I believe they do.

local services = {
	data_store = game:GetService("DataStoreService")
}

--local data_store = services.data_store:GetDataStore("name_0")
--local data_store = services.data_store:GetDataStore("name_1")
local data_store = services.data_store:GetDataStore("name_2")

game.Players.PlayerAdded:Connect(function(player)
	wait(10)
	local value = data_store:GetAsync("key")
	data_store:SetAsync("key", value == nil and 1 or value + 1)
end)

while true do
	wait(0.5)
	local value = data_store:GetAsync("key")
	game.ReplicatedStorage.RemoteEvent:FireAllClients("Value at: " .. os.time() .. " is " .. tostring(value))
end
game.ReplicatedStorage.RemoteEvent.OnClientEvent:Connect(function(message)
	print(message)
end)

Assuming wait() calls are not taking much longer than the cache time for the thread to be resumed (which they shouldn’t be and aren’t in my testing), this code should always print that the value is the initial value, and never get the incremented value since I am resetting the cache cooldown every ~0.5 seconds. However, in my testing, it does receive the new value.

Perhaps I’m missing something obvious, but it sure seems like GlobalDataStore:SetAsync() does indeed set the read cache for the key.

That aside, great thread! Very helpful. Shame that DataStores are so needlessly complex due to the odd caching mechanism.

3 Likes

All key writes that you do on a particular server will be immediately available on that server. Perhaps I could describe this more clearly, but with “setting the cache” I mean that it sets the cache time (the value is irrelevant in that description). For example, doing an UpdateAsync and then a GetAsync afterwards makes the GetAsync not consume any budget, since UpdateAsync set the cache time. SetAsync does not set the get cache time, so the GetAsync call after that will consume budget, even though the server should know what the most recent value is because of the recent SetAsync.

2 Likes

Ah, the things we learn when we try to reverse engineer and emulate a black-box system. I know what that’s like. Really great work.

2 Likes

Updated topic with new max size limit.

3 Likes

The limit is actually 2^22-1, or 4,194,303. You forgot to include the encoded double-quotes from the string you used to check the size limit. :slight_smile:

2 Likes

@Fractality can you confirm? ^

1 Like

I noticed an inaccuracy. In this, you state that the documentation is wrong about the 50 character limit for keys. I don’t know if this was true in the past, but it doesn’t seem to be anymore.

image

In this image, you can see the error happens on line 14 and not 13.

1 Like

You can query this directly by the looks of it.

print(settings():GetFVariable("DataStoreMaxValueSize"))
4194304

Which makes sense - that’s 4MB. The real limit is probably that minus one.

3 Likes

Yep looks like they fixed this. Edited the post.

2 Likes

The write cooldown says it is 6 seconds, however, when I try to use UpdateAsync every 6 seconds, it gives a warning.

Code in question:

while os.clock() - Start < TIME_BEFORE_FORCE_STEAL do
   print('trying')

   local LoadFuture = LoadProfileData(profileStore.DataStore, key)

   wait(6)

   if not LoadFuture:isResolved() then
      LoadFuture:await()
   end
end

It’s hard to see what’s happening here because your code sample is not self-contained, but you probably don’t want to wait exactly 6 seconds between requests. The network time taken for requests / for wait(6) will vary a bit so you could be hitting the server a few tens of milliseconds too early if you do it this way.

As far as I understand, the write time is set upon datastore call completion, and read upon datastore call invocation, so you need to wait more than 6 seconds in practice. You’re doing this with a future so that wait(6) is measured between the start times of the requests, not the span between a request ending and a request starting.

3 Likes

If I waited for the call to be complete + 6 seconds, would that work?

I think that should work because then you’re sure the time between the request ending and starting is >=6 (since wait(6) guarantees >=6 seconds wait)

3 Likes

Thanks a lot for this comprehensive material.
One question: Write cooldown 6 sec - it has its own queue? does it ever throw?
BTW, the read cache issue is very important to me. Has it not been fixed since your OP?
Ok, it’s three questions…

I believe there are only queues for every kind of operation. So write cooldown violations go in the same queue as writes out of budget (SetIncrementAsync).

If your queue is full (30 requests max) it can throw, yes.

I believe that was fixed:

Let me update the thread accordingly

2 Likes

I don’t think these limits are correct. With 10 players I am managing 1,380 requests for Set & Get. This means the rate is actually what this user posted. Request Limits on Data Store Errors and Limits page are not accurate

3 * ( 60 + ( 40 * 10 ) ) = 1,380.

1 Like

I’m not fully convinced these rates are intended, see: Request Limits on Data Store Errors and Limits page are not accurate - #5 by buildthomas

If they do end up being intended and the official documentation is updated to reflect it, I will update this post.

The rate calculation is still the same, it’s just the multiplier seems to be 40 instead of 10 currently.

2 Likes

It’s possible they might not have reset it back. It might still be good to put a little note somewhere on your post that it is currently 40 per player just so others know. It can always be removed if they decide to reset it back. I will try and get in contact with someone and find out if it’s permanent or not.

1 Like