DataStore Get requests steal from Update's budget

Roblox is supposed to have different request budgets for various DataStore request types. For instance, I can call SetAsync 50 times without interfering with my pool for GetAsync. GetAsync seems to be using both GetAsync and UpdateAsync’s pool though. If I call GetAsync 50 times, I’ll lose 50 requests from my UpdateAsync budget.

This happens 100% of the time on production. I do not know how long this has been happening for. The issue can be reproduced by running the following code in the in-game developer console or through the command bar in Studio for a game that has Studio API access enabled:

local dsService = game:GetService("DataStoreService")
local datastore = dsService:GetDataStore("Test")

print("GET", dsService:GetRequestBudgetForRequestType("GetAsync"))
print("UPDATE", dsService:GetRequestBudgetForRequestType("UpdateAsync"))
print("SET", dsService:GetRequestBudgetForRequestType("SetIncrementAsync"))
for i=1,50 do
	datastore:GetAsync("Test"..tostring(i))
end
print("GET", dsService:GetRequestBudgetForRequestType("GetAsync"))
print("UPDATE", dsService:GetRequestBudgetForRequestType("UpdateAsync"))
print("SET", dsService:GetRequestBudgetForRequestType("SetIncrementAsync"))

Result:

5 Likes

Just a guess that this might be an implementation problem since UpdateAsync gets the old value internally it may be accidentally counting towards GetAsync.

I checked all the DataStore methods/budgets and this happens in a few other places:

Repro code
local dsService = game:GetService("DataStoreService")
local ds1 = dsService:GetOrderedDataStore("Test")
local ds2 = dsService:GetDataStore("Test")
local datastores = {ds1, ds2}
local methods = {"GetAsync", "SetAsync", "IncrementAsync", "UpdateAsync", "RemoveAsync"}

function getBudgets()
	local budgets = {}
	
	for _,item in pairs(Enum.DataStoreRequestType:GetEnumItems()) do
		budgets[item.Name] = dsService:GetRequestBudgetForRequestType(item)
	end
	
	return budgets
end

function checkBudgetUsage(datastore, method, startBudget, finishBudget)
	local usedBudgets = {}	
	local usageAmounts = {}
	
	for budget,value in pairs(startBudget) do
		if finishBudget[budget] < value then
			table.insert(usedBudgets, budget)
			table.insert(usageAmounts, "["..tostring(budget)..": Start("..value..") Finish("..finishBudget[budget]..")]")
		end
	end
	
	print("Usage for", datastore.ClassName, "with method", method..":", table.concat(usedBudgets, ", "))
	print("Usage amounts", table.concat(usageAmounts, ", "))
end

for _,datastore in pairs(datastores) do
	for _,method in pairs(methods) do
		local start = getBudgets()
		for i=1,10 do
			local keyName = "Test_"..datastore.ClassName.."_"..method.."_"..tostring(i)
			datastore[method](datastore, keyName, method=="UpdateAsync" and function() return i end or i)
		end
		local finish = getBudgets()
		
		checkBudgetUsage(datastore, method, start, finish)
	end
	
	local start = getBudgets()
	for i=1,5 do
		datastore:OnUpdate("Test_"..datastore.ClassName.."_OnUpdate_"..tostring(i), function() end)
	end
	local finish = getBudgets()
	
	checkBudgetUsage(datastore, "OnUpdate", start, finish)
end

local start = getBudgets()
for i=1,5 do
	local datastore = dsService:GetOrderedDataStore("Test_"..tostring(i))
	datastore:GetSortedAsync(false, 10)
end
local finish = getBudgets()
checkBudgetUsage(ds1, "GetSortedAsync", start, finish)

Update uses up the budget from the Get/Set pool which is understandable for the reasons @sparker22 mentioned, but GetAsync should not take requests from UpdateAsync’s pool (original issue). GetAsync does not trigger any updates.

The Wiki documentation should be updated for UpdateAsync using Get/Set’s budget, and Get should be updated to not use requests from Update’s budget.

4 Likes