Files
Genarrative/docs/technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md
kdletters 9aae7afb2e
Some checks failed
CI / verify (push) Has been cancelled
fix: import migration via web api body
2026-04-27 16:46:01 +08:00

11 KiB
Raw Blame History

SpacetimeDB JSON 字符串迁移 procedure 设计

背景

spacetime sql 只能稳定读取 public 表或数据库 owner 可见表。当前 ai_result_reference 等运行真相表保持 private直接 SQL 导出会遇到 no such table 或 private table 提示,不能作为跨服务器迁移的稳定方案。

SpacetimeDB reducer 必须保持确定性不能访问文件系统和网络。procedure 可以返回数据,也可以在事务中读取 private 表,因此迁移改为:

  1. spacetime-module 内的导出 procedure 读取迁移白名单表,并直接返回迁移 JSON 字符串。
  2. Node 运维脚本默认通过 spacetime call 调用导出 procedure把返回的 JSON 字符串写入本地文件。
  3. Node 运维脚本读取本地 JSON 文件内容。导入时默认先通过 POST /v1/identity 创建临时 Web API identity/token再用当前 CLI 登录态把该 identity 授权为迁移操作员,最后通过 HTTP request body 把 JSON 字符串传给导入 procedure。
  4. 导入 procedure 校验 JSON 与表白名单后,在事务中写入目标数据库。

procedure 不再访问 HTTP 文件桥,也不接收部署机本地文件路径。这样可以避开 SpacetimeDB 对 private/special-purpose 地址的 HTTP 访问限制,并避免把 private 表内容通过临时 HTTP 服务转发。

spacetime login show --token 输出的是 CLI 登录 token不是 HTTP /v1/database/.../call 所需的数据库连接 token。导入脚本如果没有显式传 --token,会自动调用 POST /v1/identity 获取 Web API token迁移时不要把 CLI token 传给 --token

接口

迁移操作员授权

迁移 procedure 会读取并写入 private 表,不能对任意登录身份开放。模块内新增私有表 database_migration_operator 作为迁移操作员白名单:

  • operator_identity: 被授权调用迁移 procedure 的 SpacetimeDB identity。
  • created_at: 授权写入时间。
  • created_by: 发起授权的 identity。
  • note: 运维备注,只用于区分来源、环境或临时用途。

database_migration_operator 只控制迁移 procedure 调用权限,不会被导出或导入,避免把源库的运维权限复制到目标库。

首次授权时,操作员表为空,必须通过编译进模块的 GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET 引导密钥授权第一位操作员。发布脚本会在构建或发布 SpacetimeDB 模块时自动生成一份强随机引导密钥、注入 wasm 编译环境,并在控制台显示;运维人员必须记录对应数据库本次发布输出的密钥。表内已经存在操作员后,后续授权与撤销只能由已有操作员发起;此时不再接受引导密钥越权扩权。

新增 procedure

  • authorize_database_migration_operator: 授权或更新迁移操作员备注。
  • revoke_database_migration_operator: 撤销迁移操作员。

运维流程:

npm run spacetime:publish:maincloud -- --database <database>
# 控制台会输出:
# [spacetime:maincloud] 迁移引导密钥: <本次发布随机密钥>

发布完成后,在同一台机器上用当前 spacetime login 身份授权操作员:

node scripts/spacetime-authorize-migration-operator.mjs \
  --server maincloud \
  --database xushi-p4wfr \
  --bootstrap-secret <本次发布随机密钥> \
  --operator-identity <identity-hex> \
  --note "2026-04-27 migration"

迁移完成后可以撤销临时操作员:

node scripts/spacetime-revoke-migration-operator.mjs \
  --server maincloud \
  --database xushi-p4wfr \
  --operator-identity <identity-hex>

生产环境建议迁移完成后用 --no-migration-bootstrap-secret 重新发布一个未设置 GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET 的模块版本,避免引导密钥长期留在 wasm 中。

发布脚本密钥行为

当前所有会构建或发布 spacetime-module 的脚本默认都会生成并显示迁移引导密钥:

  • npm run spacetime:publish:maincloud:在本机 cargo build 前生成密钥,控制台输出 [spacetime:maincloud] 迁移引导密钥: ...
  • npm run dev:rust:在本地 spacetime publish --module-path 前生成密钥,控制台输出 [dev:rust] 迁移引导密钥: ...
  • npm run deploy:rust:remote:在构建发布包 wasm 前生成密钥,控制台输出 [deploy:rust] 迁移引导密钥: ...,并把同一份密钥写入发布包根目录的 migration-bootstrap-secret.txt。服务器执行 ./start.sh 发布 wasm 时也会再次显示该文件里的密钥。

如果迁移完成后不希望 wasm 继续携带引导密钥,重新发布时传 --no-migration-bootstrap-secret。远端发布包若使用 --skip-spacetime-build,必须同时传 --no-migration-bootstrap-secret,否则脚本会拒绝生成一个无法注入旧 wasm 的新密钥。

导出

export_database_migration_to_file(ctx, input)

输入字段:

  • include_tables: 可选表名白名单。为空时导出当前实现支持的全部迁移表。

返回字段:

  • ok: 是否成功。
  • schema_version: 迁移 JSON 结构版本。
  • migration_json: 成功时包含完整迁移 JSON 字符串,失败时为空。
  • table_stats: 表级导出统计。
  • error_message: 失败原因。

导入

import_database_migration_from_file(ctx, input)

输入字段:

  • migration_json: 导出 procedure 生成的完整迁移 JSON 字符串。
  • include_tables: 可选表名白名单。为空时导入文件内所有支持表。
  • replace_existing: 是否先清空目标表。跨服务器全量迁移必须为 true
  • dry_run: 只解析和统计,不写表。

返回字段:

  • ok: 是否成功。
  • schema_version: 迁移 JSON 结构版本。
  • migration_json: 导入场景恒为空,避免重复回传大 JSON。
  • table_stats: 表级导入或跳过统计。
  • error_message: 失败原因。

保留 export_database_migration_to_file / import_database_migration_from_file 名称,是为了减少已经记住的 procedure 名变更;语义上不再代表 module 直接读写文件。

Node 脚本

本机导出时,先确保本机 SpacetimeDB 服务和源数据库可访问,然后授权本机调用身份:

node scripts/spacetime-authorize-migration-operator.mjs \
  --server dev \
  --database xushi-p4wfr \
  --bootstrap-secret <本机源库发布时输出的随机密钥> \
  --operator-identity <本机 spacetime login show 中的 identity> \
  --note "local export"

导出脚本负责调用本机源库 procedure 并保存返回 JSON

node scripts/spacetime-export-migration-json.mjs \
  --server dev \
  --database xushi-p4wfr \
  --out tmp/spacetime-migrations/source-2026-04-27.json

tmp/spacetime-migrations/source-2026-04-27.json 复制到服务器后,在服务器上登录目标 SpacetimeDB并授权服务器侧调用身份

node scripts/spacetime-authorize-migration-operator.mjs \
  --server maincloud \
  --database xushi-p4wfr \
  --bootstrap-secret <服务器目标库发布时输出的随机密钥> \
  --operator-identity <服务器 spacetime login show 中的 identity> \
  --note "server import"

导入脚本负责读取服务器本地文件并把 JSON 字符串通过 Web API request body 传入目标库 procedure。因为 JSON 不再放进 spacetime call 命令行参数,所以不会触发 Linux spawn E2BIG

node scripts/spacetime-import-migration-json.mjs \
  --server maincloud \
  --database xushi-p4wfr \
  --bootstrap-secret <服务器目标库发布时输出的随机密钥> \
  --in tmp/spacetime-migrations/source-2026-04-27.json \
  --replace-existing

默认情况下,脚本会自动完成三步:

  1. POST /v1/identity 创建临时 Web API identity/token。
  2. 使用当前机器 spacetime CLI 登录态调用 authorize_database_migration_operator,授权这个临时 identity。
  3. 使用 Authorization: Bearer <临时 token> 调用 import_database_migration_from_file,把完整迁移 JSON 放在 HTTP body 中。
  4. 导入请求结束后,脚本会用同一个临时 Web API token 调用 revoke_database_migration_operator,撤销该临时 identity。

如果你已经有可用的数据库连接 token也可以显式传 --token <web-api-token>。这种情况下脚本不会自动授权;该 token 对应的 identity 必须已经是迁移操作员。

正式导入前建议先加 --dry-run,确认 JSON 可解析、版本匹配、表名都在迁移白名单内。

如需分批迁移,可用逗号分隔表名:

node scripts/spacetime-export-migration-json.mjs \
  --database xushi-p4wfr \
  --out tmp/spacetime-migrations/ai.json \
  --include ai_task,ai_task_stage,ai_text_chunk,ai_result_reference

--server 支持 devlocalmaincloud,也可以直接传 SpacetimeDB 服务器 URL。导出、授权、撤销默认走 spacetime call,使用当前机器的 CLI 登录态;导入默认走 Web API request body避免大 JSON 触发命令行长度限制。数据库名可通过 --databaseGENARRATIVE_SPACETIME_MAINCLOUD_DATABASEGENARRATIVE_SPACETIME_DATABASE 提供。

授权脚本额外支持:

  • --bootstrap-secretGENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET
  • --operator-identityGENARRATIVE_SPACETIME_MIGRATION_OPERATOR_IDENTITY
  • --note

表范围

首版覆盖当前 private table 报错相关与主运行真相表:

  • 认证:auth_store_snapshotuser_accountauth_identityrefresh_session
  • AIai_taskai_task_stageai_text_chunkai_result_reference
  • 运行存档与账户投影:runtime_snapshotruntime_settinguser_browse_historyprofile_dashboard_stateprofile_wallet_ledgerprofile_invite_codeprofile_referral_relationprofile_played_worldprofile_membershipprofile_recharge_orderprofile_save_archive
  • RPG 运行真相:player_progressionchapter_progressionnpc_statestory_sessionstory_eventinventory_slotbattle_statetreasure_recordquest_recordquest_log
  • 自定义世界:custom_world_profilecustom_world_sessioncustom_world_agent_sessioncustom_world_agent_messagecustom_world_agent_operationcustom_world_draft_cardcustom_world_gallery_entry
  • 资产索引:asset_objectasset_entity_binding
  • 拼图:puzzle_agent_sessionpuzzle_agent_messagepuzzle_work_profilepuzzle_runtime_run
  • 大鱼:big_fish_creation_sessionbig_fish_agent_messagebig_fish_asset_slotbig_fish_runtime_run

后续新增 SpacetimeDB 表时,必须同步把表加入迁移白名单与本文档。

风险与限制

迁移 JSON 作为 procedure 返回值和 HTTP request body 传递,会受 SpacetimeDB 调用响应体、请求体以及中间代理大小限制。数据量较大时,先按 include_tables 分批迁移;若单表本身过大,再补充分片 procedure而不是恢复 HTTP 文件桥。

spacetime call 在 PowerShell 中手写 JSON 容易被剥掉双引号。导入大文件时也不能把完整 JSON 放进命令行参数,否则 Linux 会在启动子进程时返回 spawn E2BIG。推荐使用仓库里的 Node 脚本,由脚本直接走 Web API request body避免 shell 二次处理和命令行长度限制。