Skip to main content

IORPC Protocol for Distributed Cache

Starting from JuiceFS Enterprise Edition 5.2, IORPC provides an RPC framework for data transfer between cache group peers. TCP Pool is the default communication mode and is suitable for most scenarios. IORPC should only be used when excessive connections cause performance degradation — IORPC's connection multiplexing can reduce CPU overhead from goroutine scheduling under such conditions. IORPC is disabled by default; contact Juicedata technical support to evaluate feasibility before enabling it via --use-iorpc.

IORPC vs. TCP Pool

The distributed cache system in JuiceFS supports two network communication modes for peer-to-peer data transfer:

FeatureIORPCTCP Pool
MultiplexingOne connection handles multiple concurrent requestsEach connection handles one request at a time
I/O path (default)Direct I/O read + write to socket (two user-space copies per request)sendfile (zero-copy via kernel)
Connection countRequires fewer connections under high concurrencyRequires maintaining a large number of connections under high concurrency
TransportTCP (default) or RDMATCP only
Cache read modesdio (default), pagecache, splicesendfile (default) or dio (with --cache-try-dio)
Use caseWhen excessive connections cause performance degradationMost scenarios

TCP Pool is the legacy mode available since earlier versions and remains the default mode in all versions. It uses sendfile for zero-copy data transfer from cache disk to network by default.

IORPC supports multiplexing: a single connection can process multiple requests concurrently over TCP, significantly reducing the number of connections needed in high-concurrency scenarios. In addition, IORPC can work with RDMA to achieve higher throughput and lower CPU utilization.

Enabling IORPC

IORPC has two independent components: the server and the client. Both must be enabled for IORPC communication to take effect.

Server side

The IORPC server runs on cache group member nodes (that is, nodes mounted without --no-sharing). It is enabled by default starting from 5.2.

To disable the IORPC server on a node, use --iorpc-mode=disable.

Client side

The IORPC client is disabled by default. To enable it on any JuiceFS client (including --no-sharing nodes), use --use-iorpc.

IORPC and RDMA

When you set --rdma-network, IORPC is automatically enabled on both the server and client sides, using RDMA transport. You do not need to set --use-iorpc separately. See Distributed Cache RDMA Acceleration for details.

IORPC cache access modes

The --iorpc-mode option controls how the IORPC server reads cache data from disk. This setting applies to cache group member nodes only.

ModeDescription
dio (default)Direct I/O. Bypasses the kernel page cache. Reduces memory copies and system memory usage.
pagecacheBuffered read through the kernel page cache. Uses the standard file system read path.
spliceZero-copy mode. Uses a kernel pipe (splice/vmsplice) to transfer data directly between disk and network without user-space copying. Requires system pipe max size ≥ 8 MiB. Automatically falls back to dio when RDMA is enabled.
disableDisables the IORPC server on this node. Cache requests will be served via TCP Pool instead.

Configuring splice mode

To use the splice mode, set --iorpc-mode=splice.

Before enabling splice, increase the system's maximum pipe size to at least 8 MiB:

echo 8388608 > /proc/sys/fs/pipe-max-size

Check the current pipe max size with:

sysctl fs.pipe-max-size

If the pipe size is less than 8 MiB or RDMA is enabled, the server automatically falls back to dio mode and logs a warning.

Connection management

IORPC automatically adjusts the number of connections based on bandwidth and QPS, evaluated every second for adding connections and every minute for removing connections. TCP connections are added when bandwidth exceeds 300 MiB/s or QPS exceeds 1,000, and removed when both bandwidth and QPS fall below 100 MiB/s and 500 respectively. RDMA connections use higher thresholds for both scaling up and down (the increase threshold is 1 GiB/s or 5,000 QPS, and the decrease threshold is 512 MiB/s and 2,000 QPS). At least one connection is always kept.

Verifying IORPC

On the server side

Check the JuiceFS client log on cache group member nodes. The log line indicates the current IORPC mode and TCP pool mode:

max pipe size: 8388608, iorpc: dio, tcp pool: sendfile

The iorpc field may show:

  • dio: Direct I/O (default)
  • pagecache: Buffered read
  • splice: Zero-copy mode
  • disable: IORPC server disabled

The tcp pool field may show:

  • sendfile: Default zero-copy mode
  • dio: Direct I/O enabled (via --cache-try-dio)

On the client side

When IORPC is successfully established between a client and a cache server, the client log shows:

iorpc client started, server address X.X.X.X:X

Where X.X.X.X:X is the IP address and port of the connected cache server.

Monitoring

Key log messages

LogSourceDescription
max pipe size: ..., iorpc: dio, tcp pool: sendfileServer startupDisplays the current IORPC mode and TCP Pool disk read method. See Verifying on the server for field values.
iorpc client started, server X.X.X.X:XClientIORPC connection successfully established with the cache server.
get iorpc address from ... failed: rpc disabledClientThe peer does not support IORPC (older version), so it falls back to TCP Pool.
splice iorpc is disabled since max pipe size is ... or RDMA is enabled, use dio insteadServerThe conditions for splice are not met, so it automatically falls back to dio.
iorpc-mode should be one of ..., but got ..., set to dioServerAn invalid --iorpc-mode value is specified, so it falls back to the default dio.

Prometheus metrics

The following are core IORPC metrics with the mount_ prefix. Each latency metric provides _tp50, _tp90, _tp99 quantiles and _max.

MetricDescription
mount_iorpc_processRespDurServer-side request processing latency (from receiving the request to serializing the response).
mount_iorpc_writeBodyDurLatency of writing the response body to the network. Monitor this metric when debugging network congestion.
mount_iorpc_decodeBodyDurLatency of decoding request/response bodies.
mount_iorpc_inflightCurrent number of concurrent IORPC requests being processed by the server. When reaching the default concurrency limit of 2,048, new requests will queue and wait until a slot is freed up, which may cause increased latency and client-side timeouts. Consider scaling out the cache group when this metric remains near 2,048.

juicefs stats real-time output

Use juicefs stats with the r schema character and -l 1 to view IORPC internal latencies in real time:

juicefs stats --schema=ufmcro -l 1 /jfs

In the remotecache section, three additional columns appear:

  • db_c/lat: Number of decode operations for request / response bodies and average latency.
  • pr_c/lat: Number of RPC request processing operations on the server and average latency.
  • wb_c/lat: Number of response body write operations to the network and average latency.

Without -l 1, the r schema only shows application-level remotecache metrics. These IORPC columns always appear with -l 1 but only contain actual values when IORPC is enabled.

Tuning recommendations

When to use IORPC

note

Contact Juicedata technical support to evaluate feasibility before enabling IORPC.

When excessive TCP connections cause performance degradation, IORPC may help. Typical indicators include:

  • The cache server process has thousands of goroutines (check with cat /jfs/.stats | grep goroutines), but the remotecache throughput shown in juicefs stats does not scale with increased concurrency. This indicates that the one-goroutine-per-connection model of TCP Pool consumes CPU resources without improving throughput.
  • juicefs stats shows high CPU usage, while remotecache throughput is far below the NIC's rated bandwidth. This indicates that CPU resources are consumed by goroutine scheduling rather than data transfer.
  • A large number of TCP connections exist between clients and cache group nodes (check with ss -tan | wc -l, ESTABLISHED and TIME_WAIT connections. A high TIME_WAIT count directly indicates frequent connection creation and closure, a typical characteristic of TCP Pool). Combined with the CPU or throughput issues above, this can further confirm that the bottleneck comes from the TCP Pool connection model.

IORPC addresses this by multiplexing: a single connection can carry multiple concurrent requests simultaneously by matching message IDs. One goroutine can serve multiple requests, reducing the number of connections from thousands or tens of thousands to dozens. The number of goroutines is significantly reduced, and the overhead caused by frequent goroutine context switching in the Go scheduler is greatly minimized. CPU resources are released from connection management and returned to cache disk I/O and network data transfer.

If IORPC alone does not fully utilize available bandwidth, try the splice mode.

When to use Direct I/O (dio)

The default dio mode works well for most scenarios. It is especially useful when top shows high CPU usage by the kswapd process. This indicates that memory fragmentation is slowing down page cache allocation. In this case, direct I/O bypasses the kernel page cache to avoid the issue. However, direct I/O performance is limited by disk performance.

When to use splice

If dio mode does not provide sufficient throughput, switch to splice mode for zero-copy transfer via kernel pipes. This mode is especially beneficial when you want to maximize throughput while minimizing CPU overhead, particularly on older kernels (below 5.12) that lack TCP stack improvements.

Before enabling splice, make sure to increase the maximum pipe size to at least 8 MiB.

IORPC and RDMA

When using RDMA acceleration via --rdma-network, the --iorpc-mode option still controls how the server accesses cache data. However, splice mode automatically falls back to dio when RDMA is active. For details on how RDMA automatically enables IORPC, see the previous section. For complete configuration instructions, see Distributed Cache RDMA Acceleration.