Locking & Performances

Last updated: November 12, 2025

Understanding PostgreSQL Locks and Consistency in the Formance Ledger

When working with the Formance Ledger—especially at scale—it's important to understand how PostgreSQL locking mechanisms interact with the way ledger transactions are recorded. This article explains what causes throughput limitations, how to mitigate them, and how PostgreSQL ensures data consistency even under high concurrency.

The Context: Throughput Degradation When Using a Single Source Account

When posting large numbers of transactions using the /transactions endpoint, you may experience a decrease throughput.

Common setup in these scenarios:

  • All transactions were using the same source account (e.g., the default world account or overdraft account).

  • Hence, balance checks were disabled.

Even if the account allows overdraft (i.e., no balance enforcement), using the same source account repeatedly creates a bottleneck at the PostgreSQL level.


Why Is This Happening?

In Formance, every transaction involves writing changes to the accounts_volumes table. Even if your account has overdraft enabled (i.e., no balance check)

→ PostgreSQL applies row-level locks when updating balances.

For further details: see📄 PostgreSQL Row Locks in Formance


Solutions to Increase Throughput

Option 1: Spread Load Across Multiple Source Accounts

A workaround is to distribute writes across multiple accounts instead of just one.

How you can implement this:

  • Use dynamic account identifiers like @world:<random_id> where the suffix is randomly generated OR use dedicated overdraft source account meaningful to your use-case, such as @provider:<id>:payment:<id>

  • In case you are using a random id, set a reasonable pool size (e.g., 20) to balance between performance and account count.

This reduces contention on a single row and leverages PostgreSQL’s ability to parallelize updates across multiple rows.

For further details: see 📄 Considerations When Scaling with Multiple Source Accounts


Option 2: ASYNC / Disable Log Hashing for Write Performance

This time we aren't locking a particular row or table but the entire ledger for the purpose of the hash, we use a PostgreSQL advisory lock.

How you can disable it:

If you don’t require the tamper-evidence property, create ledger with feature "HASH_LOGS": "DISABLED”

Note: you can also make this process asynchronous by setting

"HASH_LOGS": "ASYNC”

Benefits:

  • async lock / No advisory lock is taken.

  • You’ll get significantly higher write throughput.

Tradeoff: If disabled, you lose the ability to cryptographically prove that the logs have not been altered or injected manually.

For further details: see 📄 Log Hash feature and Advisory Locks


Additional Considerations for Performance at Scale

Would using many different assets to “group” by characteristics be a bad idea performance-wise?

  • Formance tracks balances per account and per asset.

  • PostgreSQL row-level locks apply per (account, asset) pair in the accounts_volumes table.

  • Therefore, if you write to different assets—even on the same account—you avoid row-level lock contention entirely.

Using different assets strategically can be an effective tool for reducing write contention and increasing throughput.

Would having thousands (or even tens of thousands) of postings in a single transaction be problematic?

  • From a purely write-time point of view, large transactions can perform well, often comparably (or better) than many small ones.

  • However, very large transactions:

    • Consume more memory temporarily.

    • Increase latency for the full commit (it’s all-or-nothing).

    • Can amplify the impact of a failure (if one posting fails, all are rolled back).

PostgreSQL Consistency Model: COMMIT or ROLLBACK

PostgreSQL, the underlying engine of the Formance Ledger, operates with strict transactional guarantees.

Every ledger transaction is wrapped in a PostgreSQL transaction. This means:

  • All-or-nothing: Either the full ledger write is committed, or it's rolled back entirely.

  • No partial state: If a transaction fails or is interrupted, no intermediate state is saved.

  • Error feedback: Any failure during a transaction results in an error from the Ledger API.

This ensures that the Ledger always remains in a consistent state, regardless of whether log hashing is enabled.

For further details: see 📄 PostgreSQL Consistency Model: COMMIT or ROLLBACK


Summary: Key Takeaways

Concern

Description

PostgreSQL locking bottleneck

Happens on balance updates; causes queuing if same row (source account) is updated concurrently.

Mitigation

Use many source accounts (@world:<random_id>).

Log Hashing

Ensures log immutability, but adds overhead. Can be safely disabled if not needed.

Consistency

Guaranteed by PostgreSQL transactional semantics (COMMIT/ROLLBACK).

Assets as parallelization tool

Locks are per (account, asset) → spreading across assets avoids contention.


If you have questions about optimizing ledger performance for your specific workload, feel free to reach out to our support team.