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
  • As of June 2026, Linux on EC2 serves most self-hosted APIs
  • On AWS, ALB plus ENA instances with epoll edge-triggering handles most concurrency without kernel bypass
  • Benchmark pattern (hypothetical workload): c6gn
A gold epoll event loop feeding many sockets on a Graviton-style EC2 origin
Table of Contents

As of June 2026, Linux on EC2 serves most self-hosted APIs. Event loops use epoll (edge-triggered) to watch thousands of sockets per thread. BSD and macOS dev laptops use kqueue instead, so production parity tests matter.

Symptoms, mechanisms, and AWS controls

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 eventEPOLLEXCLUSIVE or SO_REUSEPORT + per-core accept threads

My recommendation: profile syscalls before rewriting in Rust. On AWS, ALB plus 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 the user/kernel boundary costs CPU. Zero-copy sendfile() moves a file to a socket without user-space buffers, which suits 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, and 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, so offload to ALB/CloudFront first.
  • Tiny payloads: syscall cost is noise next to 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 a load test and 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 is covered in 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, so 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 (or EPOLLEXCLUSIVE on a shared listen socket), 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 »