When delivering static assets, large firmware binary images, or video streams over a high-bandwidth 40Gbps backbone, traditional file I/O pipelines introduce severe system CPU overhead.
During a global release distribution event, media delivery proxy nodes running standard read/write routines saturated all 32 CPU cores at 100% utilization, while network interface throughput plateaued at less than 12Gbps (far below line rate).
# 1. dstat System Performance Metrics ----total-cpu-usage---- -dsk/total- ---system-- usr sys idl wai stq| read writ| int csw 8 82 0 10 0| 1.2G 0| 320k 1.8M <-- 1.8 Million CPU Context Switches/sec! # 2. perf top Kernel Profile 42.10% [kernel] copy_user_generic_unrolled 21.40% [kernel] page_fault 12.80% [kernel] __fget_light
To understand why system CPU utilization spiked to 82%, we must trace data buffer copies and CPU context switches during traditional read() / write() system calls.
[ 1. Traditional 4-Copy File Pipeline ]
Disk ──> Page Cache ──> User Space Buffer ──> Socket Buffer ──> NIC Wire
(DMA Copy) (CPU Copy #1) (CPU Copy #2) (DMA Copy)
[ 4 Context Switches + 2 Wasted CPU Buffer Copies ]
[ 2. Linux sendfile() Zero-Copy Pipeline ]
Disk ──> Page Cache ══════════════════════════════════════════> NIC Wire
(DMA Copy) (Descriptor Pointer Copy Only) (DMA Copy)
[ 2 Context Switches + ZERO CPU Buffer Copies ]
read(). The OS issues a DMA transfer from Disk to Kernel Page Cache.write(). CPU copies data from Application User Buffer to Socket Buffer. (Context Switch 3: User → Kernel).
By invoking the sendfile() syscall combined with NIC Scatter-Gather DMA, data is transferred directly from the kernel Page Cache to the Network Controller with ZERO CPU data copying and only 2 context switches. CPU usage drops from 80%+ down to near zero!
Configure Nginx to leverage Linux kernel sendfile(), tcp_nopush, and asynchronous Direct I/O (AIO) for large static media assets:
http {
# ----------------------------------------------------------------------
# Linux DMA Zero-Copy Engine Settings
# ----------------------------------------------------------------------
# 1. Enable Kernel sendfile() syscall
sendfile on;
# 2. Bundle TCP headers and payload into single full-sized MTU packets
tcp_nopush on;
# 3. Disable Nagle algorithm for low-latency header transmission
tcp_nodelay on;
# 4. Asynchronous Thread Pool & Direct I/O for files > 16MB
# Prevents large file reads from blocking PageCache lock
aio threads;
directio 16m;
directio_alignment 4k;
# Open File Descriptor Cache
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
server {
listen 80 reuseport;
server_name cdn.zhabrosima.com;
location /static/ {
root /var/www/assets;
# Browser Cache Control for static media
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
}
Use system-level diagnostic tools to measure context switch frequency and kernel I/O wait on live media streaming nodes:
vmstat)# Display system context switches (cs) and interrupts (in) every 1 second
vmstat 1
# Key Metric: Ensure 'cs' column remains under 50,000 under high throughput
# Trace Nginx worker process sendfile64 syscall execution
sudo strace -p $(pgrep -f "nginx: worker" | head -n 1) -e trace=sendfile64
Historically, HTTPS encryption broke sendfile() because TLS payload transformation required copying packets into user-space memory (OpenSSL) for AES encryption before transmitting.
sendfile() with kTLS to stream encrypted TLS traffic straight from Page Cache to the wire with zero user-space memory intervention!We conducted a 10Gbps streaming file delivery benchmark (100MB video file payloads, 2,000 concurrent clients):
| Performance Metric | Traditional Read/Write | Tuned DMA Zero-Copy | System Impact |
|---|---|---|---|
| Network Throughput | 11.8 Gbps (CPU Bound) | 38.9 Gbps (Line Rate) | +229.6% Throughput Gain |
| CPU Utilization (System Space) | 82.4% System CPU | 4.1% System CPU | -95.0% CPU Load Drop |
| Context Switches / sec | 1,840,000 /sec | 22,400 /sec | -98.7% Reduction |
rate(node_context_switches_total[5m])avg(rate(node_cpu_seconds_total{mode="system"}[5m])) * 100