Skip to main content

High-Concurrency Server I/O: epoll, Syscalls, and Zero-Copy on AWS EC2

Cloud ArchitectureUpdated Palaniappan P2 min read

Quick summary: C10k is solved until syscall overhead and context switches eat your Graviton cores. epoll, sendfile, and SO_REUSEPORT behaviors on EC2—and why Lambda caps concurrency differently.

Key Takeaways

  • C10k is solved until syscall overhead and context switches eat your Graviton cores
  • epoll, sendfile, and SO_REUSEPORT behaviors on EC2—and why Lambda caps concurrency differently
  • Linux on EC2 (June 2026) serves most self-hosted APIs
  • Benchmark pattern (hypothetical workload) — c6gn
  • 2xlarge (ENA enhanced) running epoll edge-triggered server, 48K concurrent connections, 0.8ms p99 accept-to-response, sendfile zero-copy reduces CPU 22% vs buffered I/O at 12K req/sec
A gold epoll event loop feeding many sockets on a Graviton-style EC2 origin
Table of Contents

Linux on EC2 (June 2026) serves most self-hosted APIs. Event loops use epoll (edge-triggered) to watch thousands of sockets per thread—contrast with kqueue on BSD/macOS dev laptops; production parity tests matter.

Symptom → mechanism → AWS control

Production symptomMechanismAWS control
CPU pegged at moderate QPSExcessive syscall churn (read/write per chunk)sendfile/splice zero-copy, larger socket buffers
Connection accept backlogSingle-threaded accept bottleneckSO_REUSEPORT multi-listener, ALB pre-connection scaling
epoll thundering herdAll workers wake on single eventEPOLLET edge-triggered + per-core accept threads

Opinionated take: Profile syscalls before rewriting in Rust—on AWS, ALB + ENA instances with epoll edge-triggering handles most concurrency without kernel bypass.

Benchmark pattern (hypothetical workload) — c6gn.2xlarge (ENA enhanced) running epoll edge-triggered server, 48K concurrent connections, 0.8ms p99 accept-to-response, sendfile zero-copy reduces CPU 22% vs buffered I/O at 12K req/sec.

Syscall and context switch tax

Each read/write crossing user/kernel boundary costs CPU. Zero-copy sendfile() moves file → socket without user-space buffers—ideal for static assets behind NGINX on EC2 (often superseded by CloudFront).

PatternBenefit on AWS
epoll + non-blocking I/OFew threads, many connections on c7g Graviton
SO_REUSEPORTAccept queue spread across workers
sendfileStatic file serving from EBS

Lambda avoids your epoll tuning—concurrency is execution environment reuse with platform limits; CPU-bound work still belongs on EC2/ECS.

AWS services map

NeedServiceSkip when
High connection fan-inALB (L7) or NLB (L4)<1K connections on single instance
Enhanced networkingENA-enabled instance types (c6gn, m6in)Bursty Lambda with no persistent connections
Kernel bypass alternativesConsider Nitro + DPDK only at 10Gbps+Standard web API under 1Gbps

When this advice breaks

  • TLS termination on instance — crypto dominates; offload to ALB/CloudFront first.
  • Tiny payloads — syscall cost noise vs business logic.

What to do this week

  1. Compare nginx/envoy worker count to CPU cores on origin ASG.
  2. Profile with perf top during load test—look for syscall hotspots.
  3. Move static assets to S3+CloudFront; keep dynamic on epoll stack.

More in This Track

Part of the Engineering Guides library (June 2026).

What this guide doesn’t cover

CPU cache false sharing—part 4 of this track.

Frequently asked questions

Should I rewrite the origin in Rust to fix syscall overhead?
Profile first. On AWS, ALB plus ENA instances with epoll edge-triggering covers most fan-in. sendfile/splice and larger socket buffers beat a language rewrite when CPU is spent in read/write per chunk.
Does Lambda need epoll tuning?
No. Lambda concurrency is execution-environment reuse with platform limits. CPU-bound work still belongs on EC2 or ECS. Do not port epoll knobs into a function.
When is kernel bypass (DPDK) the wrong next step?
Skip DPDK under about 10 Gbps. Standard web APIs should stay on epoll. TLS terminated on the instance also dominates — offload to ALB or CloudFront first.
What could go wrong with a single accept thread?
Accept backlog and thundering herd. Use SO_REUSEPORT multi-listeners, EPOLLET, per-core accept threads, and let ALB pre-scale connections. Tiny payloads make syscall noise look like a business-logic bug.
Palaniappan P
Palaniappan P

AWS Cloud Architect & AI Expert

AWS-certified cloud architect and AI expert with deep expertise in cloud migrations, cost optimization, and generative AI on AWS.

AWS ArchitectureCloud MigrationGenAI on AWSCost OptimizationDevOps

Recommended Reading

Explore All Articles »