roblox-data-stores

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Roblox data stores and cloud services

Roblox 数据存储与云服务

Choose the service

选择服务

NeedServiceNotes
Player progress, inventory, settingsDataStore via ProfileStore (session-locked)One key per player (
User_{UserId}
), ≤ 4 MB.
Global leaderboards
OrderedDataStore
Integer values only; periodic updates, cached reads.
Fast, temporary, cross-server state
MemoryStoreService
Sorted maps, queues, hash maps; everything expires (TTL ≤ 45 days).
Matchmaking queues, live events, server listsMemoryStore queue / sorted mapNot durable — never the only copy of valuable data.
Broadcast to all servers
MessagingService
≤ 1 kB per message, best effort, no delivery guarantee.
Values changed by live-ops without publishingExperience configs (
ConfigService
)
See
roblox-analytics-liveops
.
需求服务备注
玩家进度、库存和设置通过 ProfileStore 使用 DataStore(会话锁定)每名玩家一个键 (
User_{UserId}
),≤ 4 MB。
全局排行榜
OrderedDataStore
仅限整数值;定期更新,缓存读取结果。
快速、临时的跨服务器状态
MemoryStoreService
有序映射、队列、哈希映射;所有数据都会过期(TTL ≤ 45 天)。
匹配队列、实时活动、服务器列表MemoryStore 队列/有序映射不持久——绝不要将其作为有价值数据的唯一副本。
向所有服务器广播
MessagingService
每条消息 ≤ 1 kB,尽力而为,不保证送达。
无需发布即可由 live-ops 修改的值体验配置(
ConfigService
)
参见
roblox-analytics-liveops
。

Player data: use session locking

玩家数据:使用会话锁定

Two servers must never own the same player's data at once (teleports, fast rejoins), or items duplicate and progress is lost. ProfileStore (loleris) implements session locking, auto-save, and
BindToClose
flushing, and is the recommended default. Install it from Wally/the Creator Store and put it in
ServerScriptService
.
luau
-- ServerScriptService/Server/Services/DataService.luau (ProfileStore)
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")

local ProfileStore = require(ServerScriptService.ServerPackages.ProfileStore)

local TEMPLATE = {
	version = 1,
	coins = 0,
	inventory = {} :: { [string]: number },
}
type PlayerData = typeof(TEMPLATE)

local PlayerStore = ProfileStore.New("PlayerData", TEMPLATE)
local profiles: { [Player]: typeof(PlayerStore:StartSessionAsync("")) } = {}

local function onPlayerAdded(player: Player)
	local profile = PlayerStore:StartSessionAsync(`User_{player.UserId}`, {
		Cancel = function()
			return player.Parent ~= Players -- stop waiting if the player left
		end,
	})
	if profile == nil then
		player:Kick("Could not load your data. Please rejoin.")
		return
	end
	profile:AddUserId(player.UserId) -- GDPR / right-to-be-forgotten linkage
	profile:Reconcile() -- fill in fields added to TEMPLATE since this profile was created
	profile.OnSessionEnd:Connect(function()
		profiles[player] = nil
		player:Kick("Your data was loaded on another server. Please rejoin.")
	end)
	if player.Parent == Players then
		profiles[player] = profile
	else
		profile:EndSession() -- left while loading
	end
end

for _, player in Players:GetPlayers() do
	task.spawn(onPlayerAdded, player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(function(player)
	local profile = profiles[player]
	if profile then
		profile:EndSession() -- saves and releases the lock
	end
end)

local DataService = {}
function DataService.get(player: Player): PlayerData?
	local profile = profiles[player]
	return profile and profile.Data
end
return DataService
Mutate
profile.Data
directly on the server (it is cached in memory and auto-saved). Don't
SetAsync
per change. Check the ProfileStore docs for the exact current API before relying on less common features (global updates,
MessageAsync
, mock stores for Studio).
同一名玩家的数据绝不能同时由两个服务器持有(传送、快速重连),否则物品会重复且进度会丢失。ProfileStore(作者 loleris)实现了会话锁定、自动保存和
BindToClose
刷盘,是推荐的默认方案。可通过 Wally/Creator Store 安装,并将其放入
ServerScriptService
。
luau
-- ServerScriptService/Server/Services/DataService.luau (ProfileStore)
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")

local ProfileStore = require(ServerScriptService.ServerPackages.ProfileStore)

local TEMPLATE = {
	version = 1,
	coins = 0,
	inventory = {} :: { [string]: number },
}
type PlayerData = typeof(TEMPLATE)

local PlayerStore = ProfileStore.New("PlayerData", TEMPLATE)
local profiles: { [Player]: typeof(PlayerStore:StartSessionAsync("")) } = {}

local function onPlayerAdded(player: Player)
	local profile = PlayerStore:StartSessionAsync(`User_{player.UserId}`, {
		Cancel = function()
			return player.Parent ~= Players -- stop waiting if the player left
		end,
	})
	if profile == nil then
		player:Kick("Could not load your data. Please rejoin.")
		return
	end
	profile:AddUserId(player.UserId) -- GDPR / right-to-be-forgotten linkage
	profile:Reconcile() -- fill in fields added to TEMPLATE since this profile was created
	profile.OnSessionEnd:Connect(function()
		profiles[player] = nil
		player:Kick("Your data was loaded on another server. Please rejoin.")
	end)
	if player.Parent == Players then
		profiles[player] = profile
	else
		profile:EndSession() -- left while loading
	end
end

for _, player in Players:GetPlayers() do
	task.spawn(onPlayerAdded, player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(function(player)
	local profile = profiles[player]
	if profile then
		profile:EndSession() -- saves and releases the lock
	end
end)

local DataService = {}
function DataService.get(player: Player): PlayerData?
	local profile = profiles[player]
	return profile and profile.Data
end
return DataService
在服务器上直接修改
profile.Data
(数据会缓存在内存中并自动保存)。不要针对每次变更调用
SetAsync
。在依赖不太常用的功能(全局更新、
MessageAsync
、用于 Studio 的模拟存储)之前,请查阅 ProfileStore 文档以了解确切的当前 API。

Raw DataStoreService rules (when not using a wrapper)

原始 DataStoreService 规则(不使用包装器时)

  • Buffer in memory: load once on join, mutate the in-memory table, save periodically (e.g. every 2–5 minutes with random jitter), on leave, at checkpoints (purchases), and in
    game:BindToClose
    .
  • UpdateAsync
    over
    SetAsync
    whenever the write depends on the old value or multiple servers may write the key. The transform function must be pure and fast (no yields); return
    nil
    to cancel.
  • Retry transient errors with exponential backoff + jitter, capped attempts; process retries for a key in order (an old retry must not overwrite newer data). Treat a failed write as "outcome unknown".
  • Never save
    Instance
    s,
    Vector3
    ,
    CFrame
    , functions, or mixed/sparse tables: store plain tables, numbers, strings, booleans, and
    buffer
    s. Serialize datatypes (
    { x, y, z }
    ). Validate UTF-8 strings.
  • Keys and data store names ≤ 50 characters; value ≤ 4,194,304 characters after JSON encoding.
  • Stable key patterns (
    User_{UserId}
    ), never display names. Few data stores, few keys per player.
  • Studio needs File → Experience Settings → Security → Enable Studio Access to API Services; use a separate test universe or a different data store name so testing never touches production data.
Complete raw implementation (session lock, autosave, BindToClose, retries): references/raw-datastore.md.
  • 在内存中缓冲:加入时加载一次,修改内存中的表;定期保存(例如每 2–5 分钟一次并加入随机抖动)、离开时保存、在检查点(购买)保存,以及在
    game:BindToClose
    中保存。
  • 当写入依赖于旧值或多个服务器可能写入同一个键时,使用
    UpdateAsync
    而不是
    SetAsync
    。转换函数必须纯粹且快速(不能产生 yield);返回
    nil
    以取消写入。
  • 使用指数退避和抖动重试瞬时错误,并限制尝试次数;按顺序处理某个键的重试(旧重试不得覆盖较新的数据)。将失败的写入视为“结果未知”。
  • 绝不保存
    Instance
    、
    Vector3
    、
    CFrame
    、函数或混合/稀疏表:只应存储普通表、数字、字符串、布尔值和
    buffer
    。将数据类型序列化(
    { x, y, z }
    )。验证 UTF-8 字符串。
  • 键名和数据存储名称均 ≤ 50 个字符;JSON 编码后的值 ≤ 4,194,304 个字符。
  • 使用稳定的键模式(
    User_{UserId}
    ),绝不要使用显示名称。数据存储应尽量少,每名玩家的键也应尽量少。
  • Studio 需要 文件 → 体验设置 → 安全 → 启用对 API 服务的 Studio 访问权限;请使用独立的测试 universe 或不同的数据存储名称,确保测试永远不会触及生产数据。
完整的原始实现(会话锁定、自动保存、BindToClose、重试): references/raw-datastore.md。

Budgets and limits (2026)

预算和限制(2026)

LimitValue
Experience-wide standard reads / writes
300 + 40 × CCU
/
300 + 20 × CCU
per minute (shared with Open Cloud)
Default per-server reads/writes
60 + 40 × players
per minute each (configurable)
Ordered writes per server
30 + 5 × players
per minute
Per-key throughput25 MB/min read, 4 MB/min write
Storage (latest versions, compressed)
500 MB + 1 MB × lifetime users
per experience
Queue per request type30 pending requests, then errors 301–306
  • UpdateAsync
    consumes both read and write budget.
  • Check
    DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.StandardWrite)
    before bulk work; tune per-server limits once at startup with
    SetRateLimitForRequestType
    .
  • Don't pre-compress data; Roblox compresses automatically. Shard only genuinely hot keys.
  • Use versions (
    ListVersionsAsync
    ,
    GetVersionAtTimeAsync
    ) to restore data instead of writing backup keys.
限制数值
整个体验的标准读取/写入
300 + 40 × CCU
/
300 + 20 × CCU
每分钟(与 Open Cloud 共用)
每台服务器的默认读取/写入
60 + 40 × players
每分钟(读取和写入各一项,可配置)
每台服务器的 Ordered 写入
30 + 5 × players
每分钟
单键吞吐量读取 25 MB/分钟,写入 4 MB/分钟
存储空间(最新版本,已压缩)每个体验
500 MB + 1 MB × lifetime users
每类请求的队列30 个待处理请求,随后返回 301–306 错误
  • UpdateAsync
    会同时消耗读取和写入预算。
  • 在批量操作前检查
    DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.StandardWrite)
    ;在启动时使用
    SetRateLimitForRequestType
    一次性调优每服务器限制。
  • 不要预压缩数据;Roblox 会自动压缩。仅对真正的热点键进行分片。
  • 使用版本(
    ListVersionsAsync
    、
    GetVersionAtTimeAsync
    )恢复数据,而不是写入备份键。

Schema versioning and migrations

Schema 版本管理和迁移

Store a
version
field. On load, run ordered migration functions until the data reaches the current version, then reconcile defaults. Never delete fields in the same release that stops writing them; never reuse field names with different meanings.
luau
--!strict
type Data = { [string]: any }

local MIGRATIONS: { (Data) -> () } = {
	[1] = function(data) -- v1 -> v2: "gold" renamed to "coins"
		data.coins = data.gold or 0
		data.gold = nil
	end,
	[2] = function(data) -- v2 -> v3: inventory list -> counts
		local counts: { [string]: number } = {}
		for _, itemId in data.inventory or {} do
			counts[itemId] = (counts[itemId] or 0) + 1
		end
		data.inventory = counts
	end,
}
local CURRENT_VERSION = #MIGRATIONS + 1

local function migrate(data: Data): Data
	local version = data.version or 1
	while version < CURRENT_VERSION do
		MIGRATIONS[version](data)
		version += 1
	end
	data.version = CURRENT_VERSION
	return data
end

print(migrate({ gold = 5, inventory = { "Sword", "Sword" } }))
存储一个
version
字段。加载时,按顺序运行迁移函数,直至数据达到当前版本,然后补齐默认字段。不要在停止写入某个字段的同一次发布中删除该字段;也不要将具有不同含义的字段名重复用于其他用途。
luau
--!strict
type Data = { [string]: any }

local MIGRATIONS: { (Data) -> () } = {
	[1] = function(data) -- v1 -> v2: "gold" renamed to "coins"
		data.coins = data.gold or 0
		data.gold = nil
	end,
	[2] = function(data) -- v2 -> v3: inventory list -> counts
		local counts: { [string]: number } = {}
		for _, itemId in data.inventory or {} do
			counts[itemId] = (counts[itemId] or 0) + 1
		end
		data.inventory = counts
	end,
}
local CURRENT_VERSION = #MIGRATIONS + 1

local function migrate(data: Data): Data
	local version = data.version or 1
	while version < CURRENT_VERSION do
		MIGRATIONS[version](data)
		version += 1
	end
	data.version = CURRENT_VERSION
	return data
end

print(migrate({ gold = 5, inventory = { "Sword", "Sword" } }))

Leaderboards (OrderedDataStore)

排行榜(OrderedDataStore)

  • Values must be integers. Write on a schedule or on meaningful change, not every increment.
  • Read top N with
    GetSortedAsync(false, 100)
    on one server loop (e.g. every 60 s), cache the result, and replicate to clients. Don't let every server/client read constantly.
  • For "this week" boards, include the period in the store name (
    Wins_2026W39
    ) or use a MemoryStore sorted map.
  • 值必须是整数。按计划或在发生有意义的变更时写入,不要每次数值增加都写入。
  • 在单个服务器循环中(例如每 60 秒)通过
    GetSortedAsync(false, 100)
    读取前 N 名,缓存结果并复制到客户端。不要让每个服务器/客户端持续读取。
  • 对于“本周”排行榜,请在存储名称中包含周期(
    Wins_2026W39
    ),或使用 MemoryStore 有序映射。

MemoryStore essentials

MemoryStore 要点

  • Quotas are experience-wide: memory
    64 KB + 1.2 KB × users
    ; requests
    1000 + 120 × CCU
    units/min.
  • MemoryStoreSortedMap
    : ranked data, server browsers, live leaderboards (
    SetAsync
    with expiration,
    GetRangeAsync
    ,
    UpdateAsync
    ).
  • MemoryStoreQueue
    : matchmaking/work queues (
    AddAsync
    ,
    ReadAsync
    +
    RemoveAsync
    with the returned id after successful processing, invisibility timeout for crash safety).
  • MemoryStoreHashMap
    : high-volume key-value without sorting (
    SetAsync
    ,
    GetAsync
    ,
    UpdateAsync
    ,
    ListItemsAsync
    ).
  • Always set expirations; handle throttling (
    pcall
    + retry); keep items small.
  • 配额按整个体验共享:内存为
    64 KB + 1.2 KB × users
    ;请求为
    1000 + 120 × CCU
    单位/分钟。
  • MemoryStoreSortedMap
    :排名数据、服务器浏览器、实时排行榜(带过期时间的
    SetAsync
    、
    GetRangeAsync
    、
    UpdateAsync
    )。
  • MemoryStoreQueue
    :匹配/工作队列(
    AddAsync
    、
    ReadAsync
    ,并在成功处理后使用返回的 id 调用
    RemoveAsync
    ;使用不可见超时保障崩溃安全)。
  • MemoryStoreHashMap
    :无需排序的高吞吐量键值数据(
    SetAsync
    、
    GetAsync
    、
    UpdateAsync
    、
    ListItemsAsync
    )。
  • 始终设置过期时间;处理限流(
    pcall
    + 重试);保持条目较小。

MessagingService essentials

MessagingService 要点

  • Message ≤ 1 kB. Per server:
    600 + 240 × players
    sends/min;
    20 + 8 × players
    subscriptions.
  • Delivery is best effort and not ordered: use it for notifications ("refresh your cache", global announcements), never as the source of truth. Pair with DataStore/MemoryStore for state.
  • Wrap
    SubscribeAsync
    /
    PublishAsync
    in
    pcall
    ; they can fail or yield.
More code (MemoryStore queue matchmaking, cross-server announcement, RTBF): references/memory-and-messaging.md.
  • 消息 ≤ 1 kB。每台服务器每分钟可发送
    600 + 240 × players
    条消息;可使用
    20 + 8 × players
    个订阅。
  • 投递采用尽力而为方式且不保证顺序:适用于通知(“刷新你的缓存”、全局公告),绝不要将其作为唯一真实数据源。应与 DataStore/MemoryStore 配合使用来保存状态。
  • 使用
    pcall
    包装
    SubscribeAsync
    /
    PublishAsync
    ;它们可能失败或产生 yield。
更多代码(MemoryStore 队列匹配、跨服务器公告、RTBF): references/memory-and-messaging.md。

Right to be forgotten

被遗忘权

Use static key patterns so automated RTBF (configured in Creator Dashboard) can delete player data templates like
PlayerData/User_{userId}
. Otherwise handle the right-to-erasure webhook (see
roblox-open-cloud
). Link keys to user IDs:
SetAsync(key, value, { userId })
, return them as the second value from an
UpdateAsync
transform, or call ProfileStore's
AddUserId
.
使用静态键模式,使 Creator Dashboard 中配置的自动化 RTBF 能够删除
PlayerData/User_{userId}
等玩家数据模板。否则,需要处理擦除权 webhook(参见
roblox-open-cloud
)。将键与用户 ID 关联:调用
SetAsync(key, value, { userId })
,将用户 ID 作为
UpdateAsync
转换函数返回的第二个值,或调用 ProfileStore 的
AddUserId
。

Related skills

相关 skill

roblox-security
(duplication and trade exploits),
roblox-monetization
(receipts),
roblox-open-cloud
(external access, bulk operations),
roblox-teleport-matchmaking
.
roblox-security
(复制物品和交易漏洞)、
roblox-monetization
(收据)、
roblox-open-cloud
(外部访问、批量操作)、
roblox-teleport-matchmaking
。