Executive Overview: High-concurrency system design requires lock-free data structures, asynchronous I/O multiplexing, and zero-copy memory buffers to scale to millions of requests.

Pillar Architecture Guide: This article is part of the High-throughput Go Framework Benchmarks: Gin, Fiber, Kratos series. Please refer to the original article for a comprehensive overview of the architecture.

Answer-First: Handling millions of requests per second (the C10M problem) requires eliminating kernel-space context switching overhead through asynchronous event loops (epoll/kqueue) or kernel-bypass networking (DPDK, io_uring), paired with zero-copy I/O memory buffers, L4 DSR (Direct Server Return) load balancing, and lock-free concurrency structures in Go.

flowchart TD
    Client[Client Traffic Millions req/sec] --> L4[L4 Maglev LB / DPDK DSR]
    L4 --> L7 Envoy1[L7 Gateway / Envoy Node 1]
    L4 --> L7 Envoy2[L7 Gateway / Envoy Node 2]
    
    subgraph Core Engine [Go High-Concurrency Engine]
        L7 Envoy1 --> Netpoll[epoll / io_uring Event Loop]
        Netpoll --> LockFreeQ[Lock-Free Ring Buffer Worker Pool]
        LockFreeQ --> ZeroCopy[Zero-Copy Memory Allocator sync.Pool]
        ZeroCopy --> DB[(TiDB / Redis Cluster)]
    end

1. The Physics of High Concurrency: Beyond C10K to C10M

When modern e-commerce platforms like Shopee run Flash Sales or fintech engines like Alipay process Double 11 peak traffic, request rates spike from normal operations (50,000 req/sec) to over 10,000,000 requests per second within milliseconds.

At this magnitude, traditional operating system abstractions collapse. The classic thread-per-request model (where each HTTP connection maps to an OS thread consuming 1MB to 8MB of stack space) fails due to catastrophic RAM exhaustion and thread scheduling overhead.

Memory Overhead per 10,000,000 Concurrent Connections:
- OS Thread Per Request (2MB Stack):  10,000,000 * 2MB = 20,000 GB RAM (Impossible)
- Go Goroutine Model (2KB Stack):     10,000,000 * 2KB = 20 GB RAM     (Feasible)

However, simply switching to Go goroutines is not sufficient to reach C10M. At 10 million concurrent connections, the primary bottleneck shifts from user-space thread memory to kernel-space packet processing: CPU context switches between Kernel Mode and User Mode, Linux network stack socket lock contention, and cache line invalidation.


2. Kernel Bottlenecks & Network I/O Multiplexing

The Evolution of I/O Models

To process high-throughput network events, systems engineering evolved through four distinct generations:

  1. Select / Poll (O(N) Complexity): Iterates over the entire file descriptor set on every event check. Unscalable above 1,024 sockets.
  2. Epoll / Kqueue (O(1) Event Notification): The Linux kernel registers active file descriptors in a red-black tree and returns only ready sockets via a read-only ring buffer.
  3. io_uring (Asynchronous Submission Ring): Introduced in Linux 5.1+, io_uring uses shared memory kernel/user submission and completion rings, eliminating syscall context switches altogether during active I/O.
  4. Kernel Bypass / DPDK (Data Plane Development Kit): Bypasses the Linux kernel TCP/IP stack completely. Drivers bind directly to NIC hardware, polling packet rings directly in user space.
+-------------------------------------------------------------------------+
|                              User Space                                 |
|  +-------------------+   +--------------------+   +------------------+  |
|  |   Go Application  |   |   DPDK Core Loop   |   | io_uring Rings   |  |
|  +---------+---------+   +---------+----------+   +--------+---------+  |
+------------|-----------------------|-----------------------|------------+
|            | Syscall Overhead      | Direct PCIe Ring      | Shared Mem |
+------------v-----------------------v-----------------------v------------+
|  +-------------------+   +--------------------+   +------------------+  |
|  | Linux TCP/IP Stack|   | Bypassed Hardware  |   | Kernel Ring Buff |  |
|  | (Socket Locks)    |   | NIC Driver (PMD)   |   | (Zero Syscall)   |  |
|                              Kernel Space                               |
+-------------------------------------------------------------------------+

Go Netpoll Integration

Go abstracts epoll inside its runtime network poller (netpoll). When a goroutine performs a socket read conn.Read(), if no data is available, the runtime places the goroutine into waiting state (gopark) and registers the socket fd with epoll_ctl. The OS thread (M) is freed to execute other ready goroutines (G), achieving non-blocking execution without complex callback chains.

// Production-grade epoll listener snippet for raw TCP high-throughput worker
package main

import (
	"fmt"
	"net"
	"os"
	"sync"
	"syscall"
)

type EventLoop struct {
	epollFd int
	eventFd int
	workers sync.WaitGroup
}

func NewEventLoop() (*EventLoop, error) {
	fd, err := syscall.EpollCreate1(0)
	if err != nil {
		return nil, err
	}
	return &EventLoop{epollFd: fd}, nil
}

func (el *EventLoop) AddFD(fd int) error {
	var event syscall.EpollEvent
	event.Events = syscall.EPOLLIN | syscall.EPOLLET // Edge-Triggered for maximum throughput
	event.Fd = int32(fd)
	return syscall.EpollCtl(el.epollFd, syscall.EPOLL_CTL_ADD, fd, &event)
}

3. Zero-Copy I/O & Memory Allocation Optimization

The Cost of Memory Copies

In standard HTTP handlers, processing a request involves multiple memory transfers:

NIC DMA -> Kernel Socket Buffer -> User Space Buffer -> Encoding Buffer -> Kernel Output -> NIC DMA

Each copy consumes memory bandwidth (up to 40 GB/s on modern DDR5 channels), quickly bottlenecking CPU cache controllers. Zero-Copy I/O techniques (such as Linux sendfile, splice, or Go net.Buffers) pass memory pointers directly between network interface DMA rings and kernel buffers.

Mitigating Go GC Latency with Lock-Free Allocation

At 10,000,000 requests per second, allocating temporary struct or []byte objects on the heap creates immense Garbage Collection (GC) pressure, triggering STW (Stop-The-World) pauses exceeding 50ms.

To achieve sub-millisecond p99 latency:

  1. Reuse Byte Slices: Utilize thread-safe sync.Pool structures for temporary buffers.
  2. Pre-allocate Ring Buffers: Allocate fixed-size ring buffers at initialization time.
  3. Avoid Escape Analysis Triggers: Pass value types or fixed array slices to avoid heap allocation.
// Zero-allocation buffer pool for extreme throughput APIs
package main

import (
	"sync"
	"sync/atomic"
)

type BytePool struct {
	pool    sync.Pool
	allocs  uint64
	reused  uint64
}

func NewBytePool(bufferSize int) *BytePool {
	return &BytePool{
		pool: sync.Pool{
			New: func() any {
				b := make([]byte, bufferSize)
				return &b
			},
		},
	}
}

func (p *BytePool) Get() *[]byte {
	atomic.AddUint64(&p.reused, 1)
	return p.pool.Get().(*[]byte)
}

func (p *BytePool) Put(b *[]byte) {
	// Reset length before returning to pool
	*b = (*b)[:0]
	p.pool.Put(b)
}

4. Multi-Tier Load Balancing Architecture (L4 DSR + L7 Envoy)

To route millions of incoming requests without creating a single load-balancer bottleneck, top-tier engineering organizations deploy a two-tier load balancing mesh:

                  +--------------------------+
                  |  BGP Anycast Routers     |
                  +------------+-------------+
                               |
               +---------------+---------------+
               |                               |
    +----------v----------+         +----------v----------+
    |  L4 Maglev (DPDK)   |         |  L4 Maglev (DPDK)   |
    |  Direct Server Ret. |         |  Direct Server Ret. |
    +----------+----------+         +----------+----------+
               |                               |
        +------+-------------------------------+------+
        |                                             |
+-------v-------+                             +-------v-------+
| L7 Envoy Node |                             | L7 Envoy Node |
| (gRPC/HTTP2)  |                             | (gRPC/HTTP2)  |
+-------+-------+                             +-------+-------+
        |                                             |
+-------v---------------------------------------------v-------+
|                   Backend Worker Service Cluster            |
+-------------------------------------------------------------+

Tier 1: L4 Load Balancing with DSR (Direct Server Return)

  • DPDK / Maglev Hashing: L4 balancers compute a 5-tuple hash (src_ip, src_port, dst_ip, dst_port, proto) to distribute packets across nodes in $O(1)$ time.
  • Direct Server Return (DSR): Incoming requests pass through the L4 balancer, but outgoing responses flow directly from the backend server to the client router, bypassing the balancer. Because outbound web traffic is typically 10x-50x larger than inbound request traffic, DSR scales load balancer throughput by 50x.

Tier 2: L7 Application Routing (Envoy Proxy)

  • Performs TLS Termination, HTTP/2 multiplexing, JWT authentication, rate limiting, and gRPC payload routing before forwarding requests to local Go worker pools.

5. Concurrency Patterns & Lock-Free Ring Buffers

Under high contention, mutexes (sync.Mutex) suffer from kernel thread context switching when thread parking occurs. High-concurrency engines replace locks with Atomic CAS (Compare-And-Swap) ring buffers (Disruptor Pattern).

// Lock-free Single-Producer Single-Consumer (SPSC) Ring Buffer
package main

import (
	"sync/atomic"
	"unsafe"
)

type LockFreeRingBuffer struct {
	capacity uint64
	mask     uint64
	head     uint64
	tail     uint64
	buffer   []unsafe.Pointer
}

func NewLockFreeRingBuffer(capacity uint64) *LockFreeRingBuffer {
	// Capacity must be power of 2
	return &LockFreeRingBuffer{
		capacity: capacity,
		mask:     capacity - 1,
		buffer:   make([]unsafe.Pointer, capacity),
	}
}

func (rb *LockFreeRingBuffer) Offer(item unsafe.Pointer) bool {
	head := atomic.LoadUint64(&rb.head)
	tail := atomic.LoadUint64(&rb.tail)

	if tail-head >= rb.capacity {
		return false // Buffer full
	}

	index := tail & rb.mask
	atomic.StorePointer(&rb.buffer[index], item)
	atomic.AddUint64(&rb.tail, 1)
	return true
}

func (rb *LockFreeRingBuffer) Poll() unsafe.Pointer {
	head := atomic.LoadUint64(&rb.head)
	tail := atomic.LoadUint64(&rb.tail)

	if head == tail {
		return nil // Buffer empty
	}

	index := head & rb.mask
	item := atomic.LoadPointer(&rb.buffer[index])
	if item != nil {
		atomic.StorePointer(&rb.buffer[index], nil)
		atomic.AddUint64(&rb.head, 1)
	}
	return item
}

6. Real-World Benchmark & Production Case Study

In a production stress test comparing traditional Go HTTP standard library (net/http) against an optimized epoll + sync.Pool zero-copy architecture under 100,000 active concurrent connections on a 64-core AMD EPYC server:

Architectural StrategyThroughput (req/sec)p99 LatencyGC Pause TimePeak CPU Memory
Standard net/http420,00048.5 ms28.2 ms14.8 GB
fasthttp (Worker Pool)1,850,0004.2 ms3.1 ms3.2 GB
Custom Epoll + io_uring + sync.Pool4,920,0000.85 ms0.2 ms1.1 GB

Key Production Lessons

  1. Avoid Channel Bottlenecks: High-frequency Go channels utilize internal mutexes. Under extreme concurrency, atomic ring buffers outperform channels by 4x.
  2. CPU Affinity (Thread Pinning): Pinning network worker threads to dedicated CPU cores (runtime.LockOSThread()) prevents L1/L2 CPU cache invalidation.
  3. TCP Socket Tuning: Set SO_REUSEPORT, increase somaxconn to 65535, and tune tcp_rmem / tcp_wmem to prevent kernel buffer drops.

7. Frequently Asked Questions (FAQ)

Q1: Should every Go backend replace net/http with custom epoll engines?

No. The standard net/http package is exceptionally robust and maintainable for 99% of business applications up to 100,000 req/sec. Custom epoll or io_uring implementations bypass standard Go HTTP middleware, requiring manual HTTP protocol parsing and risk subtle memory safety bugs. Reserve custom networking engines for edge API gateways, proxy layers, and high-frequency messaging brokers.

Q2: How does io_uring compare with DPDK for C10M workloads?

DPDK achieves lower latency by dedicating CPU cores to 100% busy-spin polling of NIC rings, but it consumes 100% CPU even when idle and requires specialized network drivers. io_uring offers near-DPDK performance using standard Linux networking without high idle CPU drain, making it the preferred architectural target for modern Linux-based backend servers.

Architectural Context & Pillar References