Files
yovision/docs/raw/13-架构评审与修订建议.md
T
QiuSW c7d0ce3ed9
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled
docs(raw): archive prototype review sources
2026-08-04 14:46:12 +08:00

15 KiB
Raw Blame History

架构评审与修订建议

评审对象:《03-通用场景应用方案》《08-三系统职责划分》,以及工程侧 04-architecture.md 状态:全部为建议,均未生效。 每条给出现状、问题、建议、影响面和我的把握程度,供逐条裁决。 裁决后:采纳的条目改写对应文档正文,本文保留为决策依据;未采纳的条目保留并记明理由,避免以后重复讨论。 日期:2026-08-04


0. 总评

架构的骨架是对的,尤其三处:

  1. 控制面/数据面分离解 NAT —— 隧道只走 ONVIF 控制流量,视频边缘主动推、绝不过隧道,且明确"控制面丢失不停视频"。多数团队会把视频塞进 VPN 然后在带宽和单点上栽跟头
  2. 水平触发对账器,只收敛不回滚 —— DB 单一真相源、幂等、退避、独立信号量、10% 孤儿删除安全闸。"不做跨系统回滚"是分布式里最易做错的决定,这里做对了
  3. Event / Alert 分离 —— 事件不可变,告警是带状态机的响应过程,投递三个事实分开存。多数系统合成一个 notified 布尔,然后永远回答不了"到底有没有人管"

下面 13 条是我认为需要修订或补空的地方。A 组影响架构决策,B 组是数据层空缺。


A 组 · 影响架构决策

A-1 Bell 的职责边界应按变化频率再切一刀

把握程度:中(这是判断,不是硬伤)

现状:Bell = 事件校验/存储 + 规则引擎 + 告警状态机 + 投递 + 反馈闭环 + 租户 RBAC + 审计 + 配额真相源 + 管理后台 + 前端 + 大屏 + 场景包。

问题:Sense 与 Brain 的边界很干净——按运行时和语言切,边界落在阻抗真正变化的地方。Bell 没有用同一把尺子。它内部有一条清楚的断层:

事件/告警内核 管理面
内容 ingest、event、rule、alert、deliver、feedback tenant、RBAC、audit、配额、web、packs
变化频率 低 高
正确性要求 极高(状态机、幂等、重启续跑) 常规 CRUD
出错后果 告警丢失 页面报错

捆在一起意味着改一个权限要碰告警内核的部署。

建议:不必现在拆成两个部署单元,但在 Bell/internal/ 内部先立起这条边界:核心包不得依赖管理面包,管理面通过接口调用核心。M4 若确需拆分,成本接近零;不拆也没有损失。

影响面:《08》§1.1 Bell 条目、§2.3 目录结构;04-architecture.md §2。


A-2 配额不该走跨系统 API,应改为同库只读视图

把握程度:高

现状:《08》§3 边界 7、04-architecture.md §5 第 7 条——Bell 拥有 site.max_video_channels,Sense 通过"版本化内部 API 或只读投影"读取,并定义了配额服务不可达时的降级语义;api.md §2 已把它列为待 M2 设计的接口。

问题:为了一个整数,引入了三个活动部件(接口、超时重试、降级态)。而架构第 5 条已经定了一个 PostgreSQL 实例,schema 分离——同实例意味着这次跨界根本不需要网络。

现在的设计同时付两份成本:既没有独立库的隔离好处,又背上了微服务的仪式感。

建议:改为只读视图,视图名带版本号以保住"版本化"这个要求:

CREATE VIEW bell.site_quota_v1 AS
  SELECT id, tenant_id, max_video_channels FROM bell.sites;
GRANT SELECT ON bell.site_quota_v1 TO sense_app;

Bell 改表结构时只要视图签名不变,Sense 不受影响——这正是版本化想要的效果。

连带收益:省掉一个内部接口、一套超时重试,以及《12》S-09 里一半的降级态工作量(区域策略那半仍需保留,因为它可能来自 Sense 自己的写路径)。

影响面:《08》§3 边界 7;04-architecture.md §4 步骤 1、§5 第 7 条、§7;api.md §2 删去 Sense → Bell 读取站点视频配额 一行;《12》S-09 缩小范围。

⚠️ 若未来确定 Sense 与 Bell 分库部署,本条自动失效,回退到 API 方案。裁决时请一并确认"一个实例"这个前提的有效期。


A-3 「Brain 无状态」的表述需要收紧

把握程度:高

现状:04-architecture.md §2 称 Brain「Python/CUDA,业务无状态」;《08》§1 称「无状态」。

问题:判定内核是 NORMAL → SUSPECT → CONFIRMED → RECOVERING,每个 track 一份、带时间窗。这就是状态。文档说的"业务无状态"实际含义是"无 DB schema",但两者不等价,差别会在 M4 分片时显现:

  • 进程在某人处于 SUSPECT 时重启,那次判定怎么办?
  • 分片再平衡时,track 状态机迁不迁移?
  • 同一个人从 media-01 机位走到 media-02,两个 worker 各持一份状态,如何不重复报警?

建议:把表述改为——

Brain 无持久化业务状态;判定状态机是进程内易失状态,重启即丢失。已接受的代价是:重启瞬间正在进行的单次判定丢失,不影响已产出事件与后续判定。跨 worker 的同一目标关联由 anon_id(ReID)在 Bell 侧聚合承担,不依赖 Brain 之间共享状态。

若这个代价不可接受,需在 M4 分片前给出方案,不能靠"无状态"这个词绕过去。

影响面:《08》§1 总表与 §1.1 Brain 条目;04-architecture.md §2 表格。


A-4 M1 必须引入一个假 reader,否则对账器在真空里开发

把握程度:高

现状:04-architecture.md §10——M1、M2 只动 Sense,M3 才有 Brain 和 Bell。

问题:对账器的全部意义是把 mediamtx 收敛到 DB 期望态。但 sourceOnDemand: yes 的语义是有 reader 才拉流,而 Brain 不在就没有 reader。于是 M1–M2 的对账器是在对着一个什么都不做的系统收敛。

四条硬约束里最要命的两条——改 path 配置会踢掉当前发布者、推理 source 挂上去就是一个 reader——都要到 M3 才第一次真正暴露。M2 的出口标准会给出虚假的安全感。

建议:M1 引入一个最小假 reader(ffmpeg -i rtsp://… -f null -,或一个只拉不解码的 gortsplib 客户端),作为 Sense 测试装置的一部分。目的不是功能,是让 sourceOnDemand 的语义在 M1 就受力:验证有 reader 时才拉流、卸载 reader 后 30s 自动停、改 path 配置会踢掉发布者。

影响面:04-architecture.md §10;《03》§6.1 M1 骨架增加 testutil/fakereader;M1/M2 出口标准增加一条。


A-5 先做纵向切片,不要等到 M3 才有端到端

把握程度:中高(这是排期建议,不是架构缺陷)

现状:Sense/、Brain/、Bell/ 是三个空目录。围绕它们已有 13 份 raw 文档、12 份工程文档、一份冻结契约(schema + 负面测试 + 三个夹具)、两个原型、三轮原型评审、一份 24 条待办清单。按现有顺序,第一个端到端价值出现在 M3。

问题:设计密度显著领先于证据。所有架构判断——分片、配额、对账、多租户——在 M3 前都拿不到反馈。

缓解因素是真实的:silver_pose 已验证、文档大量源自一个交付过的项目。所以这不是空想,但风险形状很具体。

建议:在 M1 内插入一条纵向切片,复用 silver_pose 已跑通的链路:

1 路摄像头 → mediamtx → silver_pose(出 v0.1 事件)→ 最小 Bell(一张表 + 一条通知)

对账器可以只有 50 行,Bell 可以只有一张表。目的不是交付,是让契约、sourceOnDemand 语义、事件流转在 M1 就受一次真实的力。

现有架构完全支持这么做——三系统边界清楚、契约已冻结。缺的不是设计,是把"先纵切一刀"排进里程碑。

影响面:04-architecture.md §10;06-tasks.md 路线图。


A-6 L2/L3 的分层边界是名义上的,真边界只有一条

把握程度:中

现状:分层 L0–L5,L2 是流水线(Savant),L3 是能力(检测/姿态/跟踪/ReID)。

问题:Savant 的模型就挂在 pipeline 里,这条线在实现时会消失。把它当作可独立替换的边界会产生错误预期。

建议:文档中明确——L2/L3 是认知分层,便于讨论职责;唯一可独立替换的技术边界是 Detector / PoseEstimator 接口。那条接口设计得早、目的明确(AGPL 逃生),是真的边界,应单独强调而不是淹没在六层里。

影响面:《03》§1.1–1.2 分层说明;04-architecture.md §1。


B 组 · 数据层空缺

B-1 设备遥测的历史数据没有归属 · 建议优先处理

把握程度:高

现状:原型已展示「时间漂移 +3.8s」「重连历史」「最后遥测」「分片实际码率」。这些是时序业务数据,当前文档中无任何归属。

问题:

  • 全量塞 Postgres:128 路 × 每分钟一条 ≈ 一年 6700 万行。会成为库里最大的表,却是价值密度最低的数据
  • 塞 Prometheus:那是运维指标,保留期短,且不适合按设备做业务查询("这台摄像头上个月的漂移趋势")

建议:

数据 存哪
当前值(最后一次漂移、当前分片、实际态) sense schema,随设备行
最近 N 条(默认 100)事件式记录:重连、校时、状态跃迁 sense schema,独立表 + 定期裁剪
连续趋势(码率、漂移曲线) Prometheus
若确需长期业务查询 TimescaleDB 扩展——仍是同一个 PG 实例,不违反"一个实例"的决定

影响面:《08》§1.1 Sense 条目;04-architecture.md §8;03-tech-stack.md 增加 TimescaleDB 作为条件性选项。

B-2 Brain 的本地重试队列形态未定

把握程度:高

现状:04-architecture.md §7「Brain 投递失败落本地队列重试,不阻塞实时推理主链路」——只有语义,没有形态。

问题:Brain 号称无 schema、无持久化状态,这个队列是它唯一的持久化,而且直接决定 Brain 崩溃时丢不丢事件。

建议:明确选型(文件追加 / SQLite / BadgerDB 任一皆可,但必须选定),并定义:队列上限、超限后的丢弃策略(建议丢最旧,且丢弃必须产生运维告警)、重启后的恢复顺序、重复投递由 Bell 侧 source_event_id 去重。

影响面:《08》§2.2 Brain 目录(emit/publisher.py 旁增加队列实现);04-architecture.md §7;api.md §2。

B-3 边缘节点的断网缓存形态未定,且需与环形缓冲区分

把握程度:高

现状:需求 §3.5「断网时边缘缓存事件,恢复后补传」;《03》另有"边缘环形缓冲"用于 pre-roll。

问题:这是两种不同的东西,文档中容易混为一谈:

环形缓冲 事件缓存
内容 视频(最近 N 秒) 结构化事件 + 元数据
覆盖策略 循环覆盖,丢失可接受 不可静默丢失
用途 pre-roll 证据回捞 断网续传

建议:文档中显式区分两者,并为事件缓存定义容量上限、超限策略与补传进度的可观测指标(对应《12》S-10)。

影响面:《03》§2.6 证据回捞;《08》§1.1 Sense 条目。

B-4 M1 SQLite → Postgres 的切换时机未定

把握程度:高

现状:《03》§6.1 与《08》§2.1 都写「M1 先 SQLite,schema 与生产 Postgres 保持一致」,但没写何时切、是否一次性、有无迁移脚本。

建议:钉在 M2 入口,且切换前 schema 必须已用 Postgres 语法编写(SQLite 只作为运行时,不作为 schema 方言的来源)。迁移脚本从第一天就写 Postgres 版本,SQLite 通过兼容子集运行。

影响面:04-architecture.md §10;《08》§5 里程碑表。

B-5 用数据库角色强制 schema 边界

把握程度:高

现状:架构第 7 条写「不跨 schema 直接写」。

问题:只靠约定,迟早会被一个赶工的 JOIN 破掉,而且破掉时没有任何信号。

建议:在 DB 层强制:

CREATE ROLE sense_app;  GRANT USAGE ON SCHEMA sense TO sense_app;
CREATE ROLE bell_app;   GRANT USAGE ON SCHEMA bell  TO bell_app;
-- sense_app 对 bell schema 无任何权限,A-2 的 site_quota_v1 视图除外

这样"不跨 schema 写"从纪律问题变成权限问题——写错了连不上,而不是上线后才发现。

影响面:04-architecture.md §5 第 5、7 条;部署清单。

B-6 建库、建角色、建 extension 的归属无人认领

把握程度:高

现状:sense schema 的迁移在 Sense/,bell 的在 Bell/。但建数据库本身、建角色、安装 pgvector / TimescaleDB extension 不属于任何一方。

建议:归入独立部署清单(与 MediaMTX、MinIO、Prometheus 同级),在 M2 之前指定归属。否则会变成"谁先跑谁建",各环境不一致。

影响面:《08》§1.2 基础设施行;部署清单。

B-7 人脸相关存储的位置需在 M5 前复核

把握程度:中

现状:《02》定人脸底库「独立 schema、独立加密、独立审计」,face_vectors.embedding 用 VECTOR(pgvector)。

问题:「独立 schema」与「独立加密」在同一个 PG 实例内能做到什么程度,需要具体方案(列级加密?TDE?还是独立实例?)。现在的表述在合规评审时会被追问。

建议:M5 前明确——若"独立加密"要求达到密钥与业务库分离的程度,则人脸库应是独立 PG 实例,此时它是"一个实例"决定的合法例外(因为它不参与对账,不破坏单一真相源前提)。

影响面:《02》§11 SQL 模型;04-architecture.md §5 第 5 条增加例外说明。


汇总:需要修订的文档章节

建议 《03》 《08》 04-architecture.md 其他
A-1 Bell 内部边界 — §1.1、§2.3 §2 —
A-2 配额改视图 — §3 边界 7 §4、§5-7、§7 api.md §2 删一行;《12》S-09 缩范围
A-3 Brain 状态表述 — §1、§1.1 §2 —
A-4 M1 假 reader §6.1 — §10 M1/M2 出口标准
A-5 纵向切片 — — §10 06-tasks.md
A-6 L2/L3 边界 §1.1–1.2 — §1 —
B-1 遥测归属 — §1.1 §8 03-tech-stack.md
B-2 Brain 队列 — §2.2 §7 api.md §2
B-3 两种缓存 §2.6 §1.1 — —
B-4 SQLite 切换 §6.1 §5 §10 —
B-5 DB 角色 — — §5 部署清单
B-6 建库归属 — §1.2 — 部署清单
B-7 人脸存储 — — §5 《02》§11

建议裁决顺序

顺序 条目 理由
1 A-2(配额视图) 影响 api.md 待设计接口清单与《12》S-09 的范围,越早定省的工作越多
2 A-4(M1 假 reader) 影响 M1 任务拆分,且成本极低
3 B-1、B-2、B-3、B-4(数据层四空缺) 都是补空不是改决策,无争议
4 B-5、B-6(DB 角色与建库归属) 部署侧,M2 前必须有
5 A-3(Brain 状态表述) 改表述,但会牵出 M4 分片的真问题,宜早不宜迟
6 A-5(纵向切片) 排期决策,需与商务节奏一起看
7 A-1、A-6、B-7 判断性/远期,可延后