mcpField
R
roblox-data-stores

roblox-data-stores

github.com/el4cteoデータ・データベース56528 回インストール2 週間前に更新
リポジトリ

roblox-data-stores について

Roblox 開発者向けに、プレイヤーデータの永続化、一時的なサーバー間状態、ランキング、メッセージングを扱うスキルです。サービスの選択からセッションロック、再試行、移行、各種制限まで説明します。

主な機能

  1. 01Roblox ストレージサービスの選択
  2. 02ProfileStore によるセッションロック
  3. 03DataStore の書き込み・再試行・移行
  4. 04OrderedDataStore のランキング設計
  5. 05MemoryStore のキューと一時状態
  6. 06MessagingService の利用上の注意

活用シーン

  1. 01プレイヤーの進行状況や所持品の保存
  2. 02全体・期間別ランキングの作成
  3. 03サーバー間のマッチメイキングキュー管理
  4. 04サーバー間アナウンスの送信

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-data-stores

インストール後、Claude Code で/roblox-data-storesを入力して読み込まれたか確認します。

同梱ファイルreferences/memory-and-messaging.mdreferences/raw-datastore.md

詳細

Roblox データストアとクラウドサービス

サービスの選択

用途サービス備考
プレイヤーの進行状況、インベントリ、設定ProfileStore 経由の DataStore(セッションロック)プレイヤーごとに 1 キー(User_{UserId})、4 MB 以下。
グローバルランキングOrderedDataStore整数値のみ。定期的な更新、読み取りはキャッシュされます。
高速で一時的な、サーバー間の状態MemoryStoreServiceソート済みマップ、キュー、ハッシュマップ。すべて有効期限があります(TTL ≤ 45 日)。
マッチメイキングキュー、ライブイベント、サーバーリストMemoryStore のキュー / ソート済みマップ永続的ではありません。貴重なデータの唯一のコピーにしないでください。
すべてのサーバーへのブロードキャストMessagingServiceメッセージあたり 1 kB 以下、ベストエフォート、配信保証なし。
公開せずにライブオペレーションで変更する値Experience configs(ConfigService)roblox-analytics-liveops を参照してください。

プレイヤーデータ: セッションロックを使用する

2 つのサーバーが同時に同じプレイヤーのデータを所有してはいけません(テレポート、素早い再参加)。そうしないとアイテムが複製され、進行状況が失われます。ProfileStore(loleris)はセッションロック、自動保存、BindToClose 時のフラッシュを実装しており、推奨されるデフォルトです。Wally / Creator Store からインストールし、ServerScriptService に配置してください。

-- 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 を確認してください。

生の DataStoreService のルール(ラッパーを使用しない場合)

  • メモリにバッファリングする: 参加時に一度読み込み、インメモリのテーブルを変更し、定期的(例: ランダムなジッターを加えて 2〜5 分ごと)、退出時、チェックポイント時(購入)、および game:BindToClose で保存します。
  • 書き込みが古い値に依存する場合、または複数のサーバーがそのキーに書き込む可能性がある場合は、SetAsync よりも UpdateAsync を使用します。変換関数は純粋で高速(yield なし)でなければなりません。キャンセルするには nil を返します。
  • 一時的なエラーをリトライする: 指数バックオフ + ジッター、試行回数の上限を設けます。あるキーのリトライは順番に処理します(古いリトライが新しいデータを上書きしてはいけません)。失敗した書き込みは「結果不明」として扱います。
  • Instance、Vector3、CFrame、関数、混在した/疎なテーブルを保存しないでください。保存するのはプレーンなテーブル、数値、文字列、真偽値、buffer です。データ型をシリアライズします({ x, y, z })。UTF-8 文字列を検証します。
  • キーとデータストア名は 50 文字以下、値は JSON エンコード後に 4,194,304 文字以下です。
  • 安定したキーパターン(User_{UserId})を使用し、表示名は決して使わないでください。データストアは少なく、プレイヤーごとのキーも少なくします。
  • Studio では File → Experience Settings → Security → Enable Studio Access to API Services が必要です。テスト用に別のユニバースまたは別のデータストア名を使用し、テストが本番データに触れないようにしてください。

完全な生の実装(セッションロック、自動保存、BindToClose、リトライ): references/raw-datastore.md。

予算と制限(2026)

制限値
Experience 全体の標準読み取り / 書き込み1 分あたり 300 + 40 × CCU / 300 + 20 × CCU(Open Cloud と共有)
サーバーごとのデフォルト読み取り/書き込みそれぞれ 1 分あたり 60 + 40 × players(設定可能)
サーバーごとの Ordered 書き込み1 分あたり 30 + 5 × players
キーごとのスループット読み取り 25 MB/分、書き込み 4 MB/分
ストレージ(最新バージョン、圧縮済み)Experience ごとに 500 MB + 1 MB × 累計ユーザー数
リクエストタイプごとのキュー保留中のリクエストが 30 件、その後エラー 301〜306
  • UpdateAsync は読み取りと書き込みの両方の予算を消費します。
  • 一括処理の前に DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.StandardWrite) を確認してください。起動時に一度 SetRateLimitForRequestType でサーバーごとの制限を調整してください。
  • データを事前に圧縮しないでください。Roblox が自動的に圧縮します。シャーディングは本当にホットなキーにのみ使用してください。
  • バックアップキーに書き込む代わりに、バージョン(ListVersionsAsync、GetVersionAtTimeAsync)を使用してデータを復元してください。

スキーマのバージョン管理とマイグレーション

version フィールドを保存します。読み込み時に、データが現在のバージョンに達するまで、順番にマイグレーション関数を実行し、その後デフォルト値を調整します。書き込みを停止するのと同じリリースでフィールドを削除してはいけません。また、異なる意味でフィールド名を再利用してはいけません。

--!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" } }))

リーダーボード(OrderedDataStore)

  • 値は整数である必要があります。書き込みは一定のスケジュールに従うか、意味のある変更があったときに行い、増分のたびには行わないでください。
  • 1つのサーバーループ(例:60秒ごと)でGetSortedAsync(false, 100)を使って上位N件を読み込み、結果をキャッシュしてクライアントにレプリケートします。すべてのサーバーやクライアントが常に読み込み続ける状態にしないでください。
  • 「今週」のボードでは、ストア名に期間を含める(Wins_2026W39)か、MemoryStoreのソート済みマップを使用します。
  • Robloxネイティブのリーダーボード(完全リリース済み):Creator Hub(ゲーム → リーダーボード)でOrderedDataStoreを登録し、「Active leaderboard」を切り替えて有効にします。Robloxはプレイヤーリストに、永続的な「Global」タブと「Friends」タブを表示します。ストアが引き続き信頼できる唯一の情報源となります(ゲームごとに有効なボードは1つ)。タブが正しく表示されるよう、参加時に各プレイヤーのスコアを初期化します。

MemoryStoreの基本

  • クォータはエクスペリエンス全体で適用されます:メモリは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の基本

  • メッセージは1 kB以下です。サーバーごとの上限は、毎分600 + 240 × players件の送信、20 + 8 × players件のサブスクリプションです。
  • 配信はベストエフォートであり、順序も保証されません。「キャッシュを更新してください」などの通知やグローバル告知に使用し、信頼できる唯一の情報源としては決して使わないでください。状態の管理にはDataStore/MemoryStoreと組み合わせます。
  • SubscribeAsync/PublishAsyncはpcallで囲みます。失敗したり、処理が中断されたりすることがあります。

その他のコード(MemoryStoreのキューマッチメイキング、サーバー間の告知、RTBF):references/memory-and-messaging.md。

忘れられる権利

静的なキーのパターンを使用すると、Creator Dashboardで設定された自動RTBFで、PlayerData/User_{userId}のようなテンプレートに従うプレイヤーデータを削除できます。そうでない場合は、消去権のWebhook(roblox-open-cloudを参照)を処理します。キーをユーザーIDに関連付けるには、SetAsync(key, value, { userId })を使うか、UpdateAsyncの変換関数から2つ目の値として返すか、ProfileStoreのAddUserIdを呼び出します。

関連スキル

roblox-security(複製やトレードの悪用)、roblox-monetization(レシート)、roblox-open-cloud(外部アクセス、一括操作)、roblox-teleport-matchmaking。

コメント