DEV Community

...
...

Posted on Edited on AI-assisted

pledge: a fast double-entry ledger inside PostgreSQL, ready for Django

pledge is a double-entry ledger that runs inside PostgreSQL, with a Django package on top. If your app moves money, credits or points, it keeps the books for you:

  • Balanced by design. Every transfer writes two entries that sum to zero.
  • History built in. Each entry stores the running balance, so you can ask for any past balance.
  • Safe retries. Send your own transfer id; the same transfer twice is booked once.
  • Tamper-resistant. Ledger rows can't be deleted or edited; accounts are closed, never removed.
  • Fast. Written in Rust, one SQL statement per transfer or batch.

Django first

With django-pledge the ledger looks like ordinary Django models:

pip install django-pledge
Enter fullscreen mode Exit fullscreen mode
INSTALLED_APPS = [..., "django_pledge"]
Enter fullscreen mode Exit fullscreen mode
from decimal import Decimal
from django_pledge.models import Account, Transfer

cash = Account.objects.create(name="cash", currency="EUR")
alice = Account.objects.create(name="alice", currency="EUR", allow_negative_balance=False)

cash.transfer(alice, Decimal("10.00"))
Transfer.objects.bulk_create([
    Transfer(from_account=alice, to_account=cash, amount=Decimal("2.50")),
    Transfer(from_account=cash, to_account=alice, amount=1),
])  # one atomic batch, one statement

alice.refresh_from_db()
alice.balance          # Decimal('8.50')
alice.history()        # every entry, with its running balance
Enter fullscreen mode Exit fullscreen mode

Overdraw Alice's account and you get a typed exception, NegativeBalanceNotAllowed, with the account id attached. Store a TransferId with your own record before you call the ledger, and a retry books nothing twice. Link your own models with a normal ForeignKey to pledge.Account.

Or plain SQL

Django is optional. Everything is a function in the pledge schema:

CREATE EXTENSION pledge;

SELECT id FROM pledge.create_account('bank', 'USD');
SELECT id FROM pledge.create_account('alice', 'USD', allow_negative_balance => false);

SELECT * FROM pledge.create_transfer('pla_…bank', 'pla_…alice', 100.00);

SELECT name, balance FROM pledge.accounts_view;
SELECT * FROM pledge.verify();   -- every check should say ok
Enter fullscreen mode Exit fullscreen mode

Fast, and checked

pledge grew out of pgledger by pgr0ss, and we still benchmark against it:

pledge is about 1.5 times faster per transfer and about 2 times faster for batches. Speed only counts if the books stay straight, so pledge also passes a bank test where connections are killed mid-transfer, a model-based test, and a replay of a real bank's million transactions.

It's a young project, so the API may still change before 1.0. It runs on PostgreSQL 17 and 18.

Try it

Clone the repo and run just example to click through the exchange in your browser. Feedback and issues are very welcome.

Top comments (0)