Files
pj_supermarket_cloud/docs/face-sync-reliability.md
T
dzc 7c20242ad7 perf: optimize face library startup and differential sync
Stream and validate native feature loading with fast decoding and bounded SDK batches.
Reconcile authoritative department snapshots with rollback-safe metadata and file updates.
Bound downloads, isolate duplicate identifiers, and defer unchanged invalid photo versions.
Remove unused legacy face APIs and document device measurements.

Validation: 42 Flutter tests in an isolated source snapshot; native unit and device checks;
all 5,537 registered features verified byte-for-byte, plus real SDK photo searches and metadata checks.
2026-10-22 16:35:52 +08:00

48 lines
6.4 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.
# 人脸差异同步修复与验证
2026-10-22,Flutter 3.27.4 / RK3568 Android 11。数据与日志保留在本机私有临时审计目录,本文不包含人员、图片地址或特征。
## 已确认的问题
- 本部门只有 846 条远端人脸,本地还有全租户调试导入的其他人脸,共 5,537 条。原同步仍读取、检查全部本地档案和文件。
- 预下载尚未完成时,主流程会重复下载相同照片;预取窗口无消费背压,中断时仍会下载后续清单。
- 删除范围由远端“当前有照片的部门”反推。某个子部门完全删空时,其本地残留不会进入孤儿删除范围。
- 姓名或用户绑定只更新 SQLite,内存识别档案保留旧值。可空部门名与空字符串的比较也会产生重复写入。
- 质量接口的 `ignoredNotApprovedSampleIds` 未落确认标记,一条历史结果在每次同步末尾反复上报。
- 全租户清单实际有 5,560 行、5,559 个唯一编号,其中一组编号冲突。旧实现任意选择先读到的一条;另有 22 张照片无法提取注册特征(无可检测人脸、口罩或 SDK 图片特征错误),每次同步都会重新下载和提取。
- V2 使用 OFFSET 分页,不能直接将并发变化造成的漏页当成本地删除;旧代码还会把任意质量请求失败拆成最多二十次请求重试。
## 实现
- 部门清单分页提高到服务端上限 500,独立拉取真实部门树,覆盖没有人脸的子部门。只查询范围内档案,并按编号补查新入部门的已有调试档案,防止重复插入。
- 全租户使用 V2 上限 1,000 条分页并关闭无用总数查询;按物理主键检测重复分页,隔离整组冲突编号并保留本地样本。确实需要删除时,在任何本地变更前再次拉取完整清单并核对指纹,变化则中止。
- 比对采用独立差集计划。未变化记录没有图片下载、特征提取或原生写入;照片 URL/版本变化时先对比字节摘要,相同内容复用已有特征和质量结果。缺少历史版本时先核对一次照片,避免盲信同一 URL。
- 元数据最多 100 条一个 SQLite 事务;仅姓名/用户绑定变化时批量修改原生映射,不重新注册特征。原生先校验整批再修改,失败时回滚 SQLite。人脸时间字段无损保存微秒和时区,不影响其他业务实体。
- 下载窗口限制为 3,等待同一在途任务,复用 HTTP 连接;退出后只等待已开始的请求。下载的完整响应限时 30 秒,正式文件使用唯一版本路径。
- 对原生明确判定为图片内容错误的提取失败,按照片版本暂缓 15 分钟。记录只存摘要和到期时间,原子替换文件;版本/地址变化立即重试,成功后移除,损坏缓存不会阻断同步。网络、授权和插件异常不进入此记录。
- 图片存在性改为按目录分块读取;保留小批量和异常回退。中断遗留的 `.pending-delete` 若仍有数据库引用则恢复;SQL 删除失败恢复图片及原生特征并准确统计失败。
- 质量上报只查询未确认记录,完整处理所有确认类别。整批请求失败留到下轮,停止拆批放大重试。目录清扫按变更或一小时周期执行。
- 人脸清单日志只输出条数,不在截断前遍历、脱敏和字符串化所有人员档案;进度回调按 150 ms 合并。
部门同步继续保留范围外调试档案;全租户同步继续仅在 Debug 环境提供。复用当前服务端已有接口,本次没有修改或部署服务端,也没有修改远端冲突样本或替换无效照片。
## 删除的无调用生产代码
删除旧 `syncFace` 包装、V1 人脸清单 API、旧文件名/直接下载助手、无调用 DAO、`registerFaceByDatabase` Dart/原生通道及包装、旧 `active/getConfig/getFace` 通道与模型、静态激活标记,以及无调用的旧整库/单条注册包装。旧算法仅保留在实际执行 A/B 的 androidTest 中,不进入业务 APK。
## 验证
- 最终提交的独立源码副本 42 项 Flutter 测试通过,Debug / Release 均构建成功。
- 回归使用真实 SQLite/Floor 事务和本地 HTTP 图片服务;模拟原生通道以注入失败,覆盖重复同步、照片变化/相同字节、元数据和事务回滚、空部门删除、范围外转入、SQL 删除失败、分页中断、冲突隔离、V2 删除复核、中断图片恢复、质量确认及网络失败、坏图版本重试和到期。
- 真机 SDK 校验 5,537 条特征的每个字节和全部编号,四张实际照片生成 RECOGNIZE 特征检索;增加原生档案更新不改变特征和整批拒绝不部分生效的两项检查。
- 真机本部门 846 条全部跳过、无下载与提取;全量清单 5,559 个唯一编号,5,536 条正常本地人脸直接跳过,一组冲突及 22 张不可注册照片如实报告。
- 全量首轮建立失败版本记录后,跨进程重启复测得到 downloads=0、extractions=0、nativeUpserts=0、metadataUpdates=0、deferredImages=22,耗时 18,127 ms(含首次目录清扫 2,114 ms);不把异常照片当作同步成功。
- 随后完整重复全量同步耗时 11,897 ms,同样是零下载、零提取、零特征/档案写入,22 个失败版本仍被暂缓。
- 最新部门同步耗时 4,112 ms,846 条全部跳过、失败 0;其中清单 2,114 ms、本地档案读取 394 ms、文件核对 1,298 ms。
- 再次部门同步同样 846 条全部跳过、失败 0;两次任务记录耗时分别为 4,280 / 6,503 ms(包含调度记录开销与网络波动)。
- 稳定复制真机数据库后与初始备份逐行核对:5,537 条人脸的 id、sampleId、userId、faceModel 完全不变,v15 完整性检查正常。原有 32 条订单逐字段完整保留,挂单仍为 0。设备期间另外产生了 2 条 FACE/PAY 记录(15:17 / 15:22 创建),本任务没有发起人脸支付,也没有删除或改写这些新增记录;不将整个验证期间描述成订单数量不变。
- 最终版全量首轮 19,059 ms;前一轮中间版本为 38,319 ms。分页、日志及网络条件同时变化,这些同步数字用于现场诊断,不构成受控速度比。
- 静态分析无 error。Floor 新增两个 `IN` 查询生成四条命名/字符串风格提示,其余为既有提示;没有手工改写生成代码。
启动性能 A/B、装库实现与测试命令见 [face-startup-performance.md](face-startup-performance.md)。同步耗时包含网络,单次数字不能作为受控速度比。