Understanding PostgreSQL Schemas
At a technical level, a bucket in Formance is directly mapped to a PostgreSQL schema. In PostgreSQL, a schema is a namespace that groups together database objects (tables, views, indexes, functions):- Without schemas: All tables would live in one big global namespace, which could quickly become messy
- With schemas: You can organize and separate data logically, avoid name collisions, and apply different permissions
How Formance Uses Schemas
When you create a ledger, Formance stores its data inside a PostgreSQL schema (the bucket). This has several implications:| Aspect | Description |
|---|---|
| Isolation | Each bucket/schema provides clean separation of ledger data. Two ledgers in different buckets won’t share tables, reducing risk of accidental data mixing. |
| Shared storage within a bucket | Multiple ledgers inside the same bucket share the same underlying tables, which can be efficient but means their data is less isolated. |
| Scaling strategy | By using multiple buckets, you can spread data across different schemas to avoid a single schema growing too large and becoming a performance bottleneck. |
System Schema
Formance uses a special internal schema called_system to track metadata about your ledgers and buckets.
Practical Considerations
- Data Isolation: Buckets are a good fit if you need strong separation (e.g., per-tenant ledgers in a multi-tenant system)
- Performance Management: For high-volume workloads, spreading ledgers across multiple buckets can reduce contention and keep queries fast
- Simplicity: If isolation isn’t critical, sticking with the
_defaultbucket keeps things straightforward
Creating a bucket
Buckets are automatically created when you create a new ledger. By default, if the bucket is not specified, the ledger is created in the_default bucket.
Using fctl, you can specify the bucket when creating a new ledger:
Features
Each ledger can be configured with a set of features at creation time. Features let you tune ledger behavior for different use cases (high write throughput, full audit trail, and so on). When you create a ledger without specifying features, all features are enabled with their default values. You only need to specify features you want to override; unspecified features receive their defaults.Features summary
| Feature | Possible values | Default | Description |
|---|---|---|---|
| MOVES_HISTORY | ON, OFF | ON | Track individual fund movements per account/asset |
| MOVES_HISTORY_POST_COMMIT_EFFECTIVE_VOLUMES | SYNC, DISABLED | SYNC | Maintain effective volumes for backdated transactions |
| HASH_LOGS | SYNC, ASYNC, DISABLED | SYNC | Hash logs for integrity verification |
| ACCOUNT_METADATA_HISTORY | SYNC, DISABLED | SYNC | Historize account metadata changes |
| TRANSACTION_METADATA_HISTORY | SYNC, DISABLED | SYNC | Historize transaction metadata changes |
MOVES_HISTORY
Values:ON | OFFDefault:
ON
When enabled, the ledger tracks every individual fund movement for each account/asset pair in the moves table. Each move record includes account address, asset, amount, source/destination flag, insertion date, effective date, and post-commit volumes.
When enabled (ON): Full balance history at any point in time, historical balance queries, point-in-time support. Required for effective volumes calculation.
When disabled (OFF): Only current balances are available; better write performance and lower storage.
MOVES_HISTORY_POST_COMMIT_EFFECTIVE_VOLUMES
Values:SYNC | DISABLEDDefault:
SYNC
Maintains the post_commit_effective_volumes column in moves, which tracks volumes ordered by effective date (not insertion date). When you create backdated transactions, effective volumes for later moves are updated automatically.
Depends on MOVES_HISTORY being ON. If MOVES_HISTORY is OFF, this feature has no effect.
postCommitEffectiveVolumes. Accurate historical balance queries and correct handling of backdated transactions.
When disabled (DISABLED): The postCommitEffectiveVolumes property is not present on transaction responses. Better write performance when transactions are always inserted in chronological order.
HASH_LOGS
Values:SYNC | ASYNC | DISABLEDDefault:
SYNC
Provides cryptographic integrity verification for the ledger log chain. Each log entry is hashed (SHA-256) and chained to the previous log’s hash.
- SYNC: Hashes are computed synchronously during each log insert. Hash is immediately available; only one log can be inserted at a time per ledger (advisory lock). Best for strong integrity guarantees with lower throughput.
- ASYNC: Logs are inserted without a hash; a background worker computes hashes in batches (blocks). No locking on insert, so higher write throughput. Requires the ledger worker process and configuration (e.g.
--worker-async-block-hasher-max-block-size,--worker-async-block-hasher-schedule). - DISABLED: No hashing. Maximum write performance; no integrity verification.
ACCOUNT_METADATA_HISTORY
Values:SYNC | DISABLEDDefault:
SYNC
When enabled, every change to account metadata is historized with revision tracking. You can query account metadata at any point in time using the pit parameter. When disabled, only current metadata is stored and PIT queries return current values.
TRANSACTION_METADATA_HISTORY
Values:SYNC | DISABLEDDefault:
SYNC
When enabled, every change to transaction metadata is historized. You can query transaction metadata at any point in time using the pit parameter. When disabled, only current metadata is stored and PIT queries return current values.
Setting features at ledger creation
UsePOST /v2/{ledger} and pass features in the request body. Only specify features you want to override.
Request body:
Feature sets
Default (full audit): All features enabled. Use when you need full audit capabilities. Minimal (performance): All features disabled or minimized. Use for high-throughput scenarios where an audit trail is not required. Async hashing (balanced):HASH_LOGS: "ASYNC" with other features at defaults. Use for higher throughput with eventual integrity verification (requires the ledger worker).
Feature impact summary
| Feature | When enabled | When disabled |
|---|---|---|
| MOVES_HISTORY | Full balance history available | Only current balances available |
| MOVES_HISTORY_POST_COMMIT_EFFECTIVE_VOLUMES | postCommitEffectiveVolumes on transactions | postCommitEffectiveVolumes not available |
| HASH_LOGS | Logs cryptographically chained | No hash verification |
| ACCOUNT_METADATA_HISTORY | Full metadata revision history | Only current metadata stored |
| TRANSACTION_METADATA_HISTORY | Full metadata revision history | Only current metadata stored |
ASYNC hashing: If you use
HASH_LOGS: "ASYNC", you must run the ledger worker process with the appropriate configuration. Dependencies: MOVES_HISTORY_POST_COMMIT_EFFECTIVE_VOLUMES depends on MOVES_HISTORY. There is a trade-off between write performance and audit capabilities; choose based on your use case.Deleting and restoring buckets
You can delete buckets and restore them within a retention period if needed.Deleting a bucket
- All ledgers in the bucket become hidden from normal queries
- The data is preserved for 30 days (configurable)
- You can restore the bucket during this period
Restoring a bucket
If you deleted a bucket by mistake, restore it before the retention period ends:Listing deleted ledgers
To see deleted ledgers, addincludeDeleted=true to your query:
deletedAt timestamp in the response:
Configuring retention
By default, deleted buckets are permanently removed after 30 days. To change this, configure the worker:| Flag | Description | Default |
|---|---|---|
--worker-bucket-cleanup-retention-period | How long to keep deleted buckets | 30 days (720h) |
--worker-bucket-cleanup-schedule | How often to check for expired buckets (cron format) | Every hour |