A Bank's Day Does Not End at Midnight
In one line
A bank system has a time that the clock shows and a date that the books recognize, separately, and most incidents come from which date you post a transaction to while those two are apart.
Why this was needed
In an ordinary system, "today" is the value the date command tells you. When midnight passes, the date changes, and that is that.
In a bank, it is not so. The day ends only when the end-of-day (EOD) batch runs, which gathers all of yesterday's transactions, aggregates them per account, adds interest, settles with external institutions, and prepares data to submit to the supervisory authority. This batch usually starts around 10 p.m. and runs until the early morning. Meanwhile, ATMs keep running and internet banking is open.
So a bank system has two dates.
시스템 시각(posted_at) 실제로 그 거래가 들어온 순간. 서버 시계가 찍는다.
계정일자(biz_date) 그 거래가 어느 날 장부에 속하는가. 마감 배치가 끝나야 넘어간다.
A transfer that came in at 1 a.m. may be yesterday by accounting date, and a transfer that came in at 11 p.m. may be tomorrow by accounting date. Confusing these two values is the mistake engineers most often make when they first enter a bank site.
How it works
The order of a day is roughly like this.
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 으로 전진시킨다.
The key is that you choose what to close by the accounting date, not by the system time. Code that picks the close targets with WHERE posted_at BETWEEN ... is bound to be wrong someday.
Two things can go off here.
One, the accounting date of inflow after the cutoff. If an online channel keeps stamping the old accounting date without reading the close state, tomorrow's transactions go into today's books. To move such a transaction to the next day later, you have to pull it out of the closing output already produced, and the output may already have gone out to external institutions.
Two, re-running when the close batch dies. If the batch dies about halfway, a state remains where only some transactions are reflected. If you rerun the batch in this state, one of two things happens. If you add what is already reflected again, it is a double posting, and if you do nothing because you do not know how far it got, it is an omission.
So the close batch must be idempotent. It means the result must be the same no matter how many times you run it for the same accounting date. There are broadly two ways to make it idempotent.
증분 방식 아직 반영 안 된 것만 골라 기존 산출물에 더한다.
빠르지만 '이미 반영된 것이 전부 옳다' 를 전제한다.
잘못 들어간 것을 빼낼 수단이 없다.
전량 재계산 해당 계정일자의 산출물을 통째로 지우고 처음부터 다시 만든다.
느리지만 이전 실행이 어디까지 갔는지에 의존하지 않는다.
For a batch that runs once a day, like the end-of-day close, a full recomputation is almost always the right answer. The amount of computation is one day's worth, and what you gain is the property that "it is safe to rerun at any time."
What it looks like in the field
First, an inquiry saying "it was yesterday's transaction but it was posted today" is usually a cutoff problem. The customer speaks by the clock, and the system answers by the accounting date. In many cases, simply pulling out the two values side by side settles it. Conversely, if you report "the system is strange" without telling the two values apart, you have raised normal behavior as an incident.
Second, the most dangerous state when a close dies is one where only one side of an entry is reflected. Because it is double-entry bookkeeping, if you aggregate a full day's worth, debits and credits offset and the balance sum comes out 0. But if the batch dies after reflecting the debit line of an entry and before reflecting the credit line, this sum is not 0. This one number is the fastest signal that "the close was cut off halfway."
Third, if the close batch picks its targets with a query at run time, transactions that came in while the close was running get pulled in. If a batch that started at 22:10 runs its query at 22:12, the transactions that came in during those 2 minutes are included in the query result. The targets must be fixed by the accounting date at the start. This bug does nothing on ordinary days and shows up only on days when the close takes long or is rerun.
Fourth, confirming the close and producing the output are different things. If you make the aggregate result but do not advance the accounting date, the online channels keep stamping the old date. Conversely, if you only roll over the accounting date and there is no output, the next day's lookups receive empty values. Either finish both inside one transaction, or fix in a document the order and the behavior on failure.
What you will do in the next lab
You recreate exactly the state in which the 2026-08-25 end-of-day batch started at 22:10 and died midway, and recover from it. You work out that only 153 of 240 lines were reflected, find the 18 lines stamped with the wrong accounting date while the close was running and move them to the next business day, show in numbers why an incremental rerun does not hold in this situation, and then build a full-recomputation script and prove that the result is the same even when run twice.