How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS)

Last week, I shared my journey building an HTTP server from scratch in Go using raw TCP sockets. The performance was decent—around 4,000 requests per second at peak—but I knew there was room for improvement.

Then I learned about buffer pooling, and ev…


This content originally appeared on DEV Community and was authored by Uthman Oladele

Last week, I shared my journey building an HTTP server from scratch in Go using raw TCP sockets. The performance was decent—around 4,000 requests per second at peak—but I knew there was room for improvement.

Then I learned about buffer pooling, and everything changed.

The Problem: Death by a Thousand Allocations

My original server had a hidden performance killer. For every single HTTP request, the server was doing this:

func handleConnection(conn net.Conn) {
    buffer := make([]byte, 8192)  // New allocation
    conn.Read(buffer)
    // ... process request ...
    // Buffer gets garbage collected
}

Seems innocent, right? But when you're handling thousands of requests per second, this becomes:

  • Constant memory allocation - Creating new 8KB buffers constantly
  • Garbage collector pressure - GC running frequently to clean up discarded buffers
  • CPU cycles wasted - Allocation and deallocation overhead instead of serving requests

At 4,000 RPS, my server was allocating and throwing away 32 MB of memory every single second.

The Solution: Buffer Pooling with sync.Pool

The core idea is brilliantly simple: reuse buffers instead of creating new ones.

Go's standard library provides sync.Pool for exactly this purpose. Here's how I implemented it:

var requestBufferPool = sync.Pool{
    New: func() interface{} {
        buf := make([]byte, 8192)
        return buf
    },
}

func handleConnection(conn net.Conn) {
    // Get a buffer from the pool
    buffer := requestBufferPool.Get().([]byte)
    defer requestBufferPool.Put(buffer) // Return it when done

    conn.Read(buffer)
    // ... process request ...
}

How It Works

Request 1: Get buffer from pool → Use it → Return to pool
Request 2: Get SAME buffer → Reset & use → Return to pool
Request 3: Get SAME buffer → Reset & use → Return to pool
                              ↓
                    (Zero new allocations!)

Instead of the old wasteful cycle:

Request 1: Allocate → Use → GC cleanup
Request 2: Allocate → Use → GC cleanup
Request 3: Allocate → Use → GC cleanup
                              ↓
                    (Constant allocation churn)

Implementation: Three Strategic Pools

I identified three hot paths where buffers were being repeatedly allocated:

1. Request Buffer Pool (8KB)

var requestBufferPool = sync.Pool{
    New: func() interface{} {
        buf := make([]byte, 8192)
        return buf
    },
}

Used for reading incoming HTTP requests from TCP connections.

2. Chunk Buffer Pool (256 bytes)

var chunkBufferPool = sync.Pool{
    New: func() interface{} {
        buf := make([]byte, 256)
        return buf
    },
}

Used for smaller, chunked reads during request parsing.

3. Response Buffer Pool

var responseBufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

Used for building HTTP response strings before sending.

The Results: Nearly 2x Performance Gain

I ran the same benchmark suite before and after implementing buffer pooling:

Metric Before Buffer Pools After Buffer Pools Improvement
Peak RPS 4,000 7,721 +93% 🔥
Response Time 0.250ms 0.130ms 48% faster
Memory Allocations Constant Minimal ~95% reduction
GC Pressure High Low Significantly reduced

Before vs After Comparison

Before (4,000 RPS):

ab -n 10000 -c 10 -k http://localhost:8080/ping

Requests per second:    4000.12 [#/sec]
Time per request:       0.250 [ms] (mean)

After (7,721 RPS):

ab -n 10000 -c 10 -k http://localhost:8080/ping

Requests per second:    7721.45 [#/sec]
Time per request:       0.130 [ms] (mean)

Key Takeaways

1. Memory Management Matters—A Lot

Even in a garbage-collected language like Go, being mindful of allocations can dramatically impact performance. The GC is smart, but avoiding work is always faster than doing work efficiently.

2. Profile Before Optimizing

I initially thought my bottleneck was request parsing or routing logic. A quick profiling session revealed that memory allocation was the real culprit.

3. sync.Pool Is Your Friend

For any frequently allocated/deallocated objects (buffers, temporary structs, builders), sync.Pool is a simple way to get significant performance gains.

4. The 80/20 Rule Applies

Three strategic buffer pools gave me a 93% performance improvement. This took maybe 30 minutes to implement once I understood the concept.

Important Caveats

This Is Still a Learning Project

While 7,721 RPS sounds impressive, this benchmark tests a simple /ping endpoint that returns "pong". Real-world applications with:

  • Database queries
  • Business logic
  • File I/O
  • External API calls

...will see significantly lower RPS (typically 100-500 for most web apps). Buffer pooling helps with the networking layer, but your application logic will likely be the bottleneck in production.

Not a Silver Bullet

Buffer pooling helps when:

  • You're allocating the same size/type repeatedly
  • The objects are expensive to create
  • Allocation shows up in profiling

It won't help if your bottleneck is CPU-bound computation, database queries, or external API calls.

The Journey So Far

Here's the complete performance evolution of this project:

  1. Initial buggy version: ~250 RPS (Connection handling bugs)
  2. Bug fix: 1,389 RPS (Fixed request handling)
  3. Keep-alive optimization: 1,710 RPS (Connection reuse)
  4. Concurrency optimization: 4,000 RPS (Found optimal load)
  5. Buffer pooling: 7,721 RPS (Memory optimization) 🚀

Total improvement: ~31x from the first version!

Try It Yourself

The full source code is available on GitHub:
https://github.com/codetesla51/raw-http

Clone it, run benchmarks before and after commenting out the buffer pools, and see the difference yourself:

git clone https://github.com/codetesla51/raw-http
cd raw-http
go run main.go

# In another terminal
ab -n 10000 -c 10 -k http://localhost:8080/ping

What's Next?

Now that I've squeezed significant performance out of the memory layer, I'm curious about:

  • HTTP/2 support - Binary framing instead of text parsing
  • Connection pooling strategies - How do production servers handle thousands of concurrent connections?
  • Zero-copy techniques - Can I avoid copying data between buffers entirely?

Have you used buffer pooling in your projects? What performance gains did you see? Drop a comment below—I'd love to hear about your optimization stories!

Building from first principles teaches you things frameworks hide. This project continues to surprise me with how much performance can be unlocked by understanding what's really happening under the hood.

Project Stats:

  • 🚀 7,721 RPS peak performance
  • 🔧 Built with raw TCP sockets in Go
  • 📚 Learning-focused, not production-ready
  • ⚡ +93% performance from buffer pooling alone

Check out the full project on GitHub


This content originally appeared on DEV Community and was authored by Uthman Oladele


Print Share Comment Cite Upload Translate Updates
APA

Uthman Oladele | Sciencx (2025-11-12T16:46:35+00:00) How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS). Retrieved from https://www.scien.cx/2025/11/12/how-buffer-pooling-doubled-my-http-servers-throughput-4000-7721-rps/

MLA
" » How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS)." Uthman Oladele | Sciencx - Wednesday November 12, 2025, https://www.scien.cx/2025/11/12/how-buffer-pooling-doubled-my-http-servers-throughput-4000-7721-rps/
HARVARD
Uthman Oladele | Sciencx Wednesday November 12, 2025 » How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS)., viewed ,<https://www.scien.cx/2025/11/12/how-buffer-pooling-doubled-my-http-servers-throughput-4000-7721-rps/>
VANCOUVER
Uthman Oladele | Sciencx - » How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/11/12/how-buffer-pooling-doubled-my-http-servers-throughput-4000-7721-rps/
CHICAGO
" » How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS)." Uthman Oladele | Sciencx - Accessed . https://www.scien.cx/2025/11/12/how-buffer-pooling-doubled-my-http-servers-throughput-4000-7721-rps/
IEEE
" » How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS)." Uthman Oladele | Sciencx [Online]. Available: https://www.scien.cx/2025/11/12/how-buffer-pooling-doubled-my-http-servers-throughput-4000-7721-rps/. [Accessed: ]
rf:citation
» How Buffer Pooling Doubled My HTTP Server’s Throughput (4,000 7,721 RPS) | Uthman Oladele | Sciencx | https://www.scien.cx/2025/11/12/how-buffer-pooling-doubled-my-http-servers-throughput-4000-7721-rps/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.