From c2cd38ae9a74cd3432f487c477c9e17e7c730770 Mon Sep 17 00:00:00 2001 From: iyashjayesh Date: Thu, 1 Jan 2026 19:28:06 +0530 Subject: [PATCH 1/2] feat: Refactor module structure by moving adaptivepool package to root --- Makefile | 10 +-- README.md | 15 ++--- ...ckpressure_test.go => backpressure_test.go | 0 benchmarks/README.md | 19 ------ blog/scaling-to-1m-rps.md | 62 ------------------- ..._bench_test.go => comparison_bench_test.go | 4 +- adaptivepool/errors.go => errors.go | 0 examples/batch_processor/main.go | 2 +- .../compare.go => examples/comparison/main.go | 0 examples/http_server/main.go | 2 +- .../one_million_simulator/with_pool/main.go | 2 +- .../internal_types.go => internal_types.go | 0 adaptivepool/metrics.go => metrics.go | 0 adaptivepool/options.go => options.go | 0 adaptivepool/pool.go => pool.go | 0 .../pool_bench_test.go => pool_bench_test.go | 4 +- adaptivepool/pool_test.go => pool_test.go | 0 .../scaling_test.go => scaling_test.go | 0 adaptivepool/shutdown.go => shutdown.go | 0 .../shutdown_test.go => shutdown_test.go | 0 adaptivepool/submit.go => submit.go | 0 21 files changed, 20 insertions(+), 100 deletions(-) rename adaptivepool/backpressure_test.go => backpressure_test.go (100%) delete mode 100644 benchmarks/README.md delete mode 100644 blog/scaling-to-1m-rps.md rename benchmarks/comparison_bench_test.go => comparison_bench_test.go (98%) rename adaptivepool/errors.go => errors.go (100%) rename scripts/compare.go => examples/comparison/main.go (100%) rename adaptivepool/internal_types.go => internal_types.go (100%) rename adaptivepool/metrics.go => metrics.go (100%) rename adaptivepool/options.go => options.go (100%) rename adaptivepool/pool.go => pool.go (100%) rename benchmarks/pool_bench_test.go => pool_bench_test.go (97%) rename adaptivepool/pool_test.go => pool_test.go (100%) rename adaptivepool/scaling_test.go => scaling_test.go (100%) rename adaptivepool/shutdown.go => shutdown.go (100%) rename adaptivepool/shutdown_test.go => shutdown_test.go (100%) rename adaptivepool/submit.go => submit.go (100%) diff --git a/Makefile b/Makefile index e4c784f..4a25586 100644 --- a/Makefile +++ b/Makefile @@ -26,11 +26,11 @@ help: build: @echo "Building adaptivepool..." - $(GOBUILD) ./adaptivepool/... + $(GOBUILD) ./... test: @echo "Running tests with race detector and coverage..." - $(GOTEST) -race -v -coverprofile=coverage.out ./adaptivepool/... + $(GOTEST) -race -v -coverprofile=coverage.out ./... @echo "To view coverage report: go tool cover -html=coverage.out" lint: @@ -39,7 +39,7 @@ lint: bench: @echo "Running benchmarks..." - $(GOTEST) -bench=. -benchmem ./benchmarks/... + $(GOTEST) -bench=. -benchmem ./... run-http: @echo "Running HTTP server example..." @@ -59,10 +59,10 @@ run-1m-without: run-comparison: @echo "Comparing Naive vs Adaptive Pool..." - $(GOCMD) run scripts/compare.go + $(GOCMD) run examples/comparison/main.go clean: @echo "Cleaning..." $(GOCLEAN) rm -f coverage.out - rm -f benchmarks/benchmark.txt + diff --git a/README.md b/README.md index 0e67843..9278354 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/iyashjayesh/go-adaptive-pool/actions/workflows/ci.yml/badge.svg)](https://github.com/iyashjayesh/go-adaptive-pool/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/iyashjayesh/go-adaptive-pool)](https://goreportcard.com/report/github.com/iyashjayesh/go-adaptive-pool) -[![GoDoc](https://godoc.org/github.com/iyashjayesh/go-adaptive-pool/adaptivepool?status.svg)](https://godoc.org/github.com/iyashjayesh/go-adaptive-pool/adaptivepool) +[![GoDoc](https://godoc.org/github.com/iyashjayesh/go-adaptive-pool?status.svg)](https://godoc.org/github.com/iyashjayesh/go-adaptive-pool) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![Visitors](https://api.visitorbadge.io/api/visitors?path=iyashjayesh%2Fgo-adaptive-pool%20&countColor=%23263759&style=flat) ![GitHub last commit]( https://img.shields.io/github/last-commit/iyashjayesh/go-adaptive-pool) @@ -22,7 +22,7 @@ A production-grade adaptive worker pool for Go that handles dynamic scaling, bac ## Installation ```bash -go get github.com/iyashjayesh/go-adaptive-pool/adaptivepool +go get github.com/iyashjayesh/go-adaptive-pool ``` ## Quick Start @@ -35,7 +35,7 @@ import ( "log" "time" - "github.com/iyashjayesh/go-adaptive-pool/adaptivepool" + "github.com/iyashjayesh/go-adaptive-pool" ) func main() { @@ -186,6 +186,8 @@ We performed an extreme pressure test (1M RPS target for 30s with 500KB tasks) t **Why the Pool Wins:** Under extreme load, the Naive approach causes a "Goroutine Explosion" and "Memory Bomb" that forces the Go runtime into constant Garbage Collection, leading to unusable 2-second latencies. The `go-adaptive-pool` shields your system by enforcing backpressure and resource caps. +For a detailed deep-dive into this test and the mechanics of the pool, check out the blog post: [Scaling to 1 Million RPS](https://iyashjayesh.github.io/go-adaptive-pool-website/blog/scaling-to-1m-rps/). + **Run the comparison yourself:** ```bash make run-comparison @@ -196,7 +198,6 @@ make run-comparison Run standard micro-benchmarks: ```bash -cd benchmarks go test -bench=. -benchmem -benchtime=10s ``` @@ -245,13 +246,13 @@ Key components: Run tests with race detector: ```bash -go test -race -v ./adaptivepool/ +go test -race -v ./... ``` Run with coverage: ```bash -go test -race -coverprofile=coverage.out ./adaptivepool/ +go test -race -coverprofile=coverage.out ./... go tool cover -html=coverage.out ``` @@ -273,7 +274,7 @@ Inspired by: ## Related Documentation - [Examples](examples/) - Complete working examples -- [Benchmarks](benchmarks/) - Performance comparisons +- [Benchmarks](.) - Performance comparisons ## Star History diff --git a/adaptivepool/backpressure_test.go b/backpressure_test.go similarity index 100% rename from adaptivepool/backpressure_test.go rename to backpressure_test.go diff --git a/benchmarks/README.md b/benchmarks/README.md deleted file mode 100644 index 9e3fe13..0000000 --- a/benchmarks/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Benchmarks - -Benchmarks for testing throughput, latency, and comparing the pool against other approaches. - -## Running benchmarks - -To run the standard benchmarks: -```bash -go test -bench=. -benchmem -``` - -## Comparisons - -The `comparison_bench_test.go` file contains tests that specifically check how the adaptive pool performs against a naive "go func()" approach under different types of load (CPU vs Memory focus). - -For detailed data-driven logs and side-by-side tables, use the root command instead: -```bash -make run-comparison -``` \ No newline at end of file diff --git a/blog/scaling-to-1m-rps.md b/blog/scaling-to-1m-rps.md deleted file mode 100644 index 1e5541f..0000000 --- a/blog/scaling-to-1m-rps.md +++ /dev/null @@ -1,62 +0,0 @@ -# Your 1M RPS Service is a Ticking Time Bomb - -"Goroutines are cheap." Every Go developer has heard this. And for common use cases, it's true. But when you start aiming for 1 Million Requests Per Second, that saying becomes dangerous. - -I ran some tests to see exactly when the "Naive" approach to Go concurrency falls apart and how an adaptive worker pool changes the game. - -## The Test: 1 Million RPS Burst -I used a 10-core machine and pushed 1 Million requests per second for 30 seconds. Each request was designed to be somewhat realistic: 20ms of processing and a 500KB memory allocation (like handling a decoded JSON object). - -### Case A: The Naive approach (No Pool) -```go -func handle(w http.ResponseWriter, r *http.Request) { - go process(r.Context()) // Just spawn and hope -} -``` - -### Case B: Adaptive Pool -```go -pool, _ := adaptivepool.New( - adaptivepool.WithMaxWorkers(5000), - adaptivepool.WithQueueSize(100000), -) -// ... in handler -err := pool.Submit(ctx, job) -``` - -## The Results - -### The Stability Paradox -The Naive version immediately tried to spawn over 100,000 goroutines. The result was a mess. The CPU spent more time on context switching and garbage collection than actual work. - -With the adaptive pool, even though we were rejecting millions of incoming requests, we actually finished 4.6 times more jobs than the naive version did. By capping the workers at 5,000, the CPU could actually focus on finishing tasks rather than managing them. - -### The Memory Tax -RAM usage is where the naive approach really fails. -- Naive: Hit 50.86 GB of RAM. Most standard servers would have crashed long before this. -- Adaptive Pool: Stayed steady at 1.41 GB. - -The naive approach is essentially a landmine. It works fine during your dev tests, but in production it'll just eat RAM until the OS kills your process. - -### Latency Issues -When the system is overwhelmed, users feel it. -- Naive: Average latency shot up to 2,063 ms. That's a 100x increase for a 20ms task. -- Adaptive Pool: Kept it around 161 ms under the same pressure. - -The pool rejected 31 million tasks, which sounds bad, but it's actually exactly what you want. It's better to give some users an immediate error than to make every single user wait 2 seconds before the whole server crashes anyway. - -## Comparison Table - -| Metric | Naive Approach | Adaptive Pool | -| :--- | :--- | :--- | -| Reliability | High risk of OOM | Resource-bounded | -| Latency | 2,000ms+ (Unusable) | 160ms (Acceptable) | -| System Health | Near-zero CPU for work | Focused on execution | -| Result | Server crash | System stays up | - -## Final Thoughts -Scaling to high throughput isn't about how many goroutines you can Create. It's about how many you can manage before the overhead kills your system. - -Concurrency is a resource, just like memory or CPU. If you don't bound it, your system isn't production-ready. - -Check out the code on GitHub: [go-adaptive-pool](https://github.com/iyashjayesh/go-adaptive-pool) diff --git a/benchmarks/comparison_bench_test.go b/comparison_bench_test.go similarity index 98% rename from benchmarks/comparison_bench_test.go rename to comparison_bench_test.go index 4cec029..72329d4 100644 --- a/benchmarks/comparison_bench_test.go +++ b/comparison_bench_test.go @@ -1,4 +1,4 @@ -package benchmarks +package adaptivepool_test import ( "context" @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/iyashjayesh/go-adaptive-pool/adaptivepool" + "github.com/iyashjayesh/go-adaptive-pool" ) // BenchmarkNaiveGoroutines benchmarks spawning a goroutine for each job diff --git a/adaptivepool/errors.go b/errors.go similarity index 100% rename from adaptivepool/errors.go rename to errors.go diff --git a/examples/batch_processor/main.go b/examples/batch_processor/main.go index b901fb9..4eab589 100644 --- a/examples/batch_processor/main.go +++ b/examples/batch_processor/main.go @@ -12,7 +12,7 @@ import ( "syscall" "time" - "github.com/iyashjayesh/go-adaptive-pool/adaptivepool" + adaptivepool "github.com/iyashjayesh/go-adaptive-pool" ) func main() { diff --git a/scripts/compare.go b/examples/comparison/main.go similarity index 100% rename from scripts/compare.go rename to examples/comparison/main.go diff --git a/examples/http_server/main.go b/examples/http_server/main.go index e1059f7..413aead 100644 --- a/examples/http_server/main.go +++ b/examples/http_server/main.go @@ -12,7 +12,7 @@ import ( "syscall" "time" - "github.com/iyashjayesh/go-adaptive-pool/adaptivepool" + adaptivepool "github.com/iyashjayesh/go-adaptive-pool" ) var pool adaptivepool.Pool diff --git a/examples/one_million_simulator/with_pool/main.go b/examples/one_million_simulator/with_pool/main.go index dc68bcb..3ab50b4 100644 --- a/examples/one_million_simulator/with_pool/main.go +++ b/examples/one_million_simulator/with_pool/main.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "time" - "github.com/iyashjayesh/go-adaptive-pool/adaptivepool" + adaptivepool "github.com/iyashjayesh/go-adaptive-pool" ) func main() { diff --git a/adaptivepool/internal_types.go b/internal_types.go similarity index 100% rename from adaptivepool/internal_types.go rename to internal_types.go diff --git a/adaptivepool/metrics.go b/metrics.go similarity index 100% rename from adaptivepool/metrics.go rename to metrics.go diff --git a/adaptivepool/options.go b/options.go similarity index 100% rename from adaptivepool/options.go rename to options.go diff --git a/adaptivepool/pool.go b/pool.go similarity index 100% rename from adaptivepool/pool.go rename to pool.go diff --git a/benchmarks/pool_bench_test.go b/pool_bench_test.go similarity index 97% rename from benchmarks/pool_bench_test.go rename to pool_bench_test.go index 1a6d8db..cd7e0f1 100644 --- a/benchmarks/pool_bench_test.go +++ b/pool_bench_test.go @@ -1,4 +1,4 @@ -package benchmarks +package adaptivepool_test import ( "context" @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/iyashjayesh/go-adaptive-pool/adaptivepool" + adaptivepool "github.com/iyashjayesh/go-adaptive-pool" ) // BenchmarkPoolThroughput benchmarks the pool's throughput with different worker counts diff --git a/adaptivepool/pool_test.go b/pool_test.go similarity index 100% rename from adaptivepool/pool_test.go rename to pool_test.go diff --git a/adaptivepool/scaling_test.go b/scaling_test.go similarity index 100% rename from adaptivepool/scaling_test.go rename to scaling_test.go diff --git a/adaptivepool/shutdown.go b/shutdown.go similarity index 100% rename from adaptivepool/shutdown.go rename to shutdown.go diff --git a/adaptivepool/shutdown_test.go b/shutdown_test.go similarity index 100% rename from adaptivepool/shutdown_test.go rename to shutdown_test.go diff --git a/adaptivepool/submit.go b/submit.go similarity index 100% rename from adaptivepool/submit.go rename to submit.go From 3a131e27b9786dadd5eabd1c9f6326a72baa601c Mon Sep 17 00:00:00 2001 From: iyashjayesh Date: Thu, 1 Jan 2026 19:30:29 +0530 Subject: [PATCH 2/2] ci: expand go test and benchmark --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f46e9e2..0498054 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: go mod download - name: Run tests - run: go test -race -v -coverprofile=coverage.out ./adaptivepool/ + run: go test -race -v -coverprofile=coverage.out ./... - name: Upload coverage uses: codecov/codecov-action@v4 @@ -78,7 +78,7 @@ jobs: go-version: '1.23' - name: Run benchmarks - run: go test -bench=. -benchmem -benchtime=5s ./benchmarks/ | tee benchmark.txt + run: go test -bench=. -benchmem -benchtime=5s ./... | tee benchmark.txt - name: Upload benchmark results uses: actions/upload-artifact@v4