-
Notifications
You must be signed in to change notification settings - Fork 3
feat(ledger): add v3 reconciler with Raft StatefulSet support #434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9ca91be
dd00278
dd2cca4
24a53f1
a9652ec
ab59f2b
8a63607
ce73ffe
99ad9c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,3 +28,6 @@ Dockerfile.cross | |
| charts | ||
|
|
||
| *.tgz | ||
| *.tar.gz | ||
|
|
||
| dist | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,3 +100,148 @@ Available fields: | |
| - `push-retry-period`: Retry period for failed pushes | ||
| - `sync-period`: Synchronization period | ||
| - `logs-page-size`: Number of logs per page | ||
|
|
||
| ## Ledger v3 Mirror | ||
|
|
||
| Ledger v3 can run alongside an existing v2 deployment as a **mirror**: it continuously replicates v2 ledger data into its own Raft-based storage. This allows gradual migration or read-offloading without disrupting v2. | ||
|
|
||
| When the `modules.ledger.v3-mirror` setting is present, the operator: | ||
| 1. Deploys v2 normally (database, migrations, Deployment) | ||
| 2. Deploys a v3 Raft StatefulSet in parallel | ||
| 3. Runs a provisioning Job that creates mirror ledgers in v3, each sourcing data from the v2 PostgreSQL database | ||
|
|
||
| ### Enabling v3 Mirror | ||
|
|
||
| Create a Settings resource with the key `modules.ledger.v3-mirror`. The value format is: | ||
|
|
||
| ``` | ||
| <v3-image-tag>:<ledger1>,<ledger2>,... | ||
| ``` | ||
|
Comment on lines
+117
to
+119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add language specifier to fenced code block. The fenced code block should have a language specified per markdownlint MD040. Use -```
+```text
<v3-image-tag>:<ledger1>,<ledger2>,...🧰 Tools🪛 markdownlint-cli2 (0.22.0)[warning] 117-117: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
|
|
||
| - **v3-image-tag**: The container image tag of the ledger v3 binary (e.g. `v3.0.0-alpha.1`) | ||
| - **ledger names**: Comma-separated list of v2 ledger names to mirror | ||
|
|
||
| ```yaml | ||
| apiVersion: formance.com/v1beta1 | ||
| kind: Settings | ||
| metadata: | ||
| name: ledger-v3-mirror | ||
| spec: | ||
| stacks: ["my-stack"] | ||
| key: modules.ledger.v3-mirror | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| value: "v3.0.0-alpha.1:default,payments" | ||
| ``` | ||
|
|
||
| This example deploys a v3 cluster using image tag `v3.0.0-alpha.1` and creates two mirror ledgers (`default` and `payments`) that replicate from the v2 PostgreSQL database. | ||
|
|
||
| ### How It Works | ||
|
|
||
| The provisioning Job connects to the v3 cluster's gRPC endpoint and calls `ledgerctl ledgers create` for each listed ledger with: | ||
| - `--mode mirror` — marks the ledger as a mirror (read-only, no direct writes) | ||
| - `--mirror-source-type postgres` — uses direct PostgreSQL access for replication | ||
| - `--mirror-dsn` — the PostgreSQL DSN of the v2 database (derived automatically from the Database resource) | ||
|
|
||
| The Job is idempotent: if a mirror ledger already exists, the error is ignored. It retries on failure (e.g. if the v3 cluster is not yet ready). | ||
|
|
||
| ### Architecture | ||
|
|
||
| The operator creates the following resources for v3: | ||
|
|
||
| | Resource | Purpose | | ||
| |----------|---------| | ||
| | `StatefulSet/ledger` | Raft cluster nodes with `OrderedReady` pod management | | ||
| | `Service/ledger-raft` (headless) | DNS-based peer discovery for Raft consensus | | ||
| | `Job/v3-mirror-provision` | Creates mirror ledgers in the v3 cluster | | ||
| | 3 PVCs per pod | `wal`, `data`, `cold-cache` | | ||
|
|
||
| ### Requirements | ||
|
|
||
| Ledger v3 does **not** require its own PostgreSQL or message broker. Storage is fully embedded (Pebble LSM). However, the v3 pods need network access to the v2 PostgreSQL database for mirror replication. | ||
|
|
||
| ### Cluster Settings | ||
|
|
||
| ```yaml | ||
| apiVersion: formance.com/v1beta1 | ||
| kind: Settings | ||
| metadata: | ||
| name: ledger-v3-replicas | ||
| spec: | ||
| stacks: ["*"] | ||
| key: module.ledger.v3.replicas | ||
| value: "3" | ||
| ``` | ||
|
|
||
| - `module.ledger.v3.replicas`: Number of Raft nodes. **Must be odd** for quorum (default: 3). | ||
|
|
||
| The Raft cluster ID is automatically set to the stack name. | ||
|
|
||
| ### Persistence Settings | ||
|
|
||
| Each pod gets three PVCs. Size and storage class are configurable: | ||
|
|
||
| ```yaml | ||
| apiVersion: formance.com/v1beta1 | ||
| kind: Settings | ||
| metadata: | ||
| name: ledger-v3-persistence | ||
| spec: | ||
| stacks: ["*"] | ||
| key: module.ledger.v3.persistence.wal.size | ||
| value: "5Gi" | ||
| --- | ||
| apiVersion: formance.com/v1beta1 | ||
| kind: Settings | ||
| metadata: | ||
| name: ledger-v3-data-size | ||
| spec: | ||
| stacks: ["*"] | ||
| key: module.ledger.v3.persistence.data.size | ||
| value: "10Gi" | ||
| --- | ||
| apiVersion: formance.com/v1beta1 | ||
| kind: Settings | ||
| metadata: | ||
| name: ledger-v3-cold-cache-size | ||
| spec: | ||
| stacks: ["*"] | ||
| key: module.ledger.v3.persistence.cold-cache.size | ||
| value: "10Gi" | ||
| ``` | ||
|
|
||
| | Key | Default | Description | | ||
| |-----|---------|-------------| | ||
| | `module.ledger.v3.persistence.wal.size` | 5Gi | WAL PVC size | | ||
| | `module.ledger.v3.persistence.wal.storage-class` | (cluster default) | WAL storage class | | ||
| | `module.ledger.v3.persistence.data.size` | 10Gi | Pebble data PVC size | | ||
| | `module.ledger.v3.persistence.data.storage-class` | (cluster default) | Data storage class | | ||
| | `module.ledger.v3.persistence.cold-cache.size` | 10Gi | Cold cache PVC size | | ||
| | `module.ledger.v3.persistence.cold-cache.storage-class` | (cluster default) | Cold cache storage class | | ||
|
|
||
| ### Pebble Tunables | ||
|
|
||
| All Pebble settings are optional. When unset, the ledger binary defaults apply. | ||
|
|
||
| | Key | Example | Description | | ||
| |-----|---------|-------------| | ||
| | `module.ledger.v3.pebble.cache-size` | 1073741824 | Block cache size in bytes | | ||
| | `module.ledger.v3.pebble.memtable-size` | 268435456 | Memtable size in bytes | | ||
| | `module.ledger.v3.pebble.memtable-stop-writes-threshold` | 2 | Memtable count before stopping writes | | ||
| | `module.ledger.v3.pebble.l0-compaction-threshold` | 4 | L0 files to trigger compaction | | ||
| | `module.ledger.v3.pebble.l0-stop-writes-threshold` | 12 | L0 files before stopping writes | | ||
| | `module.ledger.v3.pebble.lbase-max-bytes` | 67108864 | L1 max size in bytes | | ||
| | `module.ledger.v3.pebble.target-file-size` | 67108864 | SST file target size | | ||
| | `module.ledger.v3.pebble.max-concurrent-compactions` | 2 | Compaction parallelism | | ||
|
|
||
| ### Raft Tunables | ||
|
|
||
| All Raft settings are optional. When unset, the ledger binary defaults apply. | ||
|
|
||
| | Key | Example | Description | | ||
| |-----|---------|-------------| | ||
| | `module.ledger.v3.raft.snapshot-threshold` | 5000 | Log entries before snapshot | | ||
| | `module.ledger.v3.raft.election-tick` | 10 | Election timeout in ticks | | ||
| | `module.ledger.v3.raft.heartbeat-tick` | 1 | Heartbeat interval in ticks | | ||
| | `module.ledger.v3.raft.tick-interval` | 100ms | Duration of one tick | | ||
| | `module.ledger.v3.raft.max-size-per-msg` | 1048576 | Max message size in bytes | | ||
| | `module.ledger.v3.raft.max-inflight-msgs` | 256 | Max in-flight messages | | ||
| | `module.ledger.v3.raft.compaction-margin` | 1000 | Log retention after snapshot | | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure
${GOPATH}/binexists before writing the binary.Line 103 can fail on clean environments where
${GOPATH}/binis missing.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents