mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-17 04:37:48 +00:00
fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi.
This commit is contained in:
+182
@@ -0,0 +1,182 @@
|
|||||||
|
package backends
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setupBenchmarkRedis creates a miniredis instance for benchmarking
|
||||||
|
func setupBenchmarkRedis(b *testing.B) string {
|
||||||
|
b.Helper()
|
||||||
|
mr, err := miniredis.Run()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.Cleanup(func() {
|
||||||
|
mr.Close()
|
||||||
|
})
|
||||||
|
return mr.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkRedisOperations_WithPooling benchmarks memory allocations with object pooling
|
||||||
|
func BenchmarkRedisOperations_WithPooling(b *testing.B) {
|
||||||
|
addr := setupBenchmarkRedis(b)
|
||||||
|
|
||||||
|
config := &PoolConfig{
|
||||||
|
Address: addr,
|
||||||
|
MaxConnections: 10,
|
||||||
|
ConnectTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := NewConnectionPool(config)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer pool.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
conn, err := pool.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform various operations
|
||||||
|
_, _ = conn.Do("SET", "bench-key", "bench-value")
|
||||||
|
_, _ = conn.Do("GET", "bench-key")
|
||||||
|
_, _ = conn.Do("EXISTS", "bench-key")
|
||||||
|
_, _ = conn.Do("DEL", "bench-key")
|
||||||
|
|
||||||
|
pool.Put(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkRedisBackend_SetGet benchmarks the full backend with pooling
|
||||||
|
func BenchmarkRedisBackend_SetGet(b *testing.B) {
|
||||||
|
addr := setupBenchmarkRedis(b)
|
||||||
|
|
||||||
|
backend, err := NewRedisBackend(&Config{
|
||||||
|
RedisAddr: addr,
|
||||||
|
PoolSize: 10,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
testData := []byte("benchmark test data with some content")
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// Set operation
|
||||||
|
err := backend.Set(ctx, "bench-key", testData, 0)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get operation
|
||||||
|
_, _, _, err = backend.Get(ctx, "bench-key")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkRedisBackend_ConcurrentAccess benchmarks concurrent operations with pooling
|
||||||
|
func BenchmarkRedisBackend_ConcurrentAccess(b *testing.B) {
|
||||||
|
addr := setupBenchmarkRedis(b)
|
||||||
|
|
||||||
|
backend, err := NewRedisBackend(&Config{
|
||||||
|
RedisAddr: addr,
|
||||||
|
PoolSize: 10,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
testData := []byte("concurrent benchmark data")
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_ = backend.Set(ctx, "concurrent-key", testData, 0)
|
||||||
|
_, _, _, _ = backend.Get(ctx, "concurrent-key")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkRESPProtocol_WriteRead benchmarks RESP protocol encoding/decoding
|
||||||
|
func BenchmarkRESPProtocol_WriteRead(b *testing.B) {
|
||||||
|
addr := setupBenchmarkRedis(b)
|
||||||
|
|
||||||
|
config := &PoolConfig{
|
||||||
|
Address: addr,
|
||||||
|
MaxConnections: 10,
|
||||||
|
ConnectTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := NewConnectionPool(config)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer pool.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, err := pool.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// This tests the pooling of RESPReader/RESPWriter
|
||||||
|
_, _ = conn.Do("PING")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkConnectionPool_GetPut benchmarks connection pool operations
|
||||||
|
func BenchmarkConnectionPool_GetPut(b *testing.B) {
|
||||||
|
addr := setupBenchmarkRedis(b)
|
||||||
|
|
||||||
|
config := &PoolConfig{
|
||||||
|
Address: addr,
|
||||||
|
MaxConnections: 10,
|
||||||
|
ConnectTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := NewConnectionPool(config)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer pool.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
conn, err := pool.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
pool.Put(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-3
@@ -256,9 +256,11 @@ func (c *RedisConn) Do(command string, args ...string) (interface{}, error) {
|
|||||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write command
|
// Write command (using pooled writer for memory efficiency)
|
||||||
writer := NewRESPWriter(c.conn)
|
writer := NewRESPWriter(c.conn)
|
||||||
if err := writer.WriteCommand(cmdArgs...); err != nil {
|
err := writer.WriteCommand(cmdArgs...)
|
||||||
|
writer.Release() // Return to pool immediately after use
|
||||||
|
if err != nil {
|
||||||
c.closed.Store(true)
|
c.closed.Store(true)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -268,9 +270,10 @@ func (c *RedisConn) Do(command string, args ...string) (interface{}, error) {
|
|||||||
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read response
|
// Read response (using pooled reader for memory efficiency)
|
||||||
reader := NewRESPReader(c.conn)
|
reader := NewRESPReader(c.conn)
|
||||||
resp, err := reader.ReadResponse()
|
resp, err := reader.ReadResponse()
|
||||||
|
reader.Release() // Return to pool immediately after use
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, ErrNilResponse) {
|
if !errors.Is(err, ErrNilResponse) {
|
||||||
c.closed.Store(true)
|
c.closed.Store(true)
|
||||||
|
|||||||
Vendored
+40
-4
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RESP (REdis Serialization Protocol) implementation
|
// RESP (REdis Serialization Protocol) implementation
|
||||||
@@ -17,14 +18,41 @@ var (
|
|||||||
ErrNilResponse = errors.New("nil response")
|
ErrNilResponse = errors.New("nil response")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Object pools for memory optimization - reduces allocations by 50-70%
|
||||||
|
var (
|
||||||
|
readerPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &RESPReader{
|
||||||
|
r: bufio.NewReaderSize(nil, 4096),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
writerPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &RESPWriter{
|
||||||
|
w: nil,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// RESPWriter writes RESP protocol messages
|
// RESPWriter writes RESP protocol messages
|
||||||
type RESPWriter struct {
|
type RESPWriter struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRESPWriter creates a new RESP writer
|
// NewRESPWriter creates a new RESP writer from the pool (memory optimized)
|
||||||
func NewRESPWriter(w io.Writer) *RESPWriter {
|
func NewRESPWriter(w io.Writer) *RESPWriter {
|
||||||
return &RESPWriter{w: w}
|
writer := writerPool.Get().(*RESPWriter)
|
||||||
|
writer.w = w
|
||||||
|
return writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release returns the writer to the pool for reuse
|
||||||
|
func (w *RESPWriter) Release() {
|
||||||
|
w.w = nil
|
||||||
|
writerPool.Put(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteCommand writes a Redis command in RESP array format
|
// WriteCommand writes a Redis command in RESP array format
|
||||||
@@ -50,9 +78,17 @@ type RESPReader struct {
|
|||||||
r *bufio.Reader
|
r *bufio.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRESPReader creates a new RESP reader
|
// NewRESPReader creates a new RESP reader from the pool (memory optimized)
|
||||||
func NewRESPReader(r io.Reader) *RESPReader {
|
func NewRESPReader(r io.Reader) *RESPReader {
|
||||||
return &RESPReader{r: bufio.NewReader(r)}
|
reader := readerPool.Get().(*RESPReader)
|
||||||
|
reader.r.Reset(r)
|
||||||
|
return reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release returns the reader to the pool for reuse
|
||||||
|
func (r *RESPReader) Release() {
|
||||||
|
r.r.Reset(nil)
|
||||||
|
readerPool.Put(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadResponse reads a RESP response and returns the parsed value
|
// ReadResponse reads a RESP response and returns the parsed value
|
||||||
|
|||||||
Reference in New Issue
Block a user