Fix DataStores' present state

Huh? How is using the same Lua code “over and over again” a sign that a feature doesn’t suit the users needs? It’s just a sign users like to use certain abstraction layers. Which are readily available (and could maybe be presented on the wiki too). Devs not using a feature at all (or avoiding it) is a sign a feature doesn’t suit the users needs, in my opinion (streamingenabled (cannot get that to work reliably myself), teleportservice (breaks down too often)…).
I do agree with adding additional things to existing APIs to improve their usability without really changing their abstraction level. Things like CFrame::upVector are very useful additions, for example, yet don’t change what CFrame’s fundamentally are.

Of course, in many cases you’re introducing an additional higher-level interface. Which is what you’re suggesting here too, just some very specific high-level interface integrated into the DataStore service rather than using Lua code. Thing is, the requirements of such an interface change from game to game. The interface for my games are completely different from interfaces of minigame games.

There’s also Pool Tycoon 4 using DataStores, out there for over 2 years. You really need to be aware of when data can be lost. DataStores haven’t ever randomly purged a value or failed to actually store a value once a Set/Update function returned normally, in my experience, so they’re insanely reliable (downtime aside).

3 Likes

I think it’s fine as it is, but god damn they need to increase the limits and remove constant caches on similar requests under 5 min. Like seriously, why? These limitations are stupid given 85% of our revenue goes to maintain server costs and other things of the sort.

‘Errors’ will always be an issue when dealing with an external saving service. This is unavoidable.

The easiest way to deal with these errors is to handle them in a new API layer made specifically for the purpose of handling whatever data it will handle.

  • The suggestion in the original post suggests a “per-player” API. I think if this is done it should be called something like PlayerDataStore in order to clearly and distinctly separate it from general-purpose DataStore methods. This prevents confusion and shows a clear purpose.

Regardless of if ROBLOX makes a player-focused DataStore API, the general-purpose DataStore API needs improvements. Us developers need to know and understand what errors we might receive. Right now, it’s not clear. In order to effectively and easily make the API abstractions we need to properly manage our data, we need that “overglorified pcall”.

We need to know all of the failures we can receive. We need set codes (Enums) for failures, not unclear, undocumented strings.

This provides us with clear, set documentation and expected behavior for all possible results of a DataStore call.

Extra note: If we had Enums for datastore result cases, one could be CachedResponse to make it clear when a GetAsync returns cached results.

Reasoning It is my understanding that errors are meant for *unexpected behavior*:
  • Strings make for bad programmatic error indicators. Strings do not have to be documented. Strings are not always consistent. Dynamic strings are more difficult to check against.
  • Given the previous statement, Lua does not lend itself well to the try-catch method of programmatic error handling, and following that does not lend itself to using errors for programmatic handling of expected failure states. Lua does not have well documented, built-in programmatic error cases and error case checking, and neither does ROBLOX. Other languages have documented Exception classes (and similar) that can be used to programmatically handle (and document) errors.
  • This means that if something is expected behavior with proper use, it should not be an error. If it is an error then it will be more difficult than necessary to programmatically check its state/result. It is easier to use values provided in an API of some sort than to parse error codes, and using errors rather than failure codes does not separate expected failures from improper use. As previously mentioned, DataStores failing during proper use is unavoidable, so DataStore failures are expected behavior.
  • pcall does not provide “error cases”, and the errors are not “well documented”. pcall provides string errors messages, not well-defined error cases. Errors are also not clearly well-documented and clear in programmatic checking for them. Other languages offer this through exception case checking APIs (which also provide a place for documentation) that can catch and check for specific error cases without having to string match undocumented error messages.

If DataStore failures are expected behavior, and Lua does not have easy-to-use, well-documented errors and error handling, then something as critical as DataStores that requires programmatic failure checking should not use errors for describing failures except for when used improperly.


As an example of how using undocumented strings instead of Enum failure codes negatively affects developer understanding, here is what I know about DataStore failures:

  • *Async* methods throw errors when they DataStore method limits are used up. I don’t know what this error is; I cannot check for it.
  • *Async* methods throw errors when they cannot connect to the external DataStore service properly. This means that data is inaccessible and may be for a long period of time. I don’t know what this error is; I cannot check for it.
  • :GetDataStore might error when the external DataStore service is down. I don’t know if it errors. I don’t know what the error is. I cannot properly check for it. (An error may be my own fault due to me using DataStores wrong, or it might be due to connection issues with the external service).
  • Not an error, but I don’t have any idea when I’m close to my own request limits. I cannot prioritize requests or leave room in the limits for essential functions of the game.

If DataStore call failures are to be expected, but developers don’t know how to check for such failures properly, then how are we meant to handle the intended, expected behavior of DataStores? DataStores should only error when the programmer has made a mistake. DataStores should provide well documented, error codes/cases/objects/enums to pass the state of DataStore operations to Lua.



Another feature that would be beneficial would be exposing read-only values to developers’ DataStore limits.
Exposing real-time request limits would:

  • Make the understanding of request limits more clear in developers new to DataStores
  • Give developers an understanding of how many requests they use as well as how many they could use
  • Allow for better prioritization of requests in order to take up the allowed requests per limit

It would also help to have up-to-date, ideally in-engine information on how often request limits are ‘expanded’ and what they are expanded by. If it takes 5 minutes to get more requests then I might want to throttle the amount of requests I’m making.


In conclusion:

  • Handling data with an external service requires handling failures.
    • Handling failures is best done in custom API layers above the general-purpose DataStore API
      • There is no problem with ROBLOX making one of these API layers, but the general-purpose API needs some improvement and custom API layers should clearly not be general-purpose API.
  • Failures should be provided in a well-documented, easy-to-handle-programmatically way.
    • I suggest an Enum.
      • Enums can be made mostly-clear by name.
      • Enum types are easy to programmatically check for.
      • Enums are already documented as existing since they appear even in the auto-generated API docs.
        • Further documentation can be provided on its wiki page.
        • Documentation of an enum’s existence cannot lag behind. Developers will at the least be aware of its existence prior to complete documentation.
      • Enums are perfect when there need to be multiple, pre-set values. Developers need access to a list of all possible response states and be able to easily check for them in order to properly and easily use the DataStore.
    • String error messages do not achieve this.
      • String error messages have no set place for documentation.
        • String error messages do not even have required documentation of their existence (unlike enums).
        • Documentation for string error messages, including their existence, can lag behind their implementation.
      • String error messages do not have set values. Developers may think they have to program for the case of ‘unknown’, undocumented values.
  • Request limits should be exposed
    • Developers will have a better understanding of how request limits work
    • Developers will have a better understanding of how many requests they use
    • Saving/Loading prioritization can be implemented in order to efficiently use remaining requests at any given time
  • Exposing how often request limits are renewed and how much they are renewed by per time period has similar advantages to exposing request limits.
2 Likes

Data Stores are complicated. I feel like the real issue is the awkward “mid level access” we have to data stores. I feel like a better built in error system would really improve the experience along with a more transparent and simple cache layer. Or, we could get the ability to query the data base through a port directly but that isn’t gonna happen.

Also, why do we keep getting told it’s easy to make a general purpose data store system? If it’s so easy, why doesn’t Roblox just provide it? It would be great to have some unified module built for those of us that don’t want to reinvent the wheel.

I’m having an issue right now where somebody’s telling me their credits that they bought didn’t save. I have a store that tells me their credits and another one that tells me how much they’ve bought, and I can’t trust anything but the incomprehensible miles of pages of singular sales in the actual roblox sales history. Now I need to go into the game and make it repeatedly try to do the same thing when it doesn’t work because Roblox doesn’t do that automatically, and I have to figure out how to deal with this guy because I have no clue what’s really going on. I shouldn’t have to do this.

Protip: save purchase details to a different datastore (date + user + asset) and only return PurchaseGranted after that has saved. That’s more useful than accumulated stats on how much they’ve bought and how much they have left.

Warning for that approach: you cannot use a single UpdateAsync to update this transaction log + your actual datastore kvp with the money/inventory stats etc., so this means the log does not reliably indicate whether or not a purchase was properly granted once. Unless you use some write-ahead approach, which is significantly more complex, keeping the log in the same key as the actual data is significantly simpler if you want a failure-proof system. (UpdateAsync the key → check if already granted, if so, do not re-grant, if not, grant + log in the same update (which is atomic, so failure proof)).

1 Like