Skip to content
All tools
Tool

Calculate a UDP receive buffer

Turn packet rate, packet size, burst multiplier, and receiver stall tolerance into a transparent SO_RCVBUF request.

How long might your reader stall?

Anything with a garbage collector

Arithmetic minimum

1.3 MiB

Exactly the stall, with nothing spare. Do not use this one.

Set this

1.9 MiB

2,039,808 bytes

Half again on top, for burst and kernel overhead.

apply it
# 1. raise the cap, or your request is silently clamped
sudo sysctl -w net.core.rmem_max=2039808

# 2. make it survive a reboot
echo "net.core.rmem_max=2039808" | sudo tee -a /etc/sysctl.d/99-shred.conf

# 3. set it on the socket, then READ IT BACK.
#    Linux reports double what you set. Anything less means it was clamped.
#    Rust:   sock.set_recv_buffer_size(2039808)?;
#    Go:     conn.SetReadBuffer(2039808)
#    Python: s.setsockopt(SOL_SOCKET, SO_RCVBUF, 2039808)
#            print(s.getsockopt(SOL_SOCKET, SO_RCVBUF))  # expect ~4079616

Sizing reduces drops, it does not prove there are none. Poll the receive error counter for your socket in /proc/net/udp and alert on it moving. Finding out from your own monitoring beats finding out from a gap in your data.

5 inputs produce one buffer request

The buffer-size calculator converts a receiver stall tolerance into bytes. Its defaults use the measured feed: 5,585 packets per second and 1,216 mean packet bytes. The user supplies stall milliseconds, burst multiplier, and a per-packet kernel-accounting allowance.

The output is a requested SO_RCVBUF value, the unrounded arithmetic, estimated packet capacity, and Linux ceiling guidance. It is a planning value that must be validated on the deployed kernel.

Method

Let p be packets per second, t be stall tolerance in seconds, b be the burst multiplier, s be mean UDP payload bytes, and o be the planning allowance for per-packet socket accounting.

text

base_packets = ceil(p * t)
packets = base_packets * b
charged_bytes_per_packet = s + o
raw_buffer_bytes = packets * charged_bytes_per_packet
request_bytes = round_up(raw_buffer_bytes, selected_boundary)

The default boundary is the next power-of-two MiB value. The displayed raw result remains visible so rounding never conceals the method.

The allowance o is not a Solana packet field. Linux accounts socket memory using internal allocations, metadata, alignment, and kernel-specific structures. The default planning allowance is 512 bytes per packet, matching the documented sizing example, but the user should replace it with measurements from the target host.

Linux commonly doubles an SO_RCVBUF request internally for bookkeeping and returns the doubled value from getsockopt. The tool distinguishes requested bytes, expected readback, and net.core.rmem_max. It does not divide the application request by two in the formula.

Worked example

Use the measured 5,585 packets per second, 250 milliseconds of stall tolerance, a burst multiplier of 4, 1,216 payload bytes, and a 512-byte accounting allowance.

text

t = 250 / 1000 = 0.250 seconds
base_packets = ceil(5,585 * 0.250)
             = ceil(1,396.25)
             = 1,397 packets

packets = 1,397 * 4
        = 5,588 packets

charged_bytes_per_packet = 1,216 + 512
                         = 1,728 bytes

raw_buffer_bytes = 5,588 * 1,728
                 = 9,656,064 bytes

Rounding to the next power-of-two MiB gives a 16 MiB request, or 16,777,216 bytes. On a Linux host, set net.core.rmem_max to at least the request and expect the effective readback to follow that kernel's doubling convention, often near 32 MiB.

The 5,588 packet result includes the conservative pre-burst ceiling. It represents at least 250 milliseconds at four times the measured mean rate, or about one second at the mean rate under the chosen accounting assumption.

Payload-only and charged capacity differ

A 16 MiB buffer divided by 1,216 payload bytes appears to hold about 13,797 mean packets. Dividing by the 1,728 planning charge gives about 9,709 packets. Neither is guaranteed because the actual kernel charge can differ by packet size, kernel, architecture, and receive path.

The calculator shows both estimates. The charged estimate is safer for planning. Live socket memory from ss and kernel counters determine whether the allowance was adequate.

Maximum packet size is 1,228 bytes under the product wire contract. Using the maximum instead of the 1,216 mean changes payload planning slightly. Burst rate and accounting allowance usually move the result more.

Validate with a stall test

Configure the host ceiling and request the calculated buffer from the application. Read the effective value back. Then run representative traffic while pausing the receive thread for 50, 100, 250, and 500 milliseconds.

Observe socket overflow, UDP receive-buffer errors, NIC drops, application sequence gaps, and the age of the oldest packet. The accepted setting is the smallest operational value that covers the declared stall and burst target without hiding unacceptable latency.

A larger buffer prevents finite queue overflow by storing more old packets. It does not increase decoder throughput. If the application remains slower than 5,585 packets per second, every finite buffer eventually fills.

Kernel and application queues are different

SO_RCVBUF protects the time before the receive call removes datagrams from the socket. A separate application queue protects downstream workers while the receive thread continues. Adding their capacities does not create one interchangeable stall budget.

If the receive thread is descheduled, only kernel capacity helps. If decode workers stall but receive continues, the application queue helps. Size and monitor both. Keep the application queue bounded so overload becomes visible instead of accumulating seconds of stale shreds.

At 54.3 Mbps mean traffic, byte rate looks modest for a 1 Gbps link. Packet scheduling and bursts still matter. Buffer sizing begins with packets per second because every datagram consumes queue metadata and one receive operation or batch slot.

Limitations

The formula treats the burst multiplier as constant over the selected interval. Real traffic has a distribution. Measure maximum and high-percentile packet counts in short windows and replace the assumption.

The per-packet allowance is a planning estimate, not a portable kernel constant. Container limits, namespaces, sysctl ceilings, memory pressure, driver behavior, and socket options can change effective capacity.

The calculator does not size NIC rings, NAPI budgets, application channels, capture buffers, or storage. It does not repair upstream loss. A clean socket overflow counter cannot prove that all sent packets reached the NIC.

The output is memory capacity, not a latency recommendation. A strategy with a 20 millisecond usefulness window should not process a one-second backlog merely because the buffer retained it. Pair buffer capacity with a maximum packet-age policy.

How it works out the answer

Compute base_packets = ceil(packets_per_second × stall_seconds). Compute bytes = base_packets × burst_multiplier × (mean_payload_bytes + per_packet_accounting_allowance). Round bytes up to the selected allocation boundary for the SO_RCVBUF request. On Linux, compare the request with net.core.rmem_max and explain that getsockopt commonly reports twice the requested value.

Questions

Why does the calculator add bytes to every packet?
The kernel consumes more receive memory than UDP payload length alone. Socket structures, packet metadata, alignment, and allocator behavior add a host-specific charge. The default 512-byte allowance is a planning estimate. Validate it with live socket memory and drop counters on the deployed kernel.
Why can Linux report twice the requested SO_RCVBUF?
Linux commonly doubles the application request for internal bookkeeping and returns that effective value through getsockopt. The tool displays request and expected readback separately. Kernel versions and platforms can differ, so record the actual returned value and validate capacity with a controlled stall test.
Can a larger receive buffer fix a slow decoder?
No. A larger buffer absorbs finite bursts or receive-thread stalls. If sustained processing remains below the incoming packet rate, every finite queue eventually fills. It can also preserve stale packets beyond a strategy's useful window. Monitor throughput and oldest-packet age alongside drops.