Documentation Index
Fetch the complete documentation index at: https://docs.formance.com/llms.txt
Use this file to discover all available pages before exploring further.
Scaling for writes
Writes contention
The Formance Ledger uses a multi-ledger, single-writer, sequential writes architecture to create an auditable, easy to reason about trail of transactions. The latest stable version of the ledger is optimised for 1K writes per second on an underlying commodity storage instance. As a result, write heavy applications above this threshold are advised to leverage the Formance Ledger’s segmentation capabilities to scale horizontally. This will allow you to create multiple ledgers and to write to them in parallel.PostgreSQL Row Locks
When posting transactions, you may experience decreased throughput if all transactions use the same source account (e.g., the defaultworld account). Even if the account allows overdraft (no balance enforcement), using the same source account repeatedly creates a bottleneck at the PostgreSQL level.
Every transaction involves writing changes to the accounts_volumes table. PostgreSQL applies row-level locks when updating balances, causing concurrent transactions targeting the same account to queue.
Solutions to Increase Throughput
Option 1: Spread Load Across Multiple Source Accounts
Distribute writes across multiple accounts instead of just one:- Use dynamic account identifiers like
@world:<random_id>where the suffix is randomly generated - Or use dedicated overdraft source accounts meaningful to your use-case, such as
@provider:<id>:payment:<id> - Set a reasonable pool size (e.g., 20) to balance between performance and account count
Option 2: Disable or Async Log Hashing
Log hashing uses a PostgreSQL advisory lock on the entire ledger, which can limit write throughput. To disable or make it asynchronous, create a ledger with the appropriate feature configuration:Performance Summary
| 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 |
Transaction commit cost breakdown
As outlined in the performance model, we consider the cost of a write to beO(N) + W. Let’s consider the following example:
@payments:1234 by $99.00, sourcing the funds from three user accounts before defaulting @world as the source of last resort. Let’s break down the operations that will be performed:
- Read the balance of
@users:1234:main - If the balance of
@users:1234:mainwas less than $99.00, read the balance of@users:1234:vouchers - If the balance of the previous account was less than $99.00, read the balance of
@users:1234:creditcard - Model the transaction
- Write the new balance of
@users:1234:main
O(1) + W and at most O(3) + W. This is because the transaction will need to read the balance of at least one account, and at most three accounts, and then commit the computed transaction to the ledger.
When optimizing for write throughput, it is important to keep in mind this cost breakdown and to design your transactions accordingly.
Scaling for reads
Chart of accounts
While usually not a concern for the scaling of writes, designing a proper chart of accounts is key to the performance of your reads. You’ll especially want to make sure that the addresses of your accounts are using properly separated segments, as this will allow you to use wildcards to query your accounts. An example of an inefficient account address would beusers:1234_main.
Implemented instead as users:1234:main, the latter will enable the ledger to efficiently query all accounts of a given user by using the users:1234:* wildcard or all accounts of all users of a given type by using the users:*:main wildcard.