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

290 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构评审与修订建议
> 评审对象:《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 分离**——同实例意味着这次跨界根本不需要网络。
现在的设计同时付两份成本:既没有独立库的隔离好处,又背上了微服务的仪式感。
**建议**:改为只读视图,视图名带版本号以保住"版本化"这个要求:
```sql
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 层强制:
```sql
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** | 判断性/远期,可延后 |