@@ -27,7 +27,7 @@ import (
2727
2828 "github.com/go-redis/redis/v8"
2929 "github.com/rs/zerolog/log"
30-
30+
3131 "github.com/optimizely/agent/pkg/metrics"
3232)
3333
@@ -37,7 +37,7 @@ type RedisStreams struct {
3737 Password string
3838 Database int
3939 // Stream configuration
40- MaxLen int64
40+ MaxLen int64
4141 ConsumerGroup string
4242 ConsumerName string
4343 // Batching configuration
@@ -55,7 +55,7 @@ type RedisStreams struct {
5555
5656func (r * RedisStreams ) Publish (ctx context.Context , channel string , message interface {}) error {
5757 streamName := r .getStreamName (channel )
58-
58+
5959 // Convert message to string for consistent handling
6060 var messageStr string
6161 switch v := message .(type ) {
@@ -71,7 +71,7 @@ func (r *RedisStreams) Publish(ctx context.Context, channel string, message inte
7171 }
7272 messageStr = string (jsonBytes )
7373 }
74-
74+
7575 // Add message to stream with automatic ID generation
7676 args := & redis.XAddArgs {
7777 Stream : streamName ,
@@ -144,34 +144,34 @@ func (r *RedisStreams) Subscribe(ctx context.Context, channel string) (chan stri
144144 Consumer : consumerName ,
145145 Streams : []string {streamName , ">" },
146146 Count : int64 (batchSize - len (batch )), // Read up to remaining batch size
147- Block : 100 * time .Millisecond , // Short block to allow flush checking
147+ Block : 100 * time .Millisecond , // Short block to allow flush checking
148148 }).Result ()
149149
150150 if err != nil {
151151 if err == redis .Nil {
152152 continue // No messages, continue polling
153153 }
154-
154+
155155 // Handle connection errors with exponential backoff reconnection
156156 if r .isConnectionError (err ) {
157157 r .incrementCounter ("connection.error" )
158158 log .Warn ().Err (err ).Msg ("Redis connection error, attempting reconnection" )
159-
159+
160160 // Apply exponential backoff for reconnection
161161 if time .Since (lastReconnect ) > reconnectDelay {
162162 r .incrementCounter ("connection.reconnect_attempt" )
163163 client .Close ()
164164 client = r .createClient ()
165165 lastReconnect = time .Now ()
166-
166+
167167 // Recreate consumer group after reconnection
168168 if groupErr := r .createConsumerGroupWithRetry (ctx , client , streamName , consumerGroup ); groupErr != nil {
169169 r .incrementCounter ("connection.group_recreate_error" )
170170 log .Error ().Err (groupErr ).Msg ("Failed to recreate consumer group after reconnection" )
171171 } else {
172172 r .incrementCounter ("connection.reconnect_success" )
173173 }
174-
174+
175175 // Increase reconnect delay with exponential backoff
176176 reconnectDelay = time .Duration (math .Min (float64 (reconnectDelay * 2 ), float64 (maxReconnectDelay )))
177177 } else {
@@ -197,12 +197,12 @@ func (r *RedisStreams) Subscribe(ctx context.Context, channel string) (chan stri
197197 if data , ok := message .Values ["data" ].(string ); ok {
198198 batch = append (batch , data )
199199 messageCount ++
200-
200+
201201 // Acknowledge the message with retry
202202 if ackErr := r .acknowledgeMessage (ctx , client , streamName , consumerGroup , message .ID ); ackErr != nil {
203203 log .Warn ().Err (ackErr ).Str ("messageID" , message .ID ).Msg ("Failed to acknowledge message" )
204204 }
205-
205+
206206 // Send batch if it's full
207207 if len (batch ) >= batchSize {
208208 r .incrementCounter ("batch.sent" )
@@ -213,7 +213,7 @@ func (r *RedisStreams) Subscribe(ctx context.Context, channel string) (chan stri
213213 }
214214 }
215215 }
216-
216+
217217 // Track successful message reads
218218 if messageCount > 0 {
219219 r .incrementCounter ("messages.read" )
@@ -237,7 +237,6 @@ func (r *RedisStreams) sendBatch(ch chan string, batch []string, ctx context.Con
237237 }
238238}
239239
240-
241240// Helper methods
242241func (r * RedisStreams ) getStreamName (channel string ) string {
243242 return fmt .Sprintf ("stream:%s" , channel )
@@ -318,13 +317,13 @@ func (r *RedisStreams) executeWithRetry(ctx context.Context, operation func(clie
318317 maxRetries := r .getMaxRetries ()
319318 retryDelay := r .getRetryDelay ()
320319 maxRetryDelay := r .getMaxRetryDelay ()
321-
320+
322321 var lastErr error
323322 for attempt := 0 ; attempt <= maxRetries ; attempt ++ {
324323 client := r .createClient ()
325324 err := operation (client )
326325 client .Close ()
327-
326+
328327 if err == nil {
329328 // Record successful operation metrics
330329 r .incrementCounter ("operations.success" )
@@ -334,22 +333,22 @@ func (r *RedisStreams) executeWithRetry(ctx context.Context, operation func(clie
334333 }
335334 return nil // Success
336335 }
337-
336+
338337 lastErr = err
339338 r .incrementCounter ("operations.error" )
340-
339+
341340 // Don't retry on non-recoverable errors
342341 if ! r .isRetryableError (err ) {
343342 r .incrementCounter ("errors.non_retryable" )
344343 return fmt .Errorf ("non-retryable error: %w" , err )
345344 }
346-
345+
347346 // Don't sleep after the last attempt
348347 if attempt < maxRetries {
349348 r .incrementCounter ("retries.attempt" )
350349 // Calculate delay with exponential backoff
351350 delay := time .Duration (math .Min (float64 (retryDelay )* math .Pow (2 , float64 (attempt )), float64 (maxRetryDelay )))
352-
351+
353352 select {
354353 case <- ctx .Done ():
355354 r .incrementCounter ("operations.canceled" )
@@ -359,7 +358,7 @@ func (r *RedisStreams) executeWithRetry(ctx context.Context, operation func(clie
359358 }
360359 }
361360 }
362-
361+
363362 r .incrementCounter ("retries.exhausted" )
364363 return fmt .Errorf ("operation failed after %d retries: %w" , maxRetries , lastErr )
365364}
@@ -379,7 +378,7 @@ func (r *RedisStreams) createConsumerGroupWithRetry(ctx context.Context, _ *redi
379378func (r * RedisStreams ) acknowledgeMessage (ctx context.Context , client * redis.Client , streamName , consumerGroup , messageID string ) error {
380379 maxRetries := 2 // Fewer retries for ACK operations
381380 retryDelay := 50 * time .Millisecond
382-
381+
383382 var lastErr error
384383 for attempt := 0 ; attempt <= maxRetries ; attempt ++ {
385384 err := client .XAck (ctx , streamName , consumerGroup , messageID ).Err ()
@@ -390,16 +389,16 @@ func (r *RedisStreams) acknowledgeMessage(ctx context.Context, client *redis.Cli
390389 }
391390 return nil // Success
392391 }
393-
392+
394393 lastErr = err
395394 r .incrementCounter ("ack.error" )
396-
395+
397396 // Don't retry on non-recoverable errors
398397 if ! r .isRetryableError (err ) {
399398 r .incrementCounter ("ack.non_retryable_error" )
400399 return fmt .Errorf ("non-retryable ACK error: %w" , err )
401400 }
402-
401+
403402 // Don't sleep after the last attempt
404403 if attempt < maxRetries {
405404 r .incrementCounter ("ack.retry_attempt" )
@@ -411,7 +410,7 @@ func (r *RedisStreams) acknowledgeMessage(ctx context.Context, client *redis.Cli
411410 }
412411 }
413412 }
414-
413+
415414 r .incrementCounter ("ack.retry_exhausted" )
416415 return fmt .Errorf ("ACK failed after %d retries: %w" , maxRetries , lastErr )
417416}
@@ -421,9 +420,9 @@ func (r *RedisStreams) isRetryableError(err error) bool {
421420 if err == nil {
422421 return false
423422 }
424-
423+
425424 errStr := err .Error ()
426-
425+
427426 // Network/connection errors that are retryable
428427 retryableErrors := []string {
429428 "connection refused" ,
@@ -438,20 +437,20 @@ func (r *RedisStreams) isRetryableError(err error) bool {
438437 "context canceled" , // Handle graceful shutdowns
439438 "no such host" , // DNS lookup failures
440439 }
441-
440+
442441 for _ , retryable := range retryableErrors {
443442 if strings .Contains (strings .ToLower (errStr ), retryable ) {
444443 return true
445444 }
446445 }
447-
446+
448447 // Redis-specific retryable errors
449448 if strings .Contains (errStr , "LOADING" ) || // Redis is loading data
450449 strings .Contains (errStr , "READONLY" ) || // Redis is in read-only mode
451450 strings .Contains (errStr , "CLUSTERDOWN" ) { // Redis cluster is down
452451 return true
453452 }
454-
453+
455454 return false
456455}
457456
@@ -460,9 +459,9 @@ func (r *RedisStreams) isConnectionError(err error) bool {
460459 if err == nil {
461460 return false
462461 }
463-
462+
464463 errStr := err .Error ()
465-
464+
466465 connectionErrors := []string {
467466 "connection refused" ,
468467 "connection reset" ,
@@ -471,13 +470,13 @@ func (r *RedisStreams) isConnectionError(err error) bool {
471470 "eof" ,
472471 "connection pool exhausted" ,
473472 }
474-
473+
475474 for _ , connErr := range connectionErrors {
476475 if strings .Contains (strings .ToLower (errStr ), connErr ) {
477476 return true
478477 }
479478 }
480-
479+
481480 return false
482481}
483482
@@ -502,4 +501,4 @@ func (r *RedisStreams) recordTimer(key string, duration float64) {
502501 timer .Update (duration )
503502 }
504503 }
505- }
504+ }
0 commit comments