Skip to main content
Auto-Batch is an optional background feature that automatically aggregates pending transfer orders into batched ledger submissions. Instead of one interactive transaction per transfer, up to 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.
AUTO_BATCH_SWITCH=true

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.
The database is initialised on every startup (schema migrations apply automatically). In in-memory mode the store is empty after each restart — any in-flight batches from a prior run are not recovered.

Batch lifecycle

An Order (a single transfer request submitted via the HTTP API) moves through two independent state machines:
EntityTerminal successTerminal failure
OrderDequeue (picked up by a batch)(released back to Queued if batch fails terminally)
BatchConfirmedFailed with attempts ≥ maxAttempts

Key invariants

  • attempts is 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 on batch.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.
Prepares and submits one new batch per idle party per cycle. Also recovers Prepared batches with attempts = 0 that were created before a restart.
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.
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.
Tune batch and retry timings via the AUTO_BATCH_* environment variables. See Configuration.