Skip to content

🧹 chore: minor performance findings (P2 cleanup pass) #4364

@pageton

Description

@pageton

Summary

Minor performance findings from a full architecture review. Individual impact is low, but collectively worth a cleanup pass.

1. Cache heap unbounded growth

File: middleware/cache/heap.go:22
entries []heapEntry has no hard cap when MaxBytes config is 0 (default). Risk: OOM in deployments that don't set MaxBytes.

2. CookieJar unbounded map

File: client/cookiejar.go:47
hostCookies map[string][]storedCookie has no size limit or eviction. Slow memory growth in long-lived clients.

3. Logger timestamp goroutine has no shutdown

File: middleware/logger/logger.go:43-48
The timestamp update goroutine runs in a for {} time.Sleep() loop with no context cancellation or done channel. Prevents clean test teardown and graceful shutdown.

Fix: Accept a context.Context or done channel, select alongside time.Sleep.

4. SSE heartbeat goroutine cleanup latency

File: middleware/sse/sse.go:208-221
Heartbeat goroutine uses time.Sleep instead of select on the done channel. Connection teardown blocks for up to HeartbeatInterval.

Fix: Replace time.Sleep with time.NewTimer + select on done channel.

5. Memory storage GC goroutine potential deadlock

File: internal/storage/memory/memory.go:151
Unbuffered done channel send. If GC goroutine has already exited after Close(), the send blocks forever.

Fix: Use a buffered channel (make(chan struct{}, 1)) or select with default.

6. BaseURL() allocation

File: ctx.go:113-118
First BaseURL() call per request allocates 32B (documented with TODO comment: "53.8 ns/op 32 B/op 1 allocs/op").

7. Proxy middleware uses RLock for client reads

File: middleware/proxy/proxy.go:122,184
RLock on every proxied request for global client variable. Use atomic.Value for lock-free reads.

8. Client fileBufPool retains 1MB buffers

File: client/hooks.go:21-26
Pool creates []byte slices of 1MB. Under load, retains up to GOMAXPROCS × 1MB.

9. Shared state defensive copy

File: shared_state.go:115,335
append([]byte(nil), data...) copies entire value on every read. Correct for safety but costly for large values. Consider documenting or adding a GetRef() method.

10. Route() fallback allocates empty slices

File: ctx.go:364-371
make([]Handler, 0) and make([]string, 0) on fallback path. Use package-level pre-allocated empty slices.


Identified during a full performance architecture review of the Fiber codebase.

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions