銀行の一日は午前0時に終わらない
一言でいうと
銀行システムには、時計が指す時刻と、帳簿が認める日付が別々にあります。その2つがずれている間に入った取引をどの日付に計上するかが、事故の大半を生みます。
なぜ必要なのか
一般的なシステムでは、「今日」はdateコマンドが教えてくれる値です。午前0時を過ぎると日付が変わり、それで終わりです。
銀行ではそうではありません。昨日の取引をすべて集めて勘定ごとに集計し、利息を付け、外部機関と清算し、監督当局に提出する資料を作る日次締め(EOD, End of Day)バッチが回って、ようやく1日が終わります。このバッチは通常、夜10時ごろに始まり、明け方まで動きます。その間もATMは動き、インターネットバンキングは開いています。
そのため、銀行システムには2つの日付があります。
시스템 시각(posted_at) 실제로 그 거래가 들어온 순간. 서버 시계가 찍는다.
계정일자(biz_date) 그 거래가 어느 날 장부에 속하는가. 마감 배치가 끝나야 넘어간다.
2つの韓国語コメントは、順に、システム時刻(posted_at)は実際にその取引が入った瞬間でサーバーの時計が刻むものであることと、勘定日付(biz_date)はその取引がどの日の帳簿に属するかを表し、締めバッチが終わって初めて次へ進むことを述べています。
午前1時に入った送金が勘定日付では昨日であることがあり、夜11時に入った送金が勘定日付では明日であることがあります。この2つの値を混同することが、銀行の現場に初めて入ったエンジニアが最もよくする間違いです。
どう動くのか
1日の流れはおおよそ次のとおりです。
09:00 계정일자 = 2026-08-25. 온라인 거래가 biz_date=2026-08-25 로 들어온다.
22:10 마감 컷오프. 이 시각 이후 유입분은 biz_date=2026-08-26 으로 찍혀야 한다.
22:10 마감 배치 시작. biz_date=2026-08-25 인 거래를 집계한다.
22:40 마감 완료. 계정일자를 2026-08-26 으로 전진시킨다.
4つの韓国語コメントは、順に、09:00は勘定日付が2026-08-25でオンライン取引がbiz_date=2026-08-25で入ってくること、22:10は締めのカットオフで、この時刻以降に流入した分はbiz_date=2026-08-26が打たれるべきこと、22:10は締めバッチの開始でbiz_date=2026-08-25の取引を集計すること、22:40は締めの完了で勘定日付を2026-08-26へ進めることを述べています。
肝心なのは、締めの対象をシステム時刻ではなく勘定日付で選ぶことです。WHERE posted_at BETWEEN ...で締めの対象を取るコードは、いつか必ず間違います。
ここで、2つの点でずれが生じえます。
1つ目は、カットオフ以降に流入した分の勘定日付です。オンラインチャネルが締めの状態を読まず、古い勘定日付を打ち続けると、明日の取引が今日の帳簿に入ります。その取引をあとで翌日付に移すには、すでに作られた締めの出力物から取り出す必要がありますが、出力物はすでに外部機関に出ているかもしれません。
2つ目は、締めバッチが死んだときの再実行です。バッチが半分ほど回ったところで死ぬと、一部の取引だけが反映された状態が残ります。この状態でバッチを再投入すると、2つのうち一方になります。すでに反映されたものをもう一度足せば二重反映で、どこまで進んだかわからずに何もしなければ漏れです。
そのため、締めバッチは冪等(idempotent)でなければなりません。同じ勘定日付に対して何回回しても結果が同じでなければならない、ということです。冪等性を作る方法は大きく2つあります。
증분 방식 아직 반영 안 된 것만 골라 기존 산출물에 더한다.
빠르지만 '이미 반영된 것이 전부 옳다' 를 전제한다.
잘못 들어간 것을 빼낼 수단이 없다.
전량 재계산 해당 계정일자의 산출물을 통째로 지우고 처음부터 다시 만든다.
느리지만 이전 실행이 어디까지 갔는지에 의존하지 않는다.
2つの韓国語コメントは、順に、増分方式はまだ反映されていないものだけを選んで既存の出力物に足す方式で、速いものの「すでに反映されたものはすべて正しい」ことを前提とし、誤って入ったものを取り除く手段がないことと、全量再計算はその勘定日付の出力物を丸ごと消して最初から作り直す方式で、遅いものの前回の実行がどこまで進んだかに依存しないことを述べています。
日次締めのように1日に1回回るバッチでは、ほとんどの場合、全量再計算が正解です。計算量は1日分で、得られるのは「いつ回し直しても安全」という性質です。
現場での姿
第一に、「昨日の取引なのに今日の日付で計上された」という問い合わせは、たいていカットオフの問題です。顧客は時計を見て話し、システムは勘定日付で答えます。調査のときに2つの値を並べて見せるだけで終わることも多くあります。逆に、2つの値を区別せずに「システムがおかしい」と報告すれば、それは正常な動作を事故として上げたことになります。
第二に、締めが死んだときに最も危険な状態は、伝票の片側だけが反映された状態です。複式簿記なので、1日分を完全に集計すれば借方と貸方が相殺されて、残高の合計が0になります。ところが、バッチが伝票の借方の行を反映し、貸方の行を反映する前に死ぬと、この合計が0になりません。この数字1つが、「締めが中途半端に途切れた」ことを知らせる最も速いサインです。
第三に、締めバッチが対象を実行中の照会で取ると、締めが回っている間に入った取引が紛れ込みます。22:10に開始したバッチが22:12に照会を実行すると、その間の2分間に入った取引が照会結果に含まれます。対象は、開始時点の勘定日付で固定しなければなりません。このバグは、普段は何も起こさず、締めが長引く日や再実行する日にだけ表に出ます。
第四に、締めの確定と出力物の生成は別の仕事です。集計結果を作っておいて勘定日付を進めないと、オンラインチャネルは古い日付を打ち続けます。逆に、勘定日付だけを進めて出力物がなければ、翌日の照会は空の値を受け取ります。この2つを1つのトランザクションの中で終えるか、順序と失敗時の動作を文書で決めておく必要があります。
次のラボですること
2026-08-25の日次締めバッチが22:10に開始し、途中で死んだ状態をそのまま作り、そこから復旧します。240行のうち153行だけが反映された状態を把握し、締めが回っている間に勘定日付が誤って打たれた18行を探して翌営業日に移し、増分再実行がこの状況でなぜ成立しないのかを数字で示したうえで、全量再計算スクリプトを作って、2回回しても結果が同じであることを証明します。