Datastores: Budgeting API and enums should be easier to understand and use

As a Roblox developer, it is difficult to understand Datastore budgeting, because the enumerators of Enum.DataStoreRequestType are obscure and do not map directly to the Datastore operations that are available. Moreover, budget consumption differs between the two available Datastore types. Developers need to look up on the wiki what kind of budget each Datastore request uses constantly for this reason, it is hard to memorize.


For example, if I want to check if GlobalDataStore::RemoveAsync is possible, I need to check if the SetIncrementAsync budget is higher than 0. They do not relate at all in terms of naming.

And when we switch to OrderedDataStore::RemoveAsync, I now suddenly need to check SetIncrementSortedAsync budget, instead of SetIncrementAsync.

For GlobalDataStore::UpdateAsync, I can simply check the UpdateAsync budget. However, for OrderedDataStore::UpdateAsync, this is not sufficient, as explained in this thread, you need to check for both GetAsync and SetIncrementSortedAsync budget to be both higher than 0.

In short: these enumerators are obfuscating in nature for no reason, and they also leak unnecessary implementation details that make them all too difficult to use. Budgeting checks should be abstracted more to make it as easy as possible for developers to write these checks by heart, to prevent mistakes due to misunderstanding how these DataStoreRequestType values map to the various Datastore operations, and to make them more backwards-compatible in case the budget consumption of a certain operation changes.


Even though I shouldn’t put solutions in feature requests as a developer, I would still like to describe a possible solution to explain what I mean and to show how it would improve the structuring and clarity of API calls that developers make in their code.

My suggestion is to deprecate the following API:

	Enum
		-DataStoreRequestType
			-GetAsync
			-GetSortedAsync
			-SetIncrementAsync
			-SetIncrementSortedAsync
			-UpdateAsync
			-OnUpdate
	DataStoreService
		-GetRequestBudgetForRequestType(DataStoreRequestType requestType)

And to add the following API:

	Enum
		+DataStoreMethod
			+Get
			+Increment
			+Set
			+Update
			+Remove
			+OnUpdate
			+GetSorted
			+AdvanceToNextPage
	GlobalDataStore
		+GetRequestBudgetForMethod(DataStoreMethod method)
	OrderedDataStore
		+GetRequestBudgetForMethod(DataStoreMethod method)

Note that the budget checking has shifted from DataStoreService to the GlobalDataStore/OrderedDataStore classes themselves, and we now have enumerators per operation rather than per arbitrarily defined budget types that have too much dependency on implementation details of internal code. This allows us to distinguish budget consumption between datastore types for the same operation, and to write clearer checks, as will be shown next.

Compare these listings of old vs proposed API:

Before:
GlobalDataStore::GetAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.GetAsync) > 0
	
GlobalDataStore::IncrementAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.SetIncrementAsync) > 0
	
GlobalDataStore::OnUpdate
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.OnUpdate) > 0
	
GlobalDataStore::RemoveAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.SetIncrementAsync) > 0
	
GlobalDataStore::SetAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.SetIncrementAsync) > 0
	
GlobalDataStore::UpdateAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.UpdateAsync) > 0
OrderedDataStore::GetAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.GetAsync) > 0
	
OrderedDataStore::IncrementAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.SetIncrementSortedAsync) > 0
	
OrderedDataStore::OnUpdate
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.OnUpdate) > 0
	
OrderedDataStore::RemoveAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.SetIncrementSortedAsync) > 0
	
OrderedDataStore::SetAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.SetIncrementSortedAsync) > 0
	
OrderedDataStore::UpdateAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.SetIncrementSortedAsync) > 0
	and DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.GetAsync) > 0
	
OrderedDataStore::GetSortedAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.GetSortedAsync) > 0
MockDataStorePages::AdvanceToNextPageAsync
	DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.GetSortedAsync) > 0

Note:

  • For most of these, the method name has nothing to do with the budget type.
  • OrderedDataStore::UpdateAsync needs to check 2 budgets
  • OrderedDataStore and GlobalDataStore writes use different budgets despite the operations named the same way.

After:
GlobalDataStore::GetAsync
	GlobalDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Get) > 0
	
GlobalDataStore::IncrementAsync
	GlobalDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Increment) > 0
	
GlobalDataStore::OnUpdate
	GlobalDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.OnUpdate) > 0
	
GlobalDataStore::RemoveAsync
	GlobalDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Remove) > 0
	
GlobalDataStore::SetAsync
	GlobalDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Set) > 0
	
GlobalDataStore::UpdateAsync
	GlobalDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Update) > 0
OrderedDataStore::GetAsync
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Get) > 0
	
OrderedDataStore::IncrementAsync
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Increment) > 0
	
OrderedDataStore::OnUpdate
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.OnUpdate) > 0
	
OrderedDataStore::RemoveAsync
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Remove) > 0
	
OrderedDataStore::SetAsync
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Set) > 0
	
OrderedDataStore::UpdateAsync
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.Update) > 0
	
OrderedDataStore::GetSortedAsync
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.GetSorted) > 0
MockDataStorePages::AdvanceToNextPageAsync
	OrderedDataStore:GetRequestBudgetForMethod(Enum.DataStoreMethod.AdvanceToNextPage) > 0

This is cleaner, does not leak implementation details, and is easy to remember and guess via Intellisense without needing to keep the Developer Hub / other kinds of documentation open on the side (PS: the information on the Developer Hub about budget consumption is actually wrong at the time of writing, even :grin:).


If Roblox is able to improve budgeting API (i.e. self-descriptive enumerators and API), it would improve my development experience because budgeting checks would be easier to write, easier to remember, easier to understand, be backwards-compatible in case the internals of datastores change or new datastore methods are added, and cause less room for user error.

10 Likes