Add memory optimization improvements for reduced GC pressure (v0.36.16)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add buffer pool (pkg/database/bufpool) with SmallPool (64B) and MediumPool (1KB) for reusing bytes.Buffer instances on hot paths - Fix escape analysis in index types (uint40, letter, word) by using fixed-size arrays instead of make() calls that escape to heap - Add handler concurrency limiter (ORLY_MAX_HANDLERS_PER_CONN, default 100) to prevent unbounded goroutine growth under WebSocket load - Add pre-allocation hints to Uint40s.Union/Intersection/Difference methods - Update compact_event.go, save-event.go, serial_cache.go, and get-indexes-for-event.go to use pooled buffers Files modified: - app/config/config.go: Add MaxHandlersPerConnection config - app/handle-websocket.go: Initialize handler semaphore - app/listener.go: Add semaphore acquire/release in messageProcessor - pkg/database/bufpool/pool.go: New buffer pool package - pkg/database/compact_event.go: Use buffer pool, fix escape analysis - pkg/database/get-indexes-for-event.go: Reuse single buffer for all indexes - pkg/database/indexes/types/letter.go: Fixed array in UnmarshalRead - pkg/database/indexes/types/uint40.go: Fixed arrays, pre-allocation hints - pkg/database/indexes/types/word.go: Fixed array in UnmarshalRead - pkg/database/save-event.go: Use buffer pool for key encoding - pkg/database/serial_cache.go: Use buffer pool for lookups 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,9 @@ type C struct {
|
||||
SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"max pubkeys to cache for compact event storage (default: 100000, ~3.2MB memory)"`
|
||||
SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"max event IDs to cache for compact event storage (default: 500000, ~16MB memory)"`
|
||||
|
||||
// Connection concurrency control
|
||||
MaxHandlersPerConnection int `env:"ORLY_MAX_HANDLERS_PER_CONN" default:"100" usage:"max concurrent message handlers per WebSocket connection (limits goroutine growth under load)"`
|
||||
|
||||
// Adaptive rate limiting (PID-controlled)
|
||||
RateLimitEnabled bool `env:"ORLY_RATE_LIMIT_ENABLED" default:"true" usage:"enable adaptive PID-controlled rate limiting for database operations"`
|
||||
RateLimitTargetMB int `env:"ORLY_RATE_LIMIT_TARGET_MB" default:"0" usage:"target memory limit in MB (0=auto-detect: 66% of available, min 500MB)"`
|
||||
|
||||
@@ -86,6 +86,12 @@ whitelist:
|
||||
})
|
||||
|
||||
defer conn.Close()
|
||||
// Determine handler semaphore size from config
|
||||
handlerSemSize := s.Config.MaxHandlersPerConnection
|
||||
if handlerSemSize <= 0 {
|
||||
handlerSemSize = 100 // Default if not configured
|
||||
}
|
||||
|
||||
listener := &Listener{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
@@ -98,6 +104,7 @@ whitelist:
|
||||
writeDone: make(chan struct{}),
|
||||
messageQueue: make(chan messageRequest, 100), // Buffered channel for message processing
|
||||
processingDone: make(chan struct{}),
|
||||
handlerSem: make(chan struct{}, handlerSemSize), // Limits concurrent handlers
|
||||
subscriptions: make(map[string]context.CancelFunc),
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ type Listener struct {
|
||||
messageQueue chan messageRequest // Buffered channel for message processing
|
||||
processingDone chan struct{} // Closed when message processor exits
|
||||
handlerWg sync.WaitGroup // Tracks spawned message handler goroutines
|
||||
handlerSem chan struct{} // Limits concurrent message handlers per connection
|
||||
authProcessing sync.RWMutex // Ensures AUTH completes before other messages check authentication
|
||||
// Flow control counters (atomic for concurrent access)
|
||||
droppedMessages atomic.Int64 // Messages dropped due to full queue
|
||||
@@ -240,9 +241,20 @@ func (l *Listener) messageProcessor() {
|
||||
// Not AUTH - unlock immediately and process concurrently
|
||||
// The next message can now be dequeued (possibly another non-AUTH to process concurrently)
|
||||
l.authProcessing.Unlock()
|
||||
|
||||
// Acquire semaphore to limit concurrent handlers (blocking with context awareness)
|
||||
select {
|
||||
case l.handlerSem <- struct{}{}:
|
||||
// Semaphore acquired
|
||||
case <-l.ctx.Done():
|
||||
return
|
||||
}
|
||||
l.handlerWg.Add(1)
|
||||
go func(data []byte, remote string) {
|
||||
defer l.handlerWg.Done()
|
||||
defer func() {
|
||||
<-l.handlerSem // Release semaphore
|
||||
l.handlerWg.Done()
|
||||
}()
|
||||
l.HandleMessage(data, remote)
|
||||
}(req.data, req.remote)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user