Skip to content

Add RPC client pool for concurrent API requests#4499

Draft
utxo-detective wants to merge 1 commit into
ordinals:masterfrom
utxo-detective:rpc-client-pool
Draft

Add RPC client pool for concurrent API requests#4499
utxo-detective wants to merge 1 commit into
ordinals:masterfrom
utxo-detective:rpc-client-pool

Conversation

@utxo-detective

Copy link
Copy Markdown

Problem

The jsonrpc crate's SimpleHttpTransport uses a single TCP connection behind a Mutex:

// jsonrpc-0.18.0/src/http/simple_http.rs
pub struct SimpleHttpTransport {
    sock: Arc<Mutex<Option<BufReader<TcpStream>>>>,
    // ...
}

This serializes all Bitcoin Core RPC calls through one socket. Under concurrent API load, this creates a staircase latency pattern where each request adds the full RPC round-trip time to queue depth.

Benchmark: 20 concurrent /output requests

Request  1:  25ms
Request  2:  51ms
Request  3:  77ms
Request  4: 103ms
...
Request 20: 504ms

Each request individually takes ~25ms, but they queue linearly because they all contend on the same socket mutex. The 20th request waits 500ms.

With instrumentation showing all time is in the RPC call phase (0ms for redb reads):

get_output_info: total=25ms  list=0ms txout=25ms  inscriptions=0ms runes=0ms
get_output_info: total=50ms  list=0ms txout=50ms  inscriptions=0ms runes=0ms
get_output_info: total=78ms  list=0ms txout=78ms  inscriptions=0ms runes=0ms
...
get_output_info: total=504ms list=0ms txout=504ms inscriptions=0ms runes=0ms

Solution

Add a configurable pool of RPC clients (--bitcoin-rpc-pool-size, default 12, also configurable via ORD_BITCOIN_RPC_POOL_SIZE env var) so concurrent API requests each get their own TCP connection to Bitcoin Core, enabling true parallel RPC execution.

The primary client field is preserved for the indexer/updater which accesses it directly via index.client.

API-serving methods use self.rpc_client() which does round-robin selection from the pool.

Impact

With a pool size of N, up to N concurrent RPC calls can execute in parallel instead of serializing. For the benchmark above, 20 concurrent requests with a pool of 12+ would complete in ~50ms total instead of ~500ms.

The jsonrpc SimpleHttpTransport uses a single TCP connection behind a
Mutex, which serializes all Bitcoin Core RPC calls. Under concurrent
API load, this creates a staircase latency pattern where each request
adds the full RPC round-trip time to queue depth.

For example, with 20 concurrent /output requests each taking ~25ms
for their RPC call, the last request waits 500ms due to serial
queueing through the single socket.

This adds a configurable pool of RPC clients (--bitcoin-rpc-pool-size,
default 12) so concurrent API requests each get their own TCP connection
to Bitcoin Core, enabling true parallel RPC execution.

The primary `client` field is preserved for the indexer/updater which
accesses it directly via `index.client`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant