scenario-roblox-data について
Roblox 開発者向けに、データ永続化、購入、取引、テレポートを扱うデータレイヤーのスキルです。再試行、クラッシュ、スキーマ変更、複数サーバーの競合に対応するパターンとテストキットを提供します。
主な機能
- 01Profile のセッションロック
- 02バージョン付きスキーマ移行
- 03冪等な書き込み再試行
- 04購入レシート処理
- 05クラッシュ後の取引復旧
- 06複数サーバーのシミュレーションテスト
活用シーン
- 01プレイヤー Profile の安全な永続化
- 02既存プレイヤーデータの移行
- 03購入アイテムの二重付与防止
- 04サーバークラッシュ後の取引復旧
MCP 対応
Roblox tooling ツールキットを介して Studio MCP でデータテストを実行し、Edit や Playtest セッションで動作を確認する方法を説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-roblox-dataインストール後、Claude Code で/scenario-roblox-dataを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/rbx_data.pyscripts/agentkit/data/DataAudit.luauDataCheck.luauKeyQueue.luaulune_data_audit.luauMigrate.luauMockDataStore.luauPlayerData.luauReceipts.luauSafeTeleport.luauServerSim.luauTrade.luau詳細
Robloxのデータとバックエンド
scenario-roblox-expertツールキットを介して操作する、macOS上のRoblox Studio(2026年10月ビルド)。
ここでいうエキスパートレベルとは、サーバーがクラッシュしたとき、書き込みが反映された後に失敗したとき、または2台のサーバーが同じプレイヤーを処理しようとしたときにも、データ層が正しく動作することを意味します。公開前のテスト場所からはDataStoreにアクセスできないため、公開テストを行う前に、シミュレートしたマルチサーバーのバックエンドでこれらのケースを検証してください。
ここに記載された関連スキルが利用可能なスキルにない場合は、ユーザーにインストールを依頼してください
(npx skills add scenario-labs/skills --skill <name>)。無人で進める場合は、ツールのスキーマを確認して作業を進め、スキルが不足していることを明記してください。
必須の前提知識: scenario-roblox-expert(チャンネル、レビューの反復、ツールキット、現在のRoblox特有の落とし穴)。
基本方針(エキスパートとして押さえるべき点)
- セッションロックは重複を防ぎますが、UpdateAsyncだけでは防げません。 プレイヤーごとに1つのProfileStoreプロファイルでロックを保持します(loleris、ProfileStoreのドキュメント)。
UpdateAsyncがSetAsyncより安全なのは、変換処理が古い値を読み取る場合に限られます(Suphi Kaner、00:04:43)。 - 書き込みに失敗しても、反映されている可能性があります。 再実行する前に、
UseCache = falseで読み込んでください(Robloxのベストプラクティス)。値の中に操作IDを入れ、繰り返し実行しても何も変更されないようにします[追加]。モック実行では、書き込みの30%が反映後に失敗する状況で、安易な再試行により、30件の操作に対して39件が保存されました。 - 同じキーへの再試行は順序を維持します。 古い値への再試行が遅れて実行されると、新しいデータが上書きされます(Robloxのベストプラクティス)。
KeyQueueでキーごとに順番に処理します。 - 購入は一度だけ付与し、保存を確認します。 各
PurchaseIdをプロファイルに記録します。LastSavedDataにそのIDが含まれている場合に限り、PurchaseGrantedを返します(Rileybytes、00:29:08、Robloxの購入に関するドキュメント)。それ以外のすべてのケースではNotProcessedYetを返します。 Reconcileの前にマイグレーションします。ReconcileはテンプレートのSchemaVersionを古いデータにコピーするため、マイグレーションがスキップされます[追加、実際のProfileStoreで検証済み]。- 古いサーバーは新しいデータを保存しません。 更新中は古いサーバーと新しいサーバーが並行して動作します。より高い
SchemaVersionを拒否し、ロックを変更せずに解除して、プレイヤーをキックします[追加]。複数の場所から成るゲームでは、すべての場所をまとめて公開してください(CJ_Oyer、00:27:33)。 - 2つのプロファイルをアトミックに保存することはできません。 yieldせずに両方を編集しても、メモリ内の保護にしかなりません(loleris)。両方の取引をエスクローに入れ、1つの取引ログレコードで結果を決定します[追加]。エスクローなしでは、2回の保存の間にクラッシュするとShieldが複製され、Swordが失われました。
- ストアを差し替えてテストします。
DataStoreServiceをMockDataStoreに差し替えられるようにデータコードを書き、変更していないProfileStoreを使って、複数のシミュレートされたサーバーを実行します[追加]。
まず確認すること
- 公開済みで、Studio APIアクセスが有効ですか? 未公開の場合、DataStoreの呼び出しはすべてエラーになるため、モックを使ってテストします。公開済みでアクセスが有効な場合、Studioから本番キーに書き込まれるため、別のテスト用エクスペリエンス(Robloxのドキュメント)または
ProfileStore.Mockを使用してください。 - 何をどこに保存しますか? プレイヤーの状態は1つのプロファイルに保存します。ランキングには
OrderedDataStoreを使用します。マッチメイキングと短時間だけ共有する状態にはMemoryStoreを使用します。アナウンスにはMessagingServiceを使用します。 - トレード、Developer Products、複数の場所はありますか? それぞれに以下の手順が追加されます。
- ピーク時のCCU、サーバーあたりのプレイヤー数、プロファイルのサイズはいくつですか? これらを
rbx_data.budgetに渡してください。 - すでに本番データがありますか? その場合、キーとストア名は変更できません。変更はすべてマイグレーション手順になります。
デフォルト:固定したコミットのProfileStore、ストア名PlayerData、キーPlayer_<UserId>、SchemaVersionフィールド、100件のPurchaseIdsログ。
手順
モジュールは scripts/agentkit/data/ にあり、requires はありません。rbx_data.run(studio, code, dm, data=[...]) でインライン化するか、rbx_data.build_place(dir) で場所に組み込んでください。コードと結果の詳細については、procedures.md を参照してください。
- 環境を調査する。 目標:どのサービスがここで応答するかを把握します。方法:
rbx_data.probe_services(studio, "Edit")を実行し、プレイテストでは"Server"を実行します。ゲート条件:エラーテキストが記録され、ストアの選択(実ストアまたはモック)がその内容に基づいていること。 - スキーマを設計する。 目標:そのまま保存でき、安全に拡張できるプロフィールを作成します。方法:
DataCheck.report(template)と最大サイズのサンプル、DataCheck.key(key, {userId})を使用します。ゲート条件:エラー指摘がゼロ、サイズが4,194,304文字の25%未満であり、JSON のラウンドトリップ結果が入力と一致すること。 - 予算を計画する。 目標:ピーク時にスロットリングが発生しないようにします。方法:
rbx_data.budget(players, ccu, kb, saves_per_min)を使用します。ゲート条件:ピーク CCU でokが true であり、キーあたりの毎分書き込み数がオートセーブと購入の両方をカバーしていること。 - セッションを接続する。 目標:各プロフィールの所有者を1人に限定します。方法:
PlayerDataでCancelを指定してStartSessionAsyncを実行し、nil の場合はキックし、その後にAddUserId、移行、Reconcile、OnSessionEndでのキック、退出時のEndSessionを行います。ゲート条件:sessionsスイートが合格し、プレイテストでDataStoreStateが "NoAccess" の状態でプロフィールが読み込まれること。
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ProfileStore = require(ServerStorage.Packages.ProfileStore)
local PlayerData = require(ServerStorage.DataKit.PlayerData)
local Migrate = require(ServerStorage.DataKit.Migrate)
local data = PlayerData.new({
ProfileStore = ProfileStore,
Players = Players,
storeName = "PlayerData",
template = { SchemaVersion = 2, Coins = 0, Inventory = {}, PurchaseIds = {} },
migrate = Migrate.new({
latest = 2,
steps = {
-- v1 kept owned items as an array of names; v2 counts them by id.
[2] = function(d: any)
d.Inventory = d.Inventory or {}
for _, item in ipairs(d.Items or {}) do
d.Inventory[item] = (d.Inventory[item] or 0) + 1
end
d.Items = nil
end,
},
}),
})
data:Start()
- スキーマにバージョンを付ける。 目標:古いデータを一度だけアップグレードします。方法:
Migrate.new{latest, steps}を使用し、古いバージョンごとにフィクスチャを用意します。ゲート条件:Reconcile の落とし穴と "newer" の拒否を含め、schemaスイートが合格すること。 - 購入を付与する。 目標:各
PurchaseIdに対して付与が正確に1回だけ行われるようにします。方法:Receipts.new{getProfile, isPresent, handlers}を使用し、1つのスクリプトでProcessReceiptを割り当てます。ゲート条件:ゴースト書き込み、クラッシュ、サーバー変更を含め、receiptsスイートが合格すること。 - ローストア。 目標:リトライを経ても維持されるリーダーボード、カウンター、ログを作成します。方法:
値に操作 ID を含め、
verify関数を指定してKeyQueue.new(store)を使用します。ゲート条件:keyqueueスイートが合格すること。 - トレード。 目標:クラッシュによってアイテムが作成されたり失われたりしないようにします。方法:両方のプレイヤーに対して
Trade.paidTradingAllowedを使用します(PolicyServiceIsPaidItemTradingAllowed。失敗時は許可しない)、Trade.new{log = KeyQueue.new(tradeLogStore)}、onLoaded内でtrade:Recover(profile)を実行します。ゲート条件:すべてのクラッシュ段階でtradeスイートが合格すること。 - テレポート。 目標:パーティー全員が一緒に到着するようにします。方法:グループには
TeleportPartyを使用して、SafeTeleport.new{TeleportService}を作成します。ゲート条件:フェイクサービスに対してteleportスイートが合格すること。実際のテレポートには公開済みのエクスペリエンスが必要です。 - 監査する。 目標:既知のミスをコードに残さないようにします。方法:Edit で
DataAudit.place(game)を実行するか、 オフラインでrbx_data.py audit place.rbxlを実行します。ゲート条件:テストフィクスチャ以外のエラー指摘がゼロであること。 - 運用する。
PlayerData:Restore(ProfileStoreVersionQuery)によるロールバック、OnLastSaveでの最終編集、rbx_data.rtbf_configによる RTBF テンプレート:手順13〜15。 - 検証して報告する。 オフラインスイートを実行し、次に Studio Edit で
rbx_runを通じて同じスイートを実行し、その後プレイテストのチェックを実行します。公開済みの場所が必要な項目を一覧にしてください。
数値
| 値 | 対象 |
|---|---|
| サーバーの読み取りまたは書き込み:1 分あたり 60 + プレイヤー数 x 40 | サーバーごと。デフォルト値。調整可能 |
| 経験値の読み取り:1 分あたり 300 + CCU x 40、書き込み:1 分あたり 300 + CCU x 20 | 経験全体。Open Cloud と共有 |
UpdateAsync により、ProfileStore の保存ごとに読み取り 1 回と書き込み 1 回を消費 | 両方の予算 |
| キーごと:書き込みは 1 分あたり 4 MB、読み取りは 1 分あたり 25 MB | リクエストごとに KB 単位で切り上げ |
| 値は 4,194,304 文字、キー、名前、スコープは 50 | キーごと |
| リクエストの種類ごとにキューは 30 件。その後、301 から 306 のエラーが発生 | サーバーごと |
| GetAsync キャッシュは 4 秒 | データストアのインスタンスごと |
| キーごとに UTC 時間あたり 1 バージョンを保持し、上書き後 30 日間保持 | キーごと |
| ストレージは 500 MB + 生涯ユーザー数 x 1 MB | 圧縮された最新バージョン |
| ProfileStore の自動保存は 300 秒、ロック奪取は 40 秒、デッドと見なすまで 630 秒 | 固定されたソース。デッド判定ではサーバー間で os.time() を比較 |
| ProfileStore の初回再試行は 5 秒、その後は 10 秒ごと | 別のサーバーがロックを保持している間 |
| プロファイルの読み込みが 5 秒を超える | バグとして扱う(loleris) |
| BindToClose の予算は 30 秒 | サーバーのシャットダウンごと |
| MessagingService のメッセージは 1 kB | 公開ごと |
品質ゲート
run_offline.sh:すべての Lune スイートが合格し、rbx_check で kit、place、snippets のエラーがゼロであること。run_live.py:Studio Edit で同じスイートが合格し、プレイテストのチェックも合格すること。DataAudit:fixture 以外のエラー検出結果がゼロであること。- コンソール:未公開のプレイテストで「[ProfileStore]: Roblox API services unavailable」の行が 1 行表示され、保存エラーがないこと。
- 公開済みテスト用エクスペリエンス(後日):2 つの実サーバー間で、5 秒未満でプレイヤーを引き継ぐこと。
よくある間違い
| 間違い | 症状 | 修正 |
|---|---|---|
PlayerRemoving でロックせずに SetAsync を使う | ロールバック、サーバー移動後のアイテム重複 | PlayerData 経由で ProfileStore を使う |
player.Name をキーにする | 名前変更後にデータが失われる | Player_<UserId> |
移行前に Reconcile する | 古いフィールドが取り残され、新しいフィールドが空になる | 最初に移行する |
| 同じ書き込みを呼び出して再試行する | カウンターが高くなりすぎる | 値に操作 ID を含め、KeyQueue を使う |
保存前に PurchaseGranted にする | 支払い済みなのにクラッシュ後に付与されない | Receipts |
| yield するハンドラー | 付与が二重に適用される | ハンドラーはコピー上で実行し、yield してはいけません |
| 取引として 2 つのプロフィールを保存する | アイテムが重複または消失する | エスクロー付きの Trade を使う |
pcall の外で GetDataStore を呼び出す | Studio の起動時にスクリプトエラーが発生する | pcall 内で確認し、その後モックにフォールバックする |
| 公開前のプレイテストで MessagingService の完了を待つ | スレッドが停止する | task.spawn 内で呼び出す |
ShouldReserveServer を使ってテレポートを再試行する | パーティーが複数のサーバーに分かれる | 一度だけ予約し、アクセスコードを再利用する |
| Edit で ModuleScript の Source を編集し、再度 require する | 古いコードが実行され続ける | まずモジュールを Clone に置き換える |
引き継ぎ
- scenario-roblox-architecture から:データサービスの配置場所と、システムによる読み取り方法。
- scenario-roblox-monetization から:プロダクト ID と数量。引き継ぎ先:
Receiptsハンドラーテーブル。 - scenario-roblox-networking から:検証済みの取引リクエストとショップリクエスト。引き継ぎ先:
Tradeとインベントリ 呼び出し。 - scenario-roblox-ui と scenario-roblox-gameplay へ:生のプロフィールではなく、Attributes またはシグナルとして公開される値。
- scenario-roblox-performance へ:シャットダウンのタイミングと更新の公開。
- scenario-roblox-tooling へ:CI 用の Lune テストスイート。
- scenario-roblox-luau へ:テンプレート用の型。
Roblox の最新情報
- DataStore の制限は 2026-07-29 にエクスペリエンス全体に適用されるようになりました。以前の回答にある「60 + プレイヤー数 x 10」と 6 秒のクールダウンは、どちらも廃止されています。
- ストレージ制限は 2026-04-09 から適用されています。
HttpService:JSONEncodeは 2026-03-10 以降、NaN と無限大をタグ付きオブジェクトとしてエンコードし、疎なエントリや型が混在するエントリを 黙って除外します(ライブテスト)。- ProfileService はサポートされていません。ProfileStore を使ってください。DataStore2 はレガシーです。
ReserveServerAsyncはReserveServerに代わるものです。- Studio の API アクセス:File > Experience Settings > Security > Enable Studio Access to API Services。 公開済みのゲームのみ対象です。
- Studio の予算はサーバーの計算式に従いません。1 人でのプレイテストでは読み取りが 585 件でした。
参考資料
- references/expert-notes.md:専門家による原則とタイムスタンプ。
- references/procedures.md:手順全体、テストパス、結果。
- references/critique.md:データレイヤーを評価するための基準。
- references/gui-paths.md:同じ作業を行うための Studio と Creator Hub のパス。
- references/sources.md:リンク付きのすべてのソース。
- scripts/rbx_data.py:予算、ProfileStore の取得、プレースのビルド、プローブ、監査。
- scripts/agentkit/data/:MockDataStore、ServerSim、DataCheck、Migrate、KeyQueue、Receipts、Trade、PlayerData、SafeTeleport、DataAudit。