Add memory optimization improvements for reduced GC pressure (v0.36.16)
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:
2025-12-25 06:03:53 +01:00
parent 24383ef1f4
commit eddd05eabf
13 changed files with 204 additions and 70 deletions

View File

@@ -35,8 +35,8 @@ func (p *Letter) MarshalWrite(w io.Writer) (err error) {
}
func (p *Letter) UnmarshalRead(r io.Reader) (err error) {
val := make([]byte, 1)
if _, err = r.Read(val); chk.E(err) {
var val [1]byte // Fixed array avoids heap escape
if _, err = r.Read(val[:]); chk.E(err) {
return
}
p.val = val[0]

View File

@@ -46,23 +46,23 @@ func (c *Uint40) MarshalWrite(w io.Writer) (err error) {
if c.value > MaxUint40 {
return errors.New("value exceeds 40-bit range")
}
// Buffer for the 5 bytes
buf := make([]byte, 5)
// Fixed array avoids heap escape
var buf [5]byte
// Write the upper 5 bytes (ignoring the most significant 3 bytes of uint64)
buf[0] = byte((c.value >> 32) & 0xFF) // Most significant byte
buf[1] = byte((c.value >> 24) & 0xFF)
buf[2] = byte((c.value >> 16) & 0xFF)
buf[3] = byte((c.value >> 8) & 0xFF)
buf[4] = byte(c.value & 0xFF) // Least significant byte
_, err = w.Write(buf)
_, err = w.Write(buf[:])
return err
}
// UnmarshalRead reads 5 bytes from the provided reader and decodes it into a 40-bit unsigned integer.
func (c *Uint40) UnmarshalRead(r io.Reader) (err error) {
// Buffer for the 5 bytes
buf := make([]byte, 5)
_, err = r.Read(buf)
// Fixed array avoids heap escape
var buf [5]byte
_, err = r.Read(buf[:])
if chk.E(err) {
return err
}
@@ -81,8 +81,9 @@ type Uint40s []*Uint40
// Union computes the union of the current Uint40s slice with another Uint40s slice. The result
// contains all unique elements from both slices.
func (s Uint40s) Union(other Uint40s) Uint40s {
valueMap := make(map[uint64]bool)
var result Uint40s
totalCap := len(s) + len(other)
valueMap := make(map[uint64]bool, totalCap)
result := make(Uint40s, 0, totalCap) // Pre-allocate for worst case
// Add elements from the current Uint40s slice to the result
for _, item := range s {
@@ -108,8 +109,13 @@ func (s Uint40s) Union(other Uint40s) Uint40s {
// Intersection computes the intersection of the current Uint40s slice with another Uint40s
// slice. The result contains only the elements that exist in both slices.
func (s Uint40s) Intersection(other Uint40s) Uint40s {
valueMap := make(map[uint64]bool)
var result Uint40s
// Result can be at most the size of the smaller slice
smallerLen := len(s)
if len(other) < smallerLen {
smallerLen = len(other)
}
valueMap := make(map[uint64]bool, len(other))
result := make(Uint40s, 0, smallerLen) // Pre-allocate for worst case
// Add all elements from the other Uint40s slice to the map
for _, item := range other {
@@ -131,8 +137,8 @@ func (s Uint40s) Intersection(other Uint40s) Uint40s {
// The result contains only the elements that are in the current slice but not in the other
// slice.
func (s Uint40s) Difference(other Uint40s) Uint40s {
valueMap := make(map[uint64]bool)
var result Uint40s
valueMap := make(map[uint64]bool, len(other))
result := make(Uint40s, 0, len(s)) // Pre-allocate for worst case (no overlap)
// Mark all elements in the other Uint40s slice
for _, item := range other {

View File

@@ -37,12 +37,12 @@ func (w *Word) MarshalWrite(wr io.Writer) (err error) {
// UnmarshalRead reads the word from the reader, stopping at the zero-byte marker
func (w *Word) UnmarshalRead(r io.Reader) error {
buf := new(bytes.Buffer)
tmp := make([]byte, 1)
var tmp [1]byte // Fixed array avoids heap escape
foundEndMarker := false
// Read bytes until the zero byte is encountered
for {
n, err := r.Read(tmp)
n, err := r.Read(tmp[:])
if n > 0 {
if tmp[0] == 0x00 { // Stop on encountering the zero-byte marker
foundEndMarker = true