Browse Source
- 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>main
13 changed files with 204 additions and 70 deletions
Binary file not shown.
@ -0,0 +1,94 @@ |
|||||||
|
//go:build !(js && wasm)
|
||||||
|
|
||||||
|
// Package bufpool provides buffer pools for reducing GC pressure in hot paths.
|
||||||
|
//
|
||||||
|
// Two pool sizes are provided:
|
||||||
|
// - SmallPool (64 bytes): For index keys, serial encoding, short buffers
|
||||||
|
// - MediumPool (1KB): For event encoding, larger serialization buffers
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// buf := bufpool.GetSmall()
|
||||||
|
// defer bufpool.PutSmall(buf)
|
||||||
|
// // Use buf...
|
||||||
|
// // IMPORTANT: Copy buf.Bytes() before Put if data is needed after
|
||||||
|
package bufpool |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// SmallBufferSize for index keys (8-64 bytes typical)
|
||||||
|
SmallBufferSize = 64 |
||||||
|
|
||||||
|
// MediumBufferSize for event encoding (300-1000 bytes typical)
|
||||||
|
MediumBufferSize = 1024 |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// smallPool for index keys and short encodings
|
||||||
|
smallPool = sync.Pool{ |
||||||
|
New: func() interface{} { |
||||||
|
return bytes.NewBuffer(make([]byte, 0, SmallBufferSize)) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// mediumPool for event encoding and larger buffers
|
||||||
|
mediumPool = sync.Pool{ |
||||||
|
New: func() interface{} { |
||||||
|
return bytes.NewBuffer(make([]byte, 0, MediumBufferSize)) |
||||||
|
}, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// GetSmall returns a small buffer (64 bytes) from the pool.
|
||||||
|
// Call PutSmall when done to return it to the pool.
|
||||||
|
//
|
||||||
|
// WARNING: Copy buf.Bytes() before calling PutSmall if the data
|
||||||
|
// is needed after the buffer is returned to the pool.
|
||||||
|
func GetSmall() *bytes.Buffer { |
||||||
|
return smallPool.Get().(*bytes.Buffer) |
||||||
|
} |
||||||
|
|
||||||
|
// PutSmall returns a small buffer to the pool.
|
||||||
|
// The buffer is reset before being returned.
|
||||||
|
func PutSmall(buf *bytes.Buffer) { |
||||||
|
if buf == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
buf.Reset() |
||||||
|
smallPool.Put(buf) |
||||||
|
} |
||||||
|
|
||||||
|
// GetMedium returns a medium buffer (1KB) from the pool.
|
||||||
|
// Call PutMedium when done to return it to the pool.
|
||||||
|
//
|
||||||
|
// WARNING: Copy buf.Bytes() before calling PutMedium if the data
|
||||||
|
// is needed after the buffer is returned to the pool.
|
||||||
|
func GetMedium() *bytes.Buffer { |
||||||
|
return mediumPool.Get().(*bytes.Buffer) |
||||||
|
} |
||||||
|
|
||||||
|
// PutMedium returns a medium buffer to the pool.
|
||||||
|
// The buffer is reset before being returned.
|
||||||
|
func PutMedium(buf *bytes.Buffer) { |
||||||
|
if buf == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
buf.Reset() |
||||||
|
mediumPool.Put(buf) |
||||||
|
} |
||||||
|
|
||||||
|
// CopyBytes copies the buffer contents to a new slice.
|
||||||
|
// Use this before returning the buffer to the pool if the
|
||||||
|
// data needs to persist.
|
||||||
|
func CopyBytes(buf *bytes.Buffer) []byte { |
||||||
|
if buf == nil || buf.Len() == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
result := make([]byte, buf.Len()) |
||||||
|
copy(result, buf.Bytes()) |
||||||
|
return result |
||||||
|
} |
||||||
Loading…
Reference in new issue