Ledger
Ledger represents a realistic, 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. It is however a realistic
implementation of a general ledger with most of the fundamental pieces like account plans,
reporting and invariant checks.
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.
Compatibility
- CockroachDB v23.2+
- PostgreSQL 9+
- macOS (main platform)
- Linux
- JDK 21
Terms of Use
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.
Setup
Things you need to build and run Ledger locally.
Prerequisites
- Java 21+ JDK
- Git
- CockroachDB Cloud or self-hosted 23.2+
- PostgreSQL 9+ (optional)
Install the JDK
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
Database Setup
CockroachDB Setup
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 = '...';"
PostgreSQL Setup (optional)
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
Building
Instructions for building the project locally.
Clone the project
git clone git@github.com:kai-niemi/ledger.git && cd ledger
Build the artifact
chmod +x mvnw
./mvnw clean install
Ledger is one single server component (executable jar) now available undertarget/.
Configuration
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.
For example:
java -jar ledger.jar \
--spring.datasource.url="jdbc:postgresql://localhost:26257/ledger?ssl=true&sslmode=require&allow_unsafe_internals=true" \
--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 --profiles craig
Running
Run with an interactive shell
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.
Start in the background
Add the shell commands you would like to run into a plain text file:
echo "version" > cmd.txt
echo "workload start transfer-funds" >> cmd.txt
echo "workload start 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.
Use PostgreSQL
PostgreSQL as the underlying database is used 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 and 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 the region(s) to use on most
workload commands.
Single-region Tutorial
Usage tutorial for running a basic demo or load test towards a single-region
or single-host CockroachDB cluster.
Basics
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 charts.
Ledger binds monetary accounts to named cities within regions. A region maps to an
actual CockroachDB --locality region tier like aws-us-east-1. One region has one or
more cities with accounts, and each city has a country and currency code. This allows
for optimal data distribution and simple data partitioning for multi-region load
test scenarios.
The default configuration includes all current CockroachDB Cloud regions for AWS, GCP
and Azure. You can easily add custom region ot city mappings through a
config/application-<profile> YAML.
For example:
...
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
- name: us-west-1
country: USA
currency: USD
cities:
- name: san francisco
- name: los angeles
- name: san diego
- name: las vegas
- name: salt lake city
...
region-mappings:
aws-us-east-1: us-east-1
aws-us-east-2: us-east-2
aws-us-west-1: us-west-1
gcp-us-east1: us-east-1
gcp-us-east4: us-east-2
gcp-us-west1: us-west-1
...Ledger initially attempts to match the cluster's --locality region tier to an application
region name in application.regions.name. If no region is found, it checks the region-mappings
section for a matching key. If none is found there either, it logs a warning that the database
cluster's locality tiers doesnt match any application region. For a multi-region use case / demo,
its required to use at least 3 separate regions preferably with disjoint cities for best data
distribution.
For more guidance, see the Multi-region Tutorial.
Create an account plan
Ledger operates towards a pre-defined account plan, which is simply a collection of accounts of
different types with an initial balance and a total balance of zero per currency. Negative balances
are used to simplify the concepts within accounting called credit and debit, which are never of
negative value.
This command will create one liability account and 5,000 asset accounts per city, by default:
db build accountplan
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 balance is ever observed it means
money has either been invented or destroyed and we can't have that. Under serializable (default)
isolation this is impossible. You can however allow such anomalies just for fun by running
in read committed isolation without locks. That would make this ledger exposed to P4
lost updates with non-zero balance sums as a result.
Transfer funds
This is a read-write intensive workload that transfer funds between accounts picked by random.
workload start transfer-funds
One transfer leg equals to one account balance update, thus the minimum number of
transfer legs is two. A balance update can hold a positive or negative value for simplicity,
rather than a credit or debit in real accounting. 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:
workload start transfer-funds --region all
Read balance
This is a purely read-only command that executes point lookups on accounts to retrieve the current balance.
workload start read-balance
Another variant is to use historical follower reads, 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 which
are authoritative.
workload start read-balance --stale true
Create accounts
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.
workload start create-accounts
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 workload start transfer-grants command to grant funds
from liability accounts.
Transfer grants
This command 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.
workload start transfer-grants
Workload Overview
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 |
Multi-region Tutorial
For the complete multi-region guide, see tutorial.
-- That is all, enjoy your accounting experience!


