Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: all build test lint bench run-http run-batch help clean
.PHONY: all build test lint bench run-autoconfig run-http run-batch help clean

# Go parameters
GOCMD=go
Expand All @@ -19,6 +19,7 @@ help:
@echo " test Run unit tests with race detector and coverage"
@echo " lint Run golangci-lint"
@echo " bench Run all benchmarks"
@echo " run-autoconfig Run the auto-configuration example"
@echo " run-http Run the HTTP server example"
@echo " run-batch Run the batch processor example"
@echo " clean Clean build artifacts and coverage files"
Expand All @@ -41,6 +42,10 @@ bench:
@echo "Running benchmarks..."
$(GOTEST) -bench=. -benchmem ./...

run-autoconfig:
@echo "Running auto-configuration example..."
$(GOCMD) run examples/autoconfig/main.go

run-http:
@echo "Running HTTP server example..."
$(GOCMD) run examples/http_server/main.go
Expand Down
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,51 @@ func main() {

## Configuration Options

### Auto-Configuration (Recommended)

The pool provides intelligent auto-configuration based on system resources and workload profiles:

```go
// Simple profile-based configuration
pool, err := adaptivepool.New(
adaptivepool.WithAutoConfig(adaptivepool.ProfileAPIServer),
)

// Advanced system-aware configuration
pool, err := adaptivepool.New(
adaptivepool.WithSystemAwareConfig(adaptivepool.SystemAwareConfig{
WorkloadType: adaptivepool.IOBound,
TargetLatencyMs: 500,
AvgJobMemoryBytes: 50 * 1024, // 50KB per job
MemoryLimitPercent: 0.2, // Use 20% of available memory
}),
)

// Get configuration suggestions
config := adaptivepool.SuggestConfig(adaptivepool.IOBound)
fmt.Printf("Suggested min workers: %d\n", config.MinWorkers())
fmt.Printf("Suggested max workers: %d\n", config.MaxWorkers())
fmt.Printf("Suggested queue size: %d\n", config.QueueSize())
```

**Available Workload Profiles:**

| Profile | Use Case | Min Workers | Max Workers | Queue Size |
|---------|----------|-------------|-------------|------------|
| `ProfileAPIServer` | I/O bound API servers | 2x CPU | 10x CPU | 20x Max Workers |
| `ProfileCPUIntensive` | CPU-bound tasks | 1x CPU | 2x CPU | 5x Max Workers |
| `ProfileBatchProcessor` | Batch processing | 2x CPU | 4x CPU | 50x Max Workers |

**Workload Types for System-Aware Config:**

- `IOBound` - Tasks that spend most time waiting on I/O (network, disk, databases)
- `CPUBound` - Compute-intensive tasks that max out CPU
- `Mixed` - Workloads with both I/O and CPU characteristics

### Manual Configuration

For fine-grained control, configure parameters manually:

```go
pool, err := adaptivepool.New(
// Minimum workers (default: 1)
Expand All @@ -112,6 +157,18 @@ pool, err := adaptivepool.New(
)
```

### Combining Auto-Config with Manual Overrides

You can start with a profile and override specific settings:

```go
pool, err := adaptivepool.New(
adaptivepool.WithAutoConfig(adaptivepool.ProfileAPIServer),
adaptivepool.WithMinWorkers(8), // Override min workers
adaptivepool.WithQueueSize(10000), // Override queue size
)
```

## Backpressure Handling

The pool enforces backpressure when the queue is full:
Expand Down Expand Up @@ -169,6 +226,19 @@ Shutdown behavior:

## Examples

### Auto-Configuration

See [examples/autoconfig](examples/autoconfig/main.go) for demonstrations of:
- Profile-based auto-configuration
- System-aware configuration with memory constraints
- Configuration suggestions for different workload types
- Combining auto-config with manual overrides

```bash
cd examples/autoconfig
go run main.go
```

### HTTP Server with Backpressure

See [examples/http_server](examples/http_server/main.go) for a complete HTTP server that:
Expand Down
166 changes: 166 additions & 0 deletions autoconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package adaptivepool

import (
"runtime"
)

// WorkloadProfile represents predefined workload types
type WorkloadProfile int

const (
// ProfileAPIServer is optimized for I/O bound API servers
ProfileAPIServer WorkloadProfile = iota
// ProfileCPUIntensive is optimized for CPU-bound tasks
ProfileCPUIntensive
// ProfileBatchProcessor is optimized for batch processing workloads
ProfileBatchProcessor
)

// WorkloadType represents the nature of the workload
type WorkloadType int

const (
// IOBound workloads spend most time waiting on I/O
IOBound WorkloadType = iota
// CPUBound workloads are compute-intensive
CPUBound
// Mixed workloads have both I/O and CPU characteristics
Mixed
)

// SystemAwareConfig provides advanced auto-configuration based on system resources
type SystemAwareConfig struct {
// WorkloadType specifies the nature of the workload
WorkloadType WorkloadType
// TargetLatencyMs is the target latency in milliseconds
TargetLatencyMs int
// AvgJobMemoryBytes is the average memory consumed per job
AvgJobMemoryBytes int64
// MemoryLimitPercent is the percentage of available memory to use (0.0 to 1.0)
MemoryLimitPercent float64
}

// systemResources holds detected system resource information
type systemResources struct {
numCPU int
totalMemory uint64
availableMemory uint64
}

// detectSystemResources detects available system resources
func detectSystemResources() systemResources {
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)

return systemResources{
numCPU: runtime.NumCPU(),
totalMemory: memStats.Sys,
availableMemory: memStats.Sys - memStats.Alloc,
}
}

// applyProfile applies a predefined workload profile to the config
func applyProfile(config *Config, profile WorkloadProfile) {
resources := detectSystemResources()
numCPU := resources.numCPU

switch profile {
case ProfileAPIServer:
// I/O bound: more workers to handle concurrent requests
config.minWorkers = numCPU * 2
config.maxWorkers = numCPU * 10
config.queueSize = config.maxWorkers * 20
config.scaleUpThreshold = 0.6

case ProfileCPUIntensive:
// CPU bound: workers close to CPU count
config.minWorkers = numCPU
config.maxWorkers = numCPU * 2
config.queueSize = config.maxWorkers * 5
config.scaleUpThreshold = 0.8

case ProfileBatchProcessor:
// Batch processing: moderate workers, large queue
config.minWorkers = numCPU * 2
config.maxWorkers = numCPU * 4
config.queueSize = config.maxWorkers * 50
config.scaleUpThreshold = 0.7
}
}

// applySystemAwareConfig applies system-aware configuration
func applySystemAwareConfig(config *Config, sysConfig SystemAwareConfig) {
resources := detectSystemResources()
numCPU := resources.numCPU

// Set defaults if not provided
if sysConfig.MemoryLimitPercent <= 0 || sysConfig.MemoryLimitPercent > 1.0 {
sysConfig.MemoryLimitPercent = 0.2 // Default to 20% of available memory
}

// Calculate worker counts based on workload type
switch sysConfig.WorkloadType {
case IOBound:
config.minWorkers = numCPU * 2
config.maxWorkers = numCPU * 8
config.scaleUpThreshold = 0.6

case CPUBound:
config.minWorkers = numCPU
config.maxWorkers = numCPU * 2
config.scaleUpThreshold = 0.8

case Mixed:
config.minWorkers = numCPU
config.maxWorkers = numCPU * 4
config.scaleUpThreshold = 0.7
}

// Calculate queue size based on memory constraints
if sysConfig.AvgJobMemoryBytes > 0 {
availableForQueue := float64(resources.availableMemory) * sysConfig.MemoryLimitPercent
maxQueueByMemory := int(availableForQueue / float64(sysConfig.AvgJobMemoryBytes))

// Use memory-based calculation but ensure reasonable bounds
minQueueSize := config.maxWorkers * 5
maxQueueSize := config.maxWorkers * 100

if maxQueueByMemory < minQueueSize {
config.queueSize = minQueueSize
} else if maxQueueByMemory > maxQueueSize {
config.queueSize = maxQueueSize
} else {
config.queueSize = maxQueueByMemory
}
} else {
// Default queue sizing based on workload type
switch sysConfig.WorkloadType {
case IOBound:
config.queueSize = config.maxWorkers * 20
case CPUBound:
config.queueSize = config.maxWorkers * 5
case Mixed:
config.queueSize = config.maxWorkers * 10
}
}

// Adjust scaling behavior based on target latency
if sysConfig.TargetLatencyMs > 0 {
// Lower latency targets need more aggressive scaling
if sysConfig.TargetLatencyMs < 100 {
config.scaleUpThreshold = 0.5
} else if sysConfig.TargetLatencyMs < 500 {
config.scaleUpThreshold = 0.6
}
}
}

// SuggestConfig analyzes the system and returns suggested configuration values
func SuggestConfig(workloadType WorkloadType) *Config {
config := defaultConfig()
applySystemAwareConfig(config, SystemAwareConfig{
WorkloadType: workloadType,
MemoryLimitPercent: 0.2,
})
return config
}
Loading
Loading