LabHub
学习 学习路径 课程

银行现场的语言

把死掉的日终幂等地救回来

在 LabHub 中继续学习

目标

重新运行中途失败的日终结算,使其恢复完成,同时既不重复入账也不遗漏。并掌握区分会计日期与系统时间来选择结算对象的方法。

为什么重要

银行系统中,时钟显示的时间(posted_at)与账簿认可的日期(biz_date)彼此独立。会计日期只有在日终批处理完成后才会推进,因此在结算运行期间进入的交易究竟应归入哪一天,会产生歧义。由此引发的事故占银行批处理故障的很大一部分。

日终批处理必须具备幂等性。这意味着针对同一个会计日期,无论运行多少次,结果都必须相同。否则批处理失败后,仅仅重新运行本身就会成为新的故障。重新运行只有两种方式:在已入账结果之上只补充未入账项目的增量方式,以“已经入账的内容全部正确”为前提。如果该前提已经不成立,增量方式便没有手段移除错误入账的内容。

本次事件如下。2026-08-25 的日终批处理于 22:10 开始,随后在中途失败。会计日期为 2026-08-25 的 240 行分录中已有 153 行入账,而且日终产出物的余额合计不为 0。此外,日终运行期间,有一个在线渠道仍持续写入旧会计日期。截止时间为 2026-08-25T22:10:00,下一营业日为 2026-08-26

步骤

  1. 创建并运行 /root/bank/gen_eod.py,生成 /root/bank/eod.db。其中应包含 372 行分录和日终运行历史。
  2. /root/bank/eod_state.txt 中写七行。business_date=eod_status= 来自 sys_paramtxn_total=applied=unapplied= 来自该会计日期的 txnbalance_rows=closing_sum= 来自 daily_balance
  3. posted_at 晚于截止时间且 biz_date2026-08-25 的行输出到 /root/bank/misdated.csv,表头为 txn_id,posted_at,biz_date,eod_applied。然后在 /root/bank/misdated.txt 中写三行:misdated=misdated_applied=naive_after_cutoff=。最后一个值表示不考虑会计日期、仅按 posted_at 筛选时的行数。
  4. 将这些行的 biz_date 移至 2026-08-26,并把 eod_applied 恢复为 0。不要改动已经完成日终结算的 2026-08-24
  5. /root/bank/rerun_plan.md 中按 ## 두 가지 재수행 방식## 선택과 근거## 멱등성을 어떻게 보장하나## 중단 기준 四个章节编写计划。“选择与依据”章节必须以数字写明需要重新计算的行数。
  6. 创建 /root/bank/eod_rerun.sh 并运行一次,将产出物输出到 /root/bank/db_run1.csv,表头为 biz_date,account,debit_total,credit_total,closing_balance。还要在 eod_run 中留下成功记录。
  7. 再次运行同一个脚本并输出 /root/bank/db_run2.csv,然后在 /root/bank/idempotency.txt 中写五行:run1_rows=run1_closing_sum=run2_rows=run2_closing_sum=diff=
  8. sys_parameod_status 改为 CLOSED,把 business_date 推进到 2026-08-26,并在 /root/bank/eod_report.md 中按 ## 무엇이 멈췄나## 계정일자가 왜 어긋났나## 재수행을 어떻게 안전하게 만들었나## 검증## 남은 위험 五个章节编写报告。

参考

重现日终结算失败时的状态

创建并运行 /root/bank/gen_eod.py,生成 /root/bank/eod.db。其中应包含 372 行分录和日终运行历史。

使用 python3 创建 /root/bank/eod.db。数据库包含 sys_param、txn、daily_balance、eod_run 四张表,并有 372 行分录。

统计失败的日终处理进行到了哪里

/root/bank/eod_state.txt 中写七行。business_date=eod_status= 来自 sys_paramtxn_total=applied=unapplied= 来自该会计日期的 txnbalance_rows=closing_sum= 来自 daily_balance

日终对象应按 biz_date 而不是 posted_at 选择。还要检查产出物的 closing_balance 总和是否为 0——如果不为 0,说明分录只入账了一侧。

找出会计日期错误的交易

posted_at 晚于截止时间且 biz_date2026-08-25 的行输出到 /root/bank/misdated.csv,表头为 txn_id,posted_at,biz_date,eod_applied。然后在 /root/bank/misdated.txt 中写三行:misdated=misdated_applied=naive_after_cutoff=。最后一个值表示不考虑会计日期、仅按 posted_at 筛选时的行数。

有两个条件:posted_at 晚于截止时间,同时 biz_date 为结算日。若只使用前一个条件,其他渠道正确写入下一营业日的交易也会被选中。

将会计日期移至下一营业日

将这些行的 biz_date 移至 2026-08-26,并把 eod_applied 恢复为 0。不要改动已经完成日终结算的 2026-08-24

不能只更改 biz_date。部分行已进入日终产出物,因此还必须同时恢复 eod_applied。不要改动已经完成日终结算的前一天。

确定重跑方式并写明依据

/root/bank/rerun_plan.md 中按 ## 두 가지 재수행 방식## 선택과 근거## 멱등성을 어떻게 보장하나## 중단 기준 四个章节编写计划。“选择与依据”章节必须以数字写明需要重新计算的行数。

需要四个章节。在“选择与依据”章节中,以数字写明需要重新计算多少行;在“幂等性”章节中,说明如何处理哪张表。

创建重跑脚本并运行一次

创建 /root/bank/eod_rerun.sh 并运行一次,将产出物输出到 /root/bank/db_run1.csv,表头为 biz_date,account,debit_total,credit_total,closing_balance。还要在 eod_run 中留下成功记录。

不要手动输入 SQL,要编写脚本。只有第二次执行与第一次逐字相同,才能测试幂等性。还要将第一次运行的产出物保存为 CSV。

再运行一次以证明幂等性

再次运行同一个脚本并输出 /root/bank/db_run2.csv,然后在 /root/bank/idempotency.txt 中写五行:run1_rows=run1_closing_sum=run2_rows=run2_closing_sum=diff=

原样再次运行同一个脚本,并重新输出产出物。逐行比较两个 CSV 的结果就是 diff。如果数值翻倍,说明你没有先删除,而是继续累加。

确认日终结算并推进会计日期

sys_parameod_status 改为 CLOSED,把 business_date 推进到 2026-08-26,并在 /root/bank/eod_report.md 中按 ## 무엇이 멈췄나## 계정일자가 왜 어긋났나## 재수행을 어떻게 안전하게 만들었나## 검증## 남은 위험 五个章节编写报告。

生成产出物与确认日终结算是两回事。要同时推进 sys_param 中的日终状态和会计日期,并完成报告的五个章节。