Ledger represents a full-stack, financial accounting ledger based on the double-entry bookkeeping principle, running on either CockroachDB or PostgreSQL. It manages monetary accounts and a journal of balanced, multi-legged, multi-currency transfers between those accounts.
It's designed to showcase CockroachDB's scalability, survival, consistency and data domiciling capabilities and not the actual domain complexity of accounting. However, it's a realistic implementation of a general ledger with most of the fundamental pieces.
Ledger is the successor to Roach Bank with more focus on load testing and operational ease-of-use. Conceptually they are the same, but Ledger has a simpler design, improved UI and leverages JDK21 features such as virtual threads for better efficiency.
- CockroachDB v23.2+
- PostgreSQL 9+
- MacOS (main platform)
- Linux
- JDK 21
This tool is not supported by Cockroach Labs. Use of this tool is entirely at your own risk and Cockroach Labs makes no guarantees or warranties about its operation.
See MIT for terms and conditions.
Things you need to build and run Ledger locally.
- Java 21+ JDK
- Git
- CockroachDB Cloud or self-hosted 23.2+
- PostgreSQL 9+ (optional)
MacOS (using sdkman):
curl -s "https://get.sdkman.io" | bash
sdk list java
sdk install java 21.0 (pick version)
Ubuntu:
sudo apt-get install openjdk-21-jdk
See start a local cluster for setup instructions.
Then create the database, in this case assuming an insecure cluster:
cockroach sql --insecure -e "create database ledger"
An enterprise license is only needed when using geo-partitioning and follower-reads (opt-out features if n/a).
To set an enterprise license:
cockroach sql --insecure --host=localhost -e "SET CLUSTER SETTING cluster.organization = '...'; SET CLUSTER SETTING enterprise.license = '...';"
Install on MacOS using brew:
brew install postgresql
Start PSQL:
brew services start postgresql@14
Create the database:
psql postgres
$ CREATE database ledger;
$ CREATE extension pgcrypto;
$ quit
Stop PSQL:
brew services stop postgresql@14
Instructions for building the project locally.
git clone [email protected]:kai-niemi/ledger.git && cd ledger
chmod +x mvnw
./mvnw clean install
Ledger is one single server component (executable jar) now available undertarget/
.
All configuration properties can be specified in config/application-default.yml that overrides the baseline configuration in src/main/resources/application.yml (see this file for a reference on all options available).
You can also override all parameters through the command line, which is the easiest approach:
java -jar ledger.jar \
--spring.datasource.url="jdbc:postgresql://localhost:26257/ledger??ssl=true&sslmode=require" \
--spring.datasource.username=craig \
--spring.datasource.password=cockroach \
--spring.profiles.active="default"
Alternatively, you can create a new YAML file with a custom name suffix and then pass that
name with the --profiles
argument:
cp src/main/resources/application.yml config/application-craig.yml
java -jar ledger.jar --spring.profiles.active="craig"
Start the server with:
java -jar target/ledger.jar <args>
(or use ./run-server.sh
).
Now you can access the application via http://localhost:9090.
Add the shell commands you would like to run into a plain text file:
echo "version" > cmd.txt
echo "transfer-funds" >> cmd.txt
echo "read-balance" >> cmd.txt
Start the server in the background by passing a command file name
as argument with a @
prefix:
nohup java -jar target/ledger.jar @cmd.txt > ledger-stdout.log 2>&1 &
(or use ./start-server.sh @cmd.txt
)
The server will run all commands in the text file and then wait to be closed.
Notice that you can't use quit
in the end since all commands are run in sequence
but executed in parallel.
PostgreSQL is enabled by activating the psql
profile:
java -jar ledger.jar \
--spring.datasource.url="jdbc:postgresql://localhost:5432/ledger \
--spring.datasource.username=craig \
--spring.datasource.password=cockroach \
--spring.profiles.active="psql"
Ledger automatically adjusts to the dialect of PostgreSQL lack of multi-region and follower read features. There's also no concept of gateway nodes, or primary and secondary regions so you will need to specify regions on most workload commands.
Usage tutorial for running a basic demo or load test towards a single-region or single-host CockroachDB cluster.
Ledger is operated through its built-in shell, or by a command file passed at startup
time, in which case the shell is disabled (see running instructions).
For a complete list of commands, run the help
command in the shell.
There's also a reactive web UI available at http://localhost:9090 (by default) to display account activities and different metric charts.
Ledger organizes monetary accounts around cities within regions. A region maps to an
actual CockroachDB deployment region like aws-us-east-1
. One region has one or
more cities, and a city has a country and currency code. This allows for good data
distribution and simple data partitioning for multi-region scenarios.
By default it includes all CockroachDB Cloud regions for AWS, GCP and Azure. You can also create a custom region/city mapping through the application YAML.
Excerpt from src/resources/application.yml
:
...
application:
regions:
- name: us-east-1
country: USA
currency: USD
cities:
- name: new york
- name: boston
- name: washington dc
- name: miami
- name: charlotte
- name: atlanta
- name: us-east-2
country: USA
currency: USD
cities:
- name: st louis
- name: indianapolis
- name: nashville
- name: dallas
- name: houston
- name: detroit
...
region-mappings:
aws-us-east-1: us-east-1 #N. Virginia
aws-us-east-2: us-east-2 #Ohio
gcp-us-east1: us-east-1 #South Carolina
gcp-us-east4: us-east-2 #Virginia
...
The first step is to build an account plan:
build-account-plan
This command will create one liability account and 5,000 asset accounts per city, by default. The account plan is organized in such a way that the total balance of all accounts for a given city (and currency) amounts to zero. Thus, if a non-zero total is ever observed it means money has either been invented or destroyed and we can't have that *).
The account plan can be dropped and recreated which is a destructive operation since it truncates the tables.
*) You can allow this to happen by running in read committed
isolation without
pre-emptive locks.
An existing account plan unlocks the different workload commands, such as:
transfer-funds
This is the primary read-write workload that transfer funds between asset accounts selected by random. It is done by balanced, multi-legged transfers executed in parallel.
One transfer leg
equals one account balance update, thus the minimum number of
legs required is two. A balance update can hold a positive or negative value for simplicity,
rather than a credit or debit in real accounting (which is never negative).
By default, all workloads select the cities in the gateway node's region (local region to the client). You can however pick any region, or all of them, affectively starting a transfer workload for every city, like:
transfer-funds --region all
This workload is purely read-only and executes point lookups on accounts to retrieve the current authoritative balance.
read-balance
Another variant is to use a historical follower read, which, if you are familiar with CockroachDB, means that any node receiving a request hosting a range replica for the requested key can service the request, at the expense of the returned value being potentially stale with up to ~5s (called a bounded staleness read). Normally only the lease holder replica can service reads.
read-balance-historical
This is a write-only command to create new asset accounts in batches. It can be used to populate the database with more accounts than included in the account plan.
Notice that these accounts will have a zero balance and are not allowed to go negative.
To fund these accounts, you need to run the transfer-grants
command to grant funds
from liability accounts.
create-accounts
Similar to the transfer-funds
command, this one will move money from liability accounts to
asset accounts withing a specified balance range. It's useful to run this after
creating new accounts to allow these to become part of workload selections.
transfer-grants
A quick overview of the main workload commands and the type of SQL transactions they perform.
Workload | Reads | Writes | Explicit | Implicit | Locks | Duration |
---|---|---|---|---|---|---|
transfer-funds | 20% | 80% | x | x | 120m | |
transfer-grants | 10% | 90% | x | x | Finite | |
create-accounts | 100% | x | 120m | |||
read-balance | 100% | x | 120m | |||
read-balance-historical | 100& | x | 120m |
For the complete multi-region guide, see tutorial.
-- The end, enjoy your accounting experience!