AUTO_BATCH_MAX_SIZE transfers for the same party are collapsed into a single submission — significantly improving throughput under load.
Enabling
Auto-Batch is disabled by default (AUTO_BATCH_SWITCH=false). When disabled, no background loops run and the HTTP server behaves exactly as before this feature was introduced.
Database
The feature persists batch state in a relational store. Two backends are supported:in-memory (default)
An H2 database in PostgreSQL-compatibility mode. No extra infrastructure required. State does not survive a restart.
psql
A PostgreSQL instance for persistent, production-grade storage. Set
AUTO_BATCH_DB_TYPE=psql.The default is
in-memory for backward compatibility. Existing deployments can opt into auto-batching without provisioning a PostgreSQL instance.Batch lifecycle
AnOrder (a single transfer request submitted via the HTTP API) moves through two independent state machines:
| Entity | Terminal success | Terminal failure |
|---|---|---|
| Order | Dequeue (picked up by a batch) | (released back to Queued if batch fails terminally) |
| Batch | Confirmed | Failed with attempts ≥ maxAttempts |
Key invariants
attemptsis incremented before each submission attempt (atomically). Both synchronous errors (network, SDK) and asynchronous ledger rejections consume exactly one credit — no silent infinite retries.- Every
update()uses optimistic locking (keyed onbatch.updatedAt) so the three concurrent loops never corrupt each other’s state.
Background loops
BatchOrdersJob spawns three loops in parallel. All loops share the same exponential-backoff wrapper (1 s → 60 s, with jitter) that activates on unhandled errors, preventing cascading failures from hammering downstream services.
batchingLoop
batchingLoop
Prepares and submits one new batch per idle party per cycle. Also recovers
Prepared batches with attempts = 0 that were created before a restart.monitoringLoop
monitoringLoop
Polls the ledger for every in-flight batch (
Submitted or Accepted) and advances its state. Terminal outcomes (Confirmed, Failed) are written back; the retry loop handles Failed.retryingLoop
retryingLoop
Separates failed batches into two groups: those still within the retry budget are re-submitted; those that have exhausted
maxAttempts are marked terminal and their orders are released back to Queued.