In SaaS credit billing, directly modifying the balance column is fast but risky. Hashscrapper records all deductions as history (ledger) and calculates the balance by summing up the history, using a ledger-based billing architecture. This approach is advantageous in terms of billing transparency, ease of debugging, and bulk concurrent processing performance.
TL;DR
- Directly modifying the balance (UPDATE) is fast but unable to trace errors
- Recording all deductions as history (ledger) allows proof of all transactions
- row lock (SELECT FOR UPDATE) causes bottlenecks in concurrent requests. Ledger INSERT has no bottlenecks
- Hashscrapper processes tens of thousands of requests per day, ledger-based approach excels in both speed and accuracy
- Billing architecture is a promise with customers, not just engineering
Why do most SaaS platforms directly modify the balance?
When deducting credits, most SaaS platforms do it this way.
They open the balance column, subtract a number, and save it.
UPDATE service_tickets SET use_credit = use_credit + 10 WHERE id = ?
It's fast.
It's intuitive.
Almost all billing tutorials teach this method.
That's how we started too.
What do we lose by only looking at the balance?
When problems arise in production,
there was nowhere to ask.
"Why is this customer's credit 3 short?"
"Was the deduction made from the failed request yesterday or not?"
"How do we verify if this number is correct?"
The balance only shows the result.
The process is missing.
It's like a household account book written in pencil.
If you erase and rewrite, you won't know what was originally written.
What is ledger-based billing?
So we changed our approach.
We don't directly modify the balance.
We record all deductions as separate history records.
The balance is calculated from the sum of the history.
이력 #1: -10 크레딧 (https://example.com, 성공, 2026-03-07 14:32)
이력 #2: -10 크레딧 (https://news.site.com, 성공, 2026-03-07 14:33)
이력 #3: 0 크레딧 (https://blocked.com, 실패, 2026-03-07 14:35)
─────────────────
잔액 = 총 크레딧 - 이력 합산
It's like a double-entry bookkeeping written in pen.
Even when making corrections, you draw a line and write on a new line.
No transaction disappears.
Every deduction must be accompanied by a record.
When, which URL, for how many credits, whether it was successful or failed.
Failed requests are not deducted.
If deducted, the reason must be preserved.
Why is ledger-based approach faster even in bulk processing?
Here's a question that arises.
"Wouldn't it be slow to INSERT history every time?"
Hashscrapper has been collecting data from over 5,000 websites for 8 years since 2018.
It processes tens of thousands of requests per day.
Speed cannot be compromised.
Paradoxically,
the ledger-based approach is faster.
To directly modify the balance, row lock (SELECT FOR UPDATE) is required.
If 10 requests from the same customer come in simultaneously,
9 requests have to wait in line.
It's like a traffic police letting one car pass at a time.
Recording history is different.
INSERT does not lock other rows.
Even if 10 requests come in at the same time, all 10 are immediately recorded.
It's like a 10-lane highway.
The balance can be summed up later.
| Approach | Processing 10 requests concurrently | Bottleneck | Error tracing |
|---|---|---|---|
| Direct balance modification (row lock) | Sequential processing (one by one) | Severe | Impossible |
| Ledger recording (INSERT) | Parallel processing (10 requests concurrently) | None | Immediate tracing with history |
The seemingly slower approach
has no bottleneck in bulk processing.
Why is billing architecture a promise with customers?
This is not a matter of engineering preference.
Billing architecture is ultimately the answer to this question:
- Can you immediately answer when a customer asks, "Where did my credit go?"
- Can you precisely identify where the system error caused incorrect deductions?
- Can you prove that failed requests were not billed?
A single balance column cannot answer these questions.
History is needed to provide answers.
We are a small company of 4 people.
Because we are small, it's not about doing things roughly,
but about doing things properly because we are small.
Being able to show at any time
where a customer's credit was spent.
That's why we chose a pen for our billing system.
Do not reduce the balance. Let history create the balance.
FAQ
Q. Isn't balance retrieval slow with a ledger-based approach?
While the balance is calculated by summing up the history, it is not aggregated in real-time with every transaction. Periodically caching or post-settlement has no impact on query performance. Even with tens of thousands of history records, index-based summation takes milliseconds.
Q. Do you record history for failed requests as well?
Yes. Failed history is recorded, but the deduction amount is 0. The fact that "this request failed and was not billed" itself is the basis of customer trust.
Q. Are there cases where row lock approach is suitable?
In small-scale services with few concurrent requests, the row lock method is simpler and more effective. However, in structures with frequent concurrent requests like API billing, the ledger-based approach is advantageous in both performance and traceability.
Q. What is the billing unit for Hashscrapper?
1 URL request = 10 credits, and deductions are made only when collection and transformation are successful. There is no credit loss in case of failure.




