Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Benchmarks

rflow's performance claim is architectural: event decode β†’ expression eval β†’ relayer hand-off are in-process function calls β€” no HTTP hop, no serialization on the hot path. This page puts numbers on that, and is precise about what was measured and what was not.

Headline numbers

The engine-side hot path β€” event claimed β†’ transaction queued in the embedded relayer β€” measured over 30 event-triggered sends:

Metricp50p95max
Claim β†’ tx queued (simulate + estimate gas + policy checks + relayer hand-off)25.1 ms34.3 ms39.2 ms
Claim β†’ run settled (full run, single send step, wait_for: none)28.1 ms37.3 msβ€”
Event's block timestamp β†’ claim (detection, polling a 1s-block chain)0.83 s0.91 s0.92 s

Those ~25 ms include real work, all of it journaled: the exactly-once claim transaction in Postgres, template evaluation, a pre-flight eth_call simulation, an eth_estimateGas, policy/gas-cap checks, the pre-send journal write with the idempotency key, and the in-process relayer queue insert.

Methodology β€” reproduce it yourself

  • Setup: a release build of rflow (v0.1.0, cargo build --release), a local anvil chain (--block-time 1, chain id 31337) and the repo's compose Postgres, all on one machine.
  • Workflow: an ERC20 Transfer event trigger with a where: filter β†’ one send_transaction step (wait_for: none, simulation on β€” the default). Essentially the token-transfer-relay example minus the balance-gate read.
  • Load: 30 qualifying deposits fired via cast send; every one produced exactly one run (n=30, all succeeded, zero duplicates).
  • Measurement: timestamps are the engine's own journal β€” workflow_runs.created_at (the claim) to step_runs.finished_at of the send step, which with wait_for: none settles at the relayer queue ack. Percentiles computed in SQL over the journal; detection latency compares the event's block timestamp to the claim time.
  • Hardware: Apple M5 Max, 128 GB RAM (a development laptop, not a tuned server).
  • In-repo harness: cargo run -p rflow_e2e_tests -- --bench runs a self-contained variant (20 sequential deposits, instant-mining anvil, unoptimized dev build) and writes bench-results.json with every sample, the full methodology, and on-chain receipt verification for each tx. Its numbers (~32 ms p50 claim β†’ send settled on a dev build) are consistent with the release-build table above.

Honest caveats

  • Local RPC. anvil answers eth_call/eth_estimateGas in microseconds; a real provider adds its network round-trips to the ~25 ms (two RPC calls sit inside the measured window). The number isolates rflow's overhead β€” it is the part of the stack rflow can promise.
  • Detection is poll-bound in this setup. The 0.83 s p50 from block timestamp to claim reflects the indexer's block polling against a 1-second chain, and block timestamps have 1-second granularity. Tightening block_poll_frequency (networks config) shrinks it; on real chains the block interval (12 s on mainnet) dwarfs this component entirely. (ws: is parsed but not wired to the engines yet, so it does not help here.)
  • Queued, not confirmed. The relayer owns everything after the queue β€” broadcast, gas bidding, inclusion. End-to-end "event β†’ CONFIRMED" is chain-time dominated (block interval Γ— your confirmations depth) and would say nothing about the engine.
  • One machine, one process, modest n. n=30 on a dev laptop is a smoke-level benchmark for the hot path, not a load test. Concurrency limits (max_concurrent_runs, group lanes) were not stressed here.

Why no competitor comparison table?

The obvious candidates (Defender, Tenderly Web3 Actions, Gelato) are hosted services in a different category: their event→action latency includes their detection infrastructure, queueing and multi-tenant scheduling, none of which is publicly benchmarkable in a controlled way — numbers we could publish would be unfair in one direction or the other. The architectural difference stands on its own: rflow's trigger-to-relayer path is a function call inside one process on your hardware, plus two RPC round-trips to your provider.

Where throughput actually goes

For capacity planning, the bottlenecks in practice, in order:

  1. Your RPC provider β€” simulation + gas estimation are two calls per send; backfills are eth_getLogs-bound (max_block_range, CU budgets).
  2. Postgres β€” every claim, step and settle is a journaled write. Give it real storage; it is the durability you are paying for.
  3. The chain itself β€” inclusion and confirmation depth; rflow just waits well (durably parked, not spinning).

See Self-hosting β†’ Sizing.