Managing Data Stores

Manage your data using versioning, listing, and caching.

Versioning

Versioning happens when you set, update, and increment data. The functions SetAsync(), UpdateAsync(), and IncrementAsync() create versioned backups of your data using the first write to each key in each UTC hour. Successive writes to a key in the same UTC hour permanently overwrite the previous data.

Versioned backups expire 30 days after a new write overwrites them. The latest version never expires.

The following functions perform versioning operations:

FunctionDescription

ListVersionsAsync()

Lists all versions for a key by returning a DataStoreVersionPages instance that you can use to enumerate all version numbers. You can filter versions using a time range.

GetVersionAsync()

Retrieves a specific version of a key using the key's version number.

RemoveVersionAsync()

Deletes a specific version of a key.

This function also creates a tombstone version while retaining the previous version. For example, if you call RemoveAsync("User_1234") and then try to call GetAsync("User_1234"), you get nil back. However, you can still use ListVersionsAsync() and GetVersionAsync() to retrieve older versions of the data.

You can use versioning to handle user requests. If a user reports that a problem occurred at 2020-10-09T01:42, you can revert data to a previous version using the following example:


local DataStoreService = game:GetService("DataStoreService")
local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
local DATA_STORE_KEY = "User_1234"
local maxDate = DateTime.fromUniversalTime(2020, 10, 09, 01, 42)
-- Gets the version closest to the given time
local listSuccess, pages = pcall(function()
return experienceStore:ListVersionsAsync(DATA_STORE_KEY, Enum.SortDirection.Descending, nil, maxDate.UnixTimestampMillis)
end)
if listSuccess then
local items = pages:GetCurrentPage()
if #items > 0 then
-- Reads the closest version
local closestEntry = items[1]
local success, value, info = pcall(function()
return experienceStore:GetVersionAsync(DATA_STORE_KEY, closestEntry.Version)
end)
-- Restores current value by overwriting it with the closest version
if success then
local setOptions = Instance.new("DataStoreSetOptions")
setOptions:SetMetadata(info:GetMetadata())
experienceStore:SetAsync(DATA_STORE_KEY, value, nil, setOptions)
end
else
-- No entries found
end
end

Snapshots

The Snapshot Data Stores Open Cloud API lets you take a snapshot of all data stores in an experience once a day. Before you publish any experience update that changes your data storage logic, make sure to take a snapshot. Taking a snapshot guarantees that you have the most recent data available from the previous version of the experience.

For example, without a snapshot, if you publish an update at 3:30 UTC that causes data corruption, the corrupted data overwrites any data written between 3:00-3:30 UTC. If you take a snapshot at 3:29 UTC, though, the corrupted data doesn't overwrite anything written before 3:29 UTC, and the latest data for all keys written between 3:00-3:29 UTC is preserved.

Listing and Prefixes

Data stores let you list by prefix. For example, listing by the first n characters of a name, like "d" , "do", or "dog" for any key or data store with a prefix of "dog".

You can specify a prefix when listing all data stores or keys, and get back only objects that match that prefix. Both ListDataStoresAsync() and ListKeysAsync() functions return a DataStoreListingPages object that you can use to enumerate the list.

FunctionDescription
ListDataStoresAsync()Lists all data stores.
ListKeysAsync()Lists all keys in a data store.

Scopes

Every key in a data store has a default global scope. You can organize keys further by setting a unique string as a scope for the second parameter of GetDataStore(). This automatically attaches the scope to the beginning of all keys in all operations done on the data store.

KeyScope
houses/User_1234houses
pets/User_1234pets
inventory/User_1234inventory

The combination of data store name, scope, and key uniquely identifies a key. All three values are required to identify a key with a scope. For example, you can read a global key named User_1234 as:


local DataStoreService = game:GetService("DataStoreService")
local inventoryStore = DataStoreService:GetDataStore("PlayerInventory")
local success, currentGold = pcall(function()
return inventoryStore:GetAsync("User_1234")
end)

If the key User_1234 has a scope of gold, though, you can only read it as:


local DataStoreService = game:GetService("DataStoreService")
local inventoryStore = DataStoreService:GetDataStore("PlayerInventory", "gold")
local success, currentGold = pcall(function()
return inventoryStore:GetAsync("User_1234")
end)

AllScopes Property

DataStoreOptions contains an AllScopes property that lets you return keys from all scopes in a list. You can then use the KeyName property of a list item for common data store operations like reading data with GetAsync() and removing data with RemoveAsync().

When you use the AllScopes property, the second parameter of GetDataStore() must be an empty string ("").


local DataStoreService = game:GetService("DataStoreService")
local options = Instance.new("DataStoreOptions")
options.AllScopes = true
local ds = DataStoreService:GetDataStore("DS1", "", options)

If you enable the AllScopes property and create a new key in the data store, you must always specify a scope for that key in the format of scope/keyname. If you don't, the APIs throw an error. For example, gold/player_34545 is acceptable with gold as the scope, but player_34545 leads to an error.

global/K1house/K1
global/L2house/L2
global/M3house/M3

Caching

Use caching to temporarily store data from data stores to improve performance and reduce the number of requests made to the server. For example, an experience can cache a copy of its data so that it can access that data quickly without having to make another call to the data store.

Caching applies to modifications you make to data store keys using:

GetVersionAsync(), ListVersionsAsync(), ListKeysAsync(), and ListDataStoresAsync() don't implement caching and always fetch the latest data from the service backend.

By default, the engine uses GetAsync() to store values you retrieve from the backend in a local cache for four seconds. Also by default, GetAsync() requests for cached keys return the cached value instead of continuing to the backend. Your GetAsync() requests that return a cached value don't count towards your server limits and throughput limits.

All GetAsync() calls that retrieve a value not being cached from the backend update the cache immediately and restart the four second timer.

Disabling Caching

To disable caching and opt out of using the cache to retrieve the most up-to-date value from the servers, add the DataStoreGetOptions parameter to your GetAsync() call and set the UseCache property to false to make your request ignore any keys in the cache.

Disabling caching is useful if you have multiple servers writing to a key with high frequency and need to get the latest value from servers. However, it can cause you to consume more of your data stores limits and quotas, since GetAsync() requests bypassing caching always count towards your throughput and server limits.

Serialization

The DataStoreService stores data in JSON format. When you save Lua data in Studio, Roblox uses a process called serialization to convert that data into JSON to save it in data stores. Roblox then converts your data back to Lua and returns it to you in another process called deserialization.

Serialization and deserialization support the following Lua data types:

  • Numbers
    • You should not store the special numeric values inf, -inf, and nan, because these values don't conform to JSON standards. You can't access keys that contain these values with Open Cloud.
  • Tables
    • Tables must only contain other supported data types
    • Numeric keys are translated into strings if the length of the table is 0

If you try to store a data type that serialization doesn't support, you either:

  • Fail in storing that data type and get an error message.
  • Succeed in storing that data type as nil.

To debug why your data type is being stored as nil, you can use the JSONEncode function. When you pass your Lua data type into this function, you receive it back in the format Roblox would have stored it with data stores, which lets you preview and investigate the returned data.