Why a Bank Does Not UPDATE the Balance
In one line
A bank core stacks up transactions and sums them instead of overwriting the balance — because if you keep only the balance, then when that number is wrong there is nothing left in the world to check it against.
Why this was needed
The way an ordinary service handles a balance is simple. UPDATE users SET balance = balance - 1000 WHERE id = 7. It is one line, it is fast, and it is easy to read.
The problem with this approach is not performance but that you cannot retrace it. One day an inquiry comes in saying the balance looks wrong, and all we have in hand is one number as it is now. To judge whether this number is right or wrong, we would have to compare it with a value computed somewhere else, and there is nothing to compare against. Someone suggests digging through the logs, but logs have a retention period, come in all kinds of formats, and above all are not the books.
Banks solved this problem centuries ago. The answer is double-entry bookkeeping.
How it works
Double-entry bookkeeping has one rule. Every transaction is recorded in two or more lines, and the sum of the debit amounts and the sum of the credit amounts must be equal.
차변(Debit) 자산이 늘거나, 부채가 줄거나, 비용이 생기는 쪽
대변(Credit) 자산이 줄거나, 부채가 늘거나, 수익이 생기는 쪽
When a customer deposits 1 million won in cash, it is recorded like this.
전표 JE-20260825-0001 (입금)
차변 1010 현금 1,000,000
대변 2010 요구불예수금 1,000,000
From the bank's point of view, the asset called cash increased by 1 million won (debit), and at the same time the liability called deposits received, the obligation to return that money to the customer, increased by 1 million won (credit). That a bank deposit is not the bank's asset but its liability — this is the point that confuses people most when they first look at a bank system.
A journal entry is one transaction, and a journal line is each line making up that entry. It is also common for three or more lines to attach to one entry. When you pay deposit interest and deduct withholding tax, two credit lines attach to one debit line.
In this structure, the balance is not a stored value but a derived value. The balance of an account comes from summing all the journal lines attached to that account. And the table that sums up all the accounts is the trial balance, and the total debits and the total credits of the trial balance must be equal.
This identity is the only self-verification device a bank system has. Even if millions of transactions come in a day, you can summarize all of them in one line and ask "is it 0?"
Real-world cores also carry a balance column per account for performance, because you cannot sum hundreds of millions of rows every time you look one up. But that column is a copy of the ledger, not the original. When the two diverge, the ledger always wins.
What it looks like in the field
First, a correction is not an UPDATE but a reversing entry. When you find a wrong entry, you do not fix that entry; you put in an entry that mirrors the original upside down (a reversing entry) to offset it, and post the correct entry anew. Three entries remain side by side in the ledger. This looks cumbersome, but it is the only way to answer someone who asks in an audit two months later, "was this entry originally this amount?" A system with traces of an UPDATE on the ledger is itself an audit finding.
When making a reversing entry, you write the amount as recorded. You must flip the wrong amount as it is for the error to be offset exactly. If you say "it is wrong anyway, so let us flip it with the right amount," at that moment only half the difference is erased, and the other half stays in the books without being recorded anywhere.
Second, the cause of an inquiry saying the balance does not match is usually one of three. The ledger is wrong, the balance copy is wrong, or both are right but the point in time they are viewed at differs. The three call for completely different responses. If the ledger is wrong, you must post a correcting entry; if the copy is wrong, you can recompute from the ledger and overwrite it; and if it is a timing issue, you fix nothing and only explain. Touching anything without telling which it is is the most expensive mistake in this line of work.
Third, an investigation that looks only at the balance column is bound to get stuck. A balance is just a number. You cannot tell from the number itself whether it is right or wrong. On the other hand, a value obtained by summing the ledger can be compared with the balance column, and the debit and credit sums per entry verify themselves. Which table you open first during an investigation decides what time you go home that day.
Rules for putting a ledger into a database
There are many ways to turn double-entry bookkeeping into tables, but the shape you will not regret later is mostly the same.
A posting is two or more lines in one transaction, summing to 0. If you put debits and credits in different tables, or put two amounts in one row, someday only one side goes in. Put them as several lines in the same table, and have the database check that the sum of that bundle is 0.
create table entries(
id bigserial primary key,
txn_id uuid not null,
account_id bigint not null references accounts(id),
amount_minor bigint not null, -- 차변은 양수, 대변은 음수
currency char(3) not null,
posted_at timestamptz not null,
business_date date not null);
create index on entries(txn_id);
-- 묶음의 합이 0이 아니면 커밋을 막는다(지연 제약 또는 트리거로).
Neither update nor delete. A wrongly entered posting is not deleted; you offset it by putting in one more opposite posting, and link the two bundles to each other. Only then can you always answer "what was the balance at a given point in time?"
Store the balance but verify it by computation. Summing everything every time is slow, so you end up keeping the balance separately, and a moment when that value and the sum of the postings diverge is sure to come. Keep it in the form of a snapshot at a point in time plus the postings after it, and at night recompute the whole thing and cross-check.
Amounts in integers of the smallest unit. For won it is 1, for dollars it is cents. Do not use floating point. If there are several currencies, never separate the amount from the currency — code that adds two amounts of different currencies is a bug.
Separate the business date from the posting time. A transaction that came in at 1 a.m. can belong to the previous business day. If you lump them into one, the closing figures differ from person to person.
Handle concurrency per account. If you post to the same account concurrently, the balance goes off. Either lock the account row with select ... for update, or choose a design that looks at the balance only as the sum of postings and keeps no balance row.
What you will do in the next lab
You build a ledger snapshot of three days yourself and find the one debit/credit imbalance hidden in it. Starting from the trial balance, you narrow down to one entry and one journal line, reconcile the balance column against the ledger sum, and confirm why the two divergences differ in nature. Finally, you correct it with a reversing entry and a re-posting without touching the original entry.