All you need is PostgreSQL
All you need is PostgreSQL
June 25, 2026<br>by Eduardo Bellani
Introduction
The setup
Laying the foundation
The foundation: schemas and user roles for modularity
Domains
Accounts, managed and external
Transfers, constrained by a state machine and temporal periods
Transfer state history
Account auditing
Transactions, the immutable events
On maintaining business rules via meaningful constraints
The transfer state machine
Transactions must fall within the transfer period
Pending transactions require a pending transfer
No future transactions when closing a transfer
On capacity planning
Working set estimation
On write throughput
Enabling HOT Updates for Transfers
Making sure there are no Unused indexes
OLTP
Listing
The history of a transfer
OLAP
Balance ledger
Incremental maintenance via triggers
On serializable isolation
On decoupling
Benchmarking the startup scenario
Seed data
Write script: full transfer lifecycle
Read script: activity stream and balance
Running the benchmark
Results
Conclusion
Appendix A: Full code suite
Introduction
There is a deep cultural reflex in modern engineering: whenever a<br>problem appears, reach for a packaged solution instead of thinking from<br>first principles. The result is architectural cargo culting and lots of<br>missed opportunities. Some intentionally absurd-but-familiar examples:
We need an audit trail, let’s use {temporal/event sourcing DBMS}
Our application is slow, let’s cache that using {in-memory key-value database}
And since a relational database like PostgreSQL is still considered<br>mandatory,thanks mostly to its unmatched reputation, companies end up<br>stacking product on top of product on top of PostgreSQL. They inflate the<br>number of moving parts, operational risk, headcount demand, and overall<br>system entropy. Complexity1 grows, not because the problems demand it,<br>but because someone reached for a tool they saw in a conference talk.
In this post, I’ll walk through a set of common misconceptions that<br>drive teams to introduce new infrastructure when they don’t need to. All<br>of these can be solved with vanilla PostgreSQL 18 using standard<br>extensions available on RDS, with no special infrastructure and no<br>distributed-systems cosplay.
The goal in this article is not to argue that specialized systems are<br>never appropriate, but to show that the default assumption for your data<br>problems should be that my company can do fine with just PostgreSQL .
The setup
Here is a list of arguments that people put forth to reach for other<br>tools besides PostgreSQL, based on my experience:
I’ll need auditing and reconstructing state
Write throughput is too low
The transactional queries are too slow
The analytical queries are too slow
My app will be coupled to the Database
To address these, I’m going to use a variation of the Drosophila<br>melanogaster of the database field: the classic Supplier and Parts<br>database(Date 2003). I’ll update it to be more in line with<br>the usual problematic tables: Financial Transaction and their<br>originating transfers.
For the rest of this article we will be constructing a database design<br>based on modern PostgreSQL that will achieve the general goals listed<br>above and specific business requirements. Here is a requirements snippet<br>from a very popular banking API company:
Transactions: are immutable records of financial interactions with<br>Increase. You can think of them as the line items on your bank<br>statement. A Transaction with a positive amount means there’s more money<br>in your account. A Transaction with a negative amount means there’s less<br>money in your account. You can’t directly create a Transaction, and they<br>never change after they are made. Anything that causes money to move<br>around your Increase account results in a Transaction - initiated or<br>received transfers, card payments, earned interest, and more.
Transfers: which includes ACH Transfers, Wire Transfers, etc - are the<br>most common way to initiate money movement over external networks with<br>Increase. Transfers are one-to-many with Transactions, which they create<br>as side-effects. Unlike Transactions, Transfers are stateful and<br>transition through a lifecycle of different statuses as they move across<br>the network.
Pending Transactions: represent potential future credits or debits of<br>money into your account and are a separate resource from Transactions<br>(despite their similar name). Notably, while Transactions are immutable,<br>Pending Transactions are not, as they don’t guarantee the movement of<br>money. For example, Pending Transactions are created for card<br>authorizations (which can mutate or timeout) and also when placing a<br>hold on an account (which can be removed). Pending Transactions do not<br>affect your current balance (which is the balance you earn interest on),<br>but do affect your available balance (which is the amount you’re able to<br>move out of Increase). (Increase, Inc....