[governance] 无关源同步触发退费宽表全量重建:空集语义反了 + 缺相关源白名单 #88
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
生产实测:同步「设备套餐」(
device_combo) 会触发refund_wide退费宽表全量重建(66,451 行 / 约 50s)。device_combo与退费宽表毫无关系:wide_table_builder.py/matcher.py中零引用device_combo。宽表纯属被 post-sync 钩子"顺带"跑起来。
影响范围
device_combo/cs_record/bms_order/ SCRM 系列等)都会全量重建退费宽表根因判断
三处缺陷(
backend/app/services/bms_sync.py+backend/app/api/v1/uploads.py):bms_sync.pyuse_incremental = len(all_affected) > 0 and ...all_affected为空 = "本次导入未影响宽表任何一行",正确动作是跳过,却降级为全量重建bms_sync.pyany_success = any(r.get("success") ...)uploads.py_run_post_import_tasks()build_refund_wide(db)无条件全量,data_type仅用于打日志铁证:同一钩子内的
cash_flow_wide就没被触发同一段代码里
cash_flow_wide有相关源白名单(_cfw_relevant),实测生产:同一轮同步、同一钩子,
cash_flow_wide被白名单挡住、refund_wide没有 → 行为不一致,确证缺陷。处理动作 / 设计要求
按建议 A + B 修复(保守 + 正确):
all_affected为空且本次无任何宽表相关源导入时 → 跳过宽表重建(记录mode='skipped'与原因),不再降级全量REFUND_WIDE_SOURCES = (refund, ticket, wallet, external_complaint)(即宽表实际消费的全部上游源,与_collect_affected_from_imports的分支一一对应),在bms_sync.pypost-sync 与uploads.py_run_post_import_tasks两处都按白名单判定,与cash_flow_wide的_cfw_relevant写法对齐mode='skipped')mode/skipped_reason/relevant_sources)不做(显式取舍):
match_all:本次仅确认宽表为无谓开销;matcher 耗时与无关源的关系未量化,不在本单扩大范围(如后续需要另开单)_collect_affected_from_imports的时间窗口过滤逻辑:属另一类潜在漏采问题(导入旧窗口数据不被收集),需独立评估,不在本单device_combo每次全量 upsert(78234 更新):属该源自身的幂等/去重议题,与本单无关,另开单验收标准 / 关闭条件
build_refund_wide」与「相关源导入 → 仍重建」device_combo后,后端日志中不再出现refund_wide全量重建,出现mode='skipped'(或等价的跳过日志)refund(相关源)后,宽表仍正常重建(增量或全量),数据无缺失(对比重建前后行数与stats接口)pytest全量通过;ruff全绿bms_sync.py与uploads.py均按白名单判定(代码可见 + 测试覆盖)关联
docs/standards/数据同步与加工说明.md(同步链路)wide-table-full-rebuild-matcher-bug-20260905(同类"永远全量"根因)、wide-table-purpose-audit-20260908(宽表定位与真实消费方)同单扩展(2026-09-14 晚,用户拍板不另开单)
用户判断: 这与本单是同一个目标——「宽表触发条件过宽」——只是同一缺陷模式在
cash_flow_wide上的另一个实例。按issue-workflow.md§一.3「一单一目标」,合并后仍有唯一可验收目标(无关源不得触发任何宽表全量重建),故并入本单。
cash_flow_wide判据到CASH_FLOW_WIDE_RELEVANT_SOURCES白名单(原先内联元组且不校验写入行数:相关源 success 但 0 行写入仍全量重建 ~12s)
cash_flow_wide白名单缺external_complaint—— builder 第 111/153/188/224 行确实读
external_complaints填has_external_complaint,旧判据漏了它 → 外部投诉同步后该列陈旧(该刷不刷)uploads.py同步改用is_cash_flow_wide_relevant()_filter_relevant_sources(),消除重复代码不做(显式取舍):两张宽表白名单刻意不同(职责不同),已加测试锁死防止被误合并。
关闭凭证核验 (2026-09-14)
提交:
927e42c(阶段一:空集语义 + refund_wide 白名单)·b496779(阶段二:cash_flow_wide 判据统一 + 补漏)生产版本:
git-20260914150934测试:
backend/tests/test_refund_wide_trigger_scope.py(36 项)逐条对应正文「验收标准 / 关闭条件」:
pytest tests/test_refund_wide_trigger_scope.py -q→ 36 passed;含test_upload_device_combo_does_not_rebuild_wide/test_upload_refund_does_rebuild_widemode='skipped'+ 两条跳过日志refund_wide mode='incremental' built=6;cash_flow_wide total=61035pytest→ 1221 passed / 42 skipped;ruff check app/ tests/ alembic/→ All checks passedbms_sync.pycash_flow_wide_relevant_sources()/uploads.pyis_cash_flow_wide_relevant();测试:test_upload_cash_flow_relevant_sources_rebuild(6 源参数化)★ 实测①:无关源(设备套餐)→ 两张宽表都跳过
生产容器内真实触发
sync_all(data_types=['device_combo']):修复前同一动作(14:44 那轮):
wide={'built': 66451, 'mode': 'full'}≈ 50s 白跑。修复后:
built=0, mode='skipped',两张宽表均不重建。✅★ 实测②:相关源(退费单)→ 两张宽表都重建(防漏刷)
即「跳过」只对无关源生效,相关源一律照常重建 → 不存在因本次修复而漏刷。✅
关于关闭条件外的一项说明
正文「不做」列表中的三项目前确实未做(未跳过
match_all、未改_collect_affected_from_imports窗口过滤、未改device_combo全量 upsert),与正文声明一致,非降级验收。
边界说明(诚实记录)
结论:5 条关闭条件全部满足,予以关闭。