--- name: world_sync description: world_sync 模块技能文档。 --- # world_sync 模块 ## 概述 世界数据同步模块,管理世界数据的导入/导出/合并同步记录与执行。依赖 world 模块(world 表)、appbase(appcodes 字典 sync_type/sync_status)。通过 load_world_sync() 挂载。 自 M11b-2/M11b-3 起,本模块同时承载 **scense_runtime 服务端权威写入薄扩展**:单事务事件+状态写入(`pbl_runtime_authority.write_event_with_state`)与**事务提交后广播**(`pbl_runtime_broadcast`)。 ## 数据模型 ### 表 world_sync(models/world_sync.json) | 字段 | 类型 | 说明 | |------|------|------| | id | str(32) | 主键 | | world_id | str(32) | 所属世界(→world.id) | | sync_type | str(16) | 同步类型(0导入/1导出/2合并) | | source | str(255) | 数据来源 | | status | str(16) | 状态(appcodes sync_status) | | result_json | text | 同步结果 JSON | | sync_date | date | 同步日期(业务日期) | | created_at | timestamp | 创建时间 | - 主键:["id"];索引:idx_sync_world(world_id) - 字典:world_id→world、sync_type→appcodes_kv(parentid='sync_type')、status→appcodes_kv(parentid='sync_status') ### 运行时权威表(M11b-2,models/) `pbl_runtime_event`(append-only 事件)、`pbl_entity_state`(实体状态,state_version 服务端权威)、 `pbl_world_state_snapshot`(世界快照)。所有读写强制 `tenant_id` 打头,缺失 fail-closed。 ## 关键接口 - list_world_syncs / get_world_sync / create_world_sync / update_world_sync / delete_world_sync / execute_world_sync - wwwroot/api/world_sync.dspy(同步契约)、create/world_sync_update/world_sync_delete.dspy、get_search_*.dspy - M11b-2:`write_event_with_state(event_data, state_update, ...)` —— 事件 + 状态在**同一事务**内提交 - M11b-3 广播 API(`world_sync/pbl_runtime_broadcast.py`,已在 `__init__.py` 导出): - `install_broadcast_hook()` / `is_broadcast_hook_installed()`:把 `write_event_with_state` 包成 COMMIT 后广播(幂等,`load_world_sync()` 已自动调用) - `emit_post_commit_broadcast(write_result, context=...)`:手动发一条 - `wrap_with_post_commit_broadcast(fn)`:包装任意返回事务结果的函数 - `configure_broadcast(channels=..., webhook_urls=..., redis_url=..., queue_size=2000, retry_attempts=2, retry_backoff_s=0.02, enabled=True)`:宿主启动时配置通道 - `subscribe_broadcast(handler)` / `unsubscribe_broadcast(handler)`:进程内订阅 - `broadcast_stats()` / `broadcast_last_message()` / `broadcast_latency_percentile_ms(99)` / `drain_broadcast(timeout=2.0)` / `shutdown_broadcast()`:观测与优雅退出 - 通道类:`InProcessChannel`、`WebhookChannel`、`RedisPubSubChannel`、`NoopChannel`(均继承 `BaseChannel`,`publish(message)`) ## 事务提交后广播(M11b-3)要点 - **触发点**:仅事务 COMMIT 成功之后(`commit_ts` 即延迟起点);事务回滚不广播。 - **异步**:单 dispatcher 线程 + 有界 `queue.Queue(2000)`,`submit` 用 `put_nowait` 微秒返回,不阻塞事务返回;队列满降级为当前线程直发一次。 - **SLO**:`BROADCAST_LATENCY_SLO_MS = 300.0`,口径 P99;实测 n=200 p50=2.4ms / p99=4.1ms / max=4.2ms(`scripts/m11b3_selftest_output.txt`)。 - **失败语义**:fire-and-forget + 有限重试;通道/订阅者异常一律吞掉并 `ERROR` 日志 + `stats["failed"]` 计数,**绝不回滚已提交事务**、绝不外抛给业务调用方。 - **无轮询**:广播路径不含 `time.sleep` 轮询、不读 DB;等待靠 `Queue.get(block=True)` 条件变量。 - **环境变量**:`PBL_BROADCAST_DISABLED`(全局关闭)、`PBL_BROADCAST_WEBHOOK_URLS`(逗号分隔 webhook)、`PBL_BROADCAST_REDIS_ENABLED` + `PBL_BROADCAST_REDIS_URL`(Redis pub/sub)。 ## 陷阱 - 库名不硬编码:.py 用 ServerEnv().get_module_dbname('world_sync'),.dspy 用 get_sor_context(request._run_ns,'world_sync') - dspy 无 import - create/execute 必须设置 created_at、sync_date,否则 sor.C 静默丢记录 - update 剔除 _text 后缀 - codes.table 引用 world 不用 module.table 点号 - **广播 hook 幂等**:`install_broadcast_hook()` 重复调用不得重复包装(已用 `__wrapped_post_commit__` 标记),否则一条事件广播多次 - **广播内禁止再写事务/再发广播**:dispatcher 线程只做 publish,回写 DB 会造成递归与延迟失真 - **测试断言前用 `drain_broadcast()` 等待,禁止 sleep 轮询**(QC 会按「引入轮询」退回) - `RedisPubSubChannel` 惰性 import `redis`:环境缺包只 `ChannelUnavailable` 跳过,不影响 InProcess/webhook 通道,不要让 Redis 成为硬依赖 - `scripts/sync_code_to_app.py` 的 `collect()` 必须以 `WS_ROOT` 拼绝对路径 —— 用相对路径 `isdir` 会恒返回空清单,导致 `--check` 打印假 PASS(M11b-3 已修,改脚本时勿回退) ## 依赖 - world、appbase、rbac - M11b-2/M11b-3 广播零第三方硬依赖(stdlib:threading/queue/urllib/hashlib;redis 可选)