数据表、事务与生命周期 v0.8.1#
2026-09-13,配套基础 schema.sql 与追加在同库的 multiplayer-schema.sql。依次执行两份DDL,不可只执行多人文件。SQL 是空库参考建表稿;生产 migration、仓储和业务处理器尚未实现。
1. 数据归属与关系#
Web 本地的 profiles/entities/outbox/sync_meta/migration_backups 保存在 IndexedDB,通过 API 访问云端数据。一个本地 profile 最多绑定一个云 user;同一个云 user 在多设备有不同本地副本。本地用户选择仅本地保存时不创建服务端行。
erDiagram
users ||--o{ auth_identities : 登录方式
auth_identities ||--o| password_credentials : 密码
users ||--o{ devices : 设备
devices ||--o{ sessions : 会话
users ||--o{ game_runs : 对局
game_runs ||--o| run_results : 唯一终局
game_runs ||--o{ save_slots : 续局分支
game_runs ||--o{ reward_ledger : 奖励事实
users ||--o{ daily_rewards : 每日累计
users ||--|| player_summary : 成长汇总
users ||--o{ sync_operations : 幂等回执
users ||--o{ sync_changes : 增量日志查看流程图源文
erDiagram
users ||--o{ auth_identities : 登录方式
auth_identities ||--o| password_credentials : 密码
users ||--o{ devices : 设备
devices ||--o{ sessions : 会话
users ||--o{ game_runs : 对局
game_runs ||--o| run_results : 唯一终局
game_runs ||--o{ save_slots : 续局分支
game_runs ||--o{ reward_ledger : 奖励事实
users ||--o{ daily_rewards : 每日累计
users ||--|| player_summary : 成长汇总
users ||--o{ sync_operations : 幂等回执
users ||--o{ sync_changes : 增量日志基础DDL 中35张表;在线领域另外24张表,共59张表。基础表按下列模块组织。辅助表可在对应能力实现时进入 migration,但首期上线所需能力不能以“稍后补表”省略身份、安全或数据恢复约束。
| 模块 | 表 | 数据来源与允许写入者 |
|---|---|---|
| schema | schema_metadata | migration runner记录设计/数据库版本;不是游戏版本 |
| 身份 | users、auth_identities、password_credentials、recovery_codes | auth service;昵称由profile接口,kind/status不得由客户端直接写 |
| 会话 | devices、sessions、auth_receipts | auth service;仅哈希与短期加密回执,不保存明文密码 |
| 发布内容 | rule_revisions、content_revisions、daily_challenges | 服务器每日生成验证后原子发布的受控清单;玩家无写接口;公开目录不含私有seed/地图 |
| 玩家设置 | profile_settings | 设置操作,按field独立CAS;联动combo整组 |
| 对局 | game_runs、run_results、save_slots | run/save同步操作;终局唯一不可更新 |
| 成长 | level_progress、daily_rewards、reward_ledger、player_summary、medal_facts、player_medals | 结算/导入/合并服务按事实派生;客户端无直接数值写接口 |
| 迁移文件 | blobs | 仅legacy_import临时迁移包;个人录像不建云表、不上传 |
| 同步 | retired_entities、sync_operations、sync_changes、sync_state、sync_clients | 同步服务;游标水位和退休ID不由客户端声明为可信 |
| 带入 | merge_tickets、merge_jobs、legacy_imports | 合并/迁移服务;服务端计算预览与指纹 |
| 后台与恢复 | background_jobs、sync_artifacts、artifact_pages、audit_events、deletion_records | 后台任务、导出/快照服务、审计与删除服务 |
2. 字段与约束口径#
- DDL 的 TEXT ID 在入口校验 UUID,所有 public JSON 数值校验安全整数。数据库 INTEGER 为64位;
xp_units上限1e14沿用现行经验容量,不是新增奖励上限。其他计数接近JS上限时返回上限错误并保留源事实,禁止溢出。 profile_version只在玩家可见资料修改时增长;data_revision每个改变可合并游戏数据的成功事务增长一次,合并预览据此防竞态;登录活动心跳不增长它。- sync实体版本各自单调增长。run.create固定元信息版本1,run.finish固定终局版本1并作为独立
run_result实体发变更;API的run.finish base_version固定为1表示已登记run,终局幂等不使用CAS覆盖。 - run_results.payload_hash 只计算规范化终局业务payload,排除 op_id、device_id、client_time等传输信息。sync_operations.payload_hash计算完整Operation,两者用途不同。重试改op_id但终局相同仍业务去重。
- blob引用使用
(user_id,blob_id)复合外键;run、device、源merge session同理。SQL之外,仓储所有查询仍显式带会话user_id;外键不能替代读权限。 - 表中的 JSON CHECK 只保证语法;枚举、模式快照结构、日期真实性、分数上界、证据可靠性、同mode与同revision必须由版本化验证器检查。UUID/摘要内容也由API检查,不把length检查误称为密码学验证。
- content_id必须把模式、难度、路线/关卡身份编入规范命名空间。经典随机局使用如
classic/easy的配置清单,随机棋盘由run.config与证据表示;每日使用daily/YYYY-MM-DD/tier固定内容。revision不可变,已发布每日映射不可覆盖。 - schema包含必要的SQL唯一/外键/非负/JSON/终局与账本不可修改约束。
retired_entities禁止重建、临时迁移包128MiB额度、参数版本匹配、用户data_revision增长等需要业务事务;没有声称DDL独自保证全部规则。
3. 事务规格#
SQLite仅一个活动写者。连接设foreign_keys=ON、WAL、synchronous=FULL、busy_timeout=5000ms [待测试];时间依据是短暂写冲突容忍窗口,超过窗口返回可重试503。BEGIN IMMEDIATE前完成耗时验证,事务中不等待网络或外部服务。
T1:游客注册#
输入:通过验证的游客上下文、规范账号、提前计算的Argon2id哈希、随机会话/恢复码/回执密文。
事务:重新鉴权 → 检查guest和当前auth_version → 插入identity/credential(唯一冲突整体回滚)→ users.kind=registered/profile_version+1 → 插入恢复码摘要 → 撤销旧会话/旧敏感receipt → 新session → 增data_revision、profile同步变更、审计、新auth_receipt → commit。
输出:原user_id的新会话与注册回执。失败:账号占用不改变游客数据;同一次网络重试取原receipt。不得先commit账号再“尽力写入”恢复码和回执。
T2:对局结算与每日补差#
事务外从已登记run和受控规则验证终局,输出奖励目标T、通关事实、计分/勋章事实及provenance。事务内重新核对run内容/规则标识与会话状态;命中op回执先返回,不重新结算。
BEGIN IMMEDIATE
if run_results[user,run]存在:
同业务payload → 本op写业务重复回执(奖励delta=0)
不同payload → 本op写RUN_ALREADY_SETTLED回执
else:
普通局:按rules_revision得到delta,award_key="run/{run_id}/terminal"
每日:查询日期/难度的daily_rewards
若前置档未完成或服务器日期未到 → rollback,返回可延后结果
previous.won=true → delta=0
否则 delta=max(0,T-previous.paid_units)
paid_units += delta;won |= 本次获胜
award_key="daily/{date}/{tier}/{run_id}/terminal"
插入run_results
受全局经验容量约束入账(允许实际delta裁剪),插入唯一reward_ledger
更新daily/关卡/勋章/summary
每个变化实体版本+1,写同版本内容到sync_changes
user.data_revision+1,写sync_operations回执
COMMIT每日 paid_units 记录该题已确认权益(T补差的逻辑累计),reward_ledger.delta_units记录实际进总经验的数值。到经验容量时可能截断入账,回执必须含entitlement_delta_units、delta_units、cap_applied,且每日权益仍视为已领取;以后降低经验不能重领。玩家主动放弃 status=abandoned 不给额外经验,统计口径按该rules_revision;不是把放弃改成可重复领奖的失败。
两个不同run同日首通由写事务串行读取won,只有第一次有效首通补差。same run两个终局由主键限定一条。服务端承认“第一次”为提交顺序,不信任客户端时钟回填先后。旧题已完成后换新规则也不能重新领同日期/难度奖励。
T3:快照更新与冲突#
先查retired_entities;匹配后执行参数化CAS:
UPDATE save_slots
SET snapshot_json = :snapshot, version = version + 1, updated_at = :now
WHERE user_id = :user_id AND slot_id = :slot_id
AND version = :base_version AND deleted_at IS NULL;changes()=1时继续同事务日志/回执;=0返回不存在、已删除或版本冲突。冲突不自动上传分支:本地先保留副本,用户选择保留云端或创建新slot_id/branch_id,后者仍共享原run_id。不要把stale update降成无条件INSERT OR REPLACE。
T4:存档删除与本地录像边界#
save删除事务清snapshot、设置deleted_at、version+1、退休ID、日志和回执。90天后可删大墓碑,但退休ID随账号保留,陈旧create/update不能复活。个人录像不建云端replays表,不写同步墓碑,不占云文件额度;本机删除只影响本机收藏。
blobs只暂存白名单过滤后的legacy_import迁移包,拒绝个人录像及录制草稿;成功导入保存指纹与来源后按24小时期限回收,未完成迁移任务可延后。daily_replay_uploads、restricted_segments/chunks只保存官方元数据/Redis key,上传正文/共享回放/播放块全部存Redis并带TTL,最小在线摘要在online_events;不持久化所有玩家的完整过程录像。
T5:游客合并#
事务核对ticket目标、有效期、source为guest、target为registered、两个data_revision与预览相等;固定按user_id排序读写,保持未来迁移数据库时的锁顺序。小数据预算按最大100操作等起点压测,超过可接受锁时长则返回可重试状态,不部分合并。
普通终局按origin/run去重(不同内容同源保留目标结果、源保留为冲突证据,不自动加奖)。对重复每日,归并paid权益取max、won取OR,不把账本相加。不同每日相加。目标ledger永不UPDATE;引入源普通独立奖励、历史基线调整和每日补差时写新的merge_adjustment,award_key包含merge_job_id和业务来源,source来源映射保存在detail_json,后续再次导入能识别已消费来源。
历史总经验可能已包含每日,分解时必须避免重复:每份旧基线B读取其每日已领D;若0≤sum(D)≤B,历史非每日底额N=B−sum(D);若缺失/不一致,标记未分解基线,保守整份取max且不另加其每日数额,只带入每日“已领”状态阻止补发。可分解来源的非每日历史底额取max,各日权益取max;与未分解基线混合时采用整份历史基线max保守方案,并在preview列明。不能一边保留完整B、一边又将其D再次加进总经验。
普通新事件与历史基线按迁移水位区分,已覆盖旧数据的事件不得再次累计。总经验受容量裁剪;账本记录实际调整和计算明细。完成后重建summary/勋章,写目标变更、源状态merged、撤销源会话及其他票据、消费当前ticket、写merge完成结果。所有source关联ID带owner,复制到target时发生ID碰撞需映射并保留origin,不修改已存在run内容。
T6:分页快照与备份恢复#
只读事务同时读取用户实体和全局seq高水位H,生成有大小上限的暂存页后结束读事务;完成持久化页/manifest后发布artifact。读取页继续鉴权。正常artifact过期不改变玩家数据;备份恢复必须换全局/玩家epoch、撤销恢复出的会话、重新执行外部删除记录,旧游标不得沿用。
4. 留存、删除与运维#
所有周期初值 [待测试],依据为离线容忍、可重试时长和初期磁盘预算;产品上线时将游客清理与账号删除周期写入界面说明。
| 数据 | 初始留存 | 清理/恢复约束 |
|---|---|---|
| 身份、玩家档案、run/结果、奖励去重、退休ID、迁移来源 | 到账号删除 | 不随普通回执清理;来源标识不因合并丢失 |
| sync_changes和大墓碑 | 90天 | 先推进min_retained_seq;旧cursor强制bootstrap |
| sync_operations | 180天 | 超窗依赖必须重基;run/award业务唯一键继续生效 |
| auth_receipts | 10分钟 | 过期或安全状态改变即失效;解密密钥在DB外管理 |
| bootstrap/export artifact | 30分钟 | 页/manifest一起删;客户端过期重开任务 |
| 临时迁移包blob | 24小时 | 已记录导入结果/指纹后回收,未完成任务暂缓;不包含个人录像 |
| 活跃会话活动写入 | 最快60秒一次 | 权限判断每次查DB,不因减少活动写入而缓存吊销 |
| 云游客 | 不活跃90天 | 先置deleting撤销访问,再执行同一删除管线 |
| 审计 | 90天 | 仅动作/脱敏元数据,个人删除时去关联;不记录正文 |
| 备份 | 每小时,日备份30天 | 一致性Backup API,加密异机;目标RPO1h/RTO4h须实测 |
以下为私人域清理;有冻结榜引用者先遵循第6节保留最小脱敏引用壳,不能硬删外键对象。物理删除不是随手DELETE FROM users:先写deletion_records和异机恢复清单,再取消任务,删除merge_jobs及相关票据、回执、结果/奖励/勋章来源/save、run、blobs、会话/设备与其他归属数据,最后删users;SQLite外键全程开启。若待删玩家被merged_into_user_id引用,同事务将这些已合并源置deleting并清merged_into_user_id、排入删除管线,不能留下悬空外键。共享merge历史需对另一方保留脱敏来源去重键,不保留源个人资料。任意一步失败,用户仍处于deleting且无法访问,任务幂等重试。
删除清单在旧备份有效期内保留;至少将其增量复制到数据库备份之外的持久位置,旧库恢复前重放清单。没有这一步,单靠旧SQLite内的删除表不能保证不复活刚删除的账号。物理清理后备份到期目标30天,在线清理目标7天;SQLite删除页的回收需安排安全擦除/重写备份策略,不把“删行”声称为磁盘每一字节已立即抹除。
5. 验证边界#
verify_schema.py 用临时数据库验证DDL、用户名唯一、跨玩家外键、终局/奖励唯一与不可变、JSON/数值检查、事务回滚、存档CAS、seq单调以及两个连接争夺同一奖励的唯一约束。它不代表HTTP、密码库、单进程限流、完整奖励算法或真实游戏数据迁移已测试。
上线前还需用真实API验证:不同op同run重复提交;首通/失败并发;合并基线分解;账号切换与CSRF;artifact与pull交错写入;过期游标+旧outbox;游客注册响应丢失;删除/备份恢复;限额竞争。每项故障都必须保留本地可导出的成果。
6. 每日与回放表、字段与事务#
在线DDL同库追加24张表,共59张。本版删除live_positions/live_sessions/live_segments三张专用表,排名/候选/注记继续用board_kind区分四榜;参考空库DDL不是升级已上线服务的migration。
| 分组 | 表 | 关键字段与约束 |
|---|---|---|
| 权威在线 | online_games、online_seats、active_competitions、online_commands、online_events、online_results | daily_normal/daily_ranked;一人一个正式占用;当前状态+命令去重+event/chain/state_hash,非完整录像归档 |
| 限制 | account_restrictions | 仅ranked作用域;无对战冷却或匹配 |
| 生成发布(新) | daily_puzzles | date/tier主键,私有map/hash,generator/validator版本,无猜要求与报告;公开目录只投影元信息 |
| 日期进度 | daily_boards、daily_account_state、daily_attempts、daily_view_entitlements、daily_bests | UTC starts/ends、open/frozen/invalid、generation/frozen_at;通关与观看锁、零伤及record_complete分别表达;attempt含官方段来源device/manifest,正文仍需候选授权 |
| 成绩候选(新) | daily_score_submissions | board_kind、1或3段attempt、total/formed/entry_no、compared_generation/manifest;同账号日期每榜一个pending |
| 官方上传(新) | daily_replay_uploads | 绑定submission/user/attempt、gzip/双hash/字节数、redis_key及状态;正文只存Redis,只有候选可发授权 |
| 公榜/回放 | daily_rank_revisions、daily_rankings、restricted_segments、restricted_chunks | 日期级四榜完整generation追加;每榜rank1..10;单项1段、综合3段;DB仅录像/投影块的hash/key/字节数/截止元数据;正文仅Redis TTL存储,到期不改名次 |
| 历史注记(新) | daily_rank_annotations | 指向固定date/generation/board_kind/rank,取消/撤回/匿名化/撤录像/平台事故,不递补 |
| 观看与运营 | watch_sessions、watch_exposures、moderation_cases、online_http_receipts | watch绑定身份/session或匿名cookie;历史允许匿名且不建exposure;当天释放屏障;复核与幂等 |
新增事务:三档地图验证后一次发布;正式局/控制占用;单操作状态与锚点/回执;正式结果桥接私人日权益;成绩预选与缺失段授权;Redis写正文/提升TTL/持久化确认后,DB事务提交best+四榜完整generation(复制未变化榜)+录像元数据+回执,之后写榜单缓存;不得宣称跨SQLite/Redis原子提交;到期冻结最后generation;当天首帧曝光锁;冻结后注记/撤播放。
日期检查同时存在于业务写调度器与SQL护栏:过了ends_at即拒绝新榜revision/best/指针更新,即使freeze任务还未运行。已发布ranking/revision不可修改或删除,冻结board不可改回open。SQL时钟护栏精度为秒,UTC日截止恰在整秒;游戏用时和业务截止检查仍用毫秒与服务器单调时间。
SQL无法独立证明以下规则,由事务服务负责:真实日期、1或3段同日/各档归属、sum/max/entry_no与服务器事实一致;上传者使用当前有效会话;header/hash/压缩正文与服务器锚点/重演一致;候选真实达到当前门槛;今日观看三档资格与曝光屏障;历史请求确属冻结Top10及未过期;每榜发布完整10行以内、日期一代最多40行、旧revision不能被追加偷改;晚到验证不能发布。禁止把JSON语法检查、一个hash或SQL测试等同于防作弊认证。
复核不得在历史榜重排。账号删除撤认证、清私人档案/设备关联与公开内容/昵称,再清无引用在线数据;被不可变榜引用的user/game/attempt只保留最小不可登录的脱敏引用壳,必要seat清空控制device引用和内部状态。快照里的名字不永久复制,展示从可清理资料投影。录像三表不含compressed_content/projected_content BLOB;到期Redis自行清正文,读取按expires_at判断,DB不需job更新expired。业务撤公开先拒绝访问再UNLINK Redis已知key;保留引用元数据。具体留存、数据删除与备份要求见每日架构。
四榜唯一键、候选条件、跨榜录像引用与最终60段引用上限见四榜规格;Redis实际容量须包含所有曾提升公开TTL的正文,见Redis容量设计。
Redis key、绝对TTL、跨库提交、AOF与故障处理见Redis设计。不在DB的JSON/回执/导出中塞gzip或base64录像。
7. 版本记录#
| 版本 | 日期 | 变化 |
|---|---|---|
| 0.8.1 | 2026-09-13 | 收紧录像大小/事件上限;双hash上传前去重;补流式拒收、配额、慢连接及验证资源保护 |
| 0.8.0 | 2026-09-13 | Redis限每日榜单缓存与官方录像TTL存储;录像正文移出SQLite,不设录像到期清理job;补跨库故障与持久化边界 |
| 0.7.0 | 2026-09-13 | 移除直播入口/协议/三张专用表;观看仅限每日公榜录像,保留四榜与私人同步;整理审阅范围 |
| 0.6.0 | 2026-09-13 | 初中高与综合四榜独立;单项1段/综合3段,录像共享、统一冻结、跨端分段提交;48项DDL检查通过 |
| 0.5.0 | 2026-09-13 | 删除对战表/枚举;新增服务器每日题、候选、上传、冻结注记;分离实时摘要与完整官方证据 |
| 0.4.0 | 2026-09-13 | 私人录像仅本机,建立首份多人DDL |