@@ -20,6 +20,7 @@ package syncer
2020import (
2121 "context"
2222 "errors"
23+ "time"
2324
2425 "github.com/optimizely/agent/config"
2526 "github.com/optimizely/agent/pkg/syncer/pubsub"
@@ -28,8 +29,10 @@ import (
2829const (
2930 // PubSubDefaultChan will be used as default pubsub channel name
3031 PubSubDefaultChan = "optimizely-sync"
31- // PubSubRedis is the name of pubsub type of Redis
32+ // PubSubRedis is the name of pubsub type of Redis (fire-and-forget)
3233 PubSubRedis = "redis"
34+ // PubSubRedisStreams is the name of pubsub type of Redis Streams (persistent)
35+ PubSubRedisStreams = "redis-streams"
3336)
3437
3538type SycnFeatureFlag string
@@ -48,12 +51,16 @@ func newPubSub(conf config.SyncConfig, featureFlag SycnFeatureFlag) (PubSub, err
4851 if featureFlag == SyncFeatureFlagNotificaiton {
4952 if conf .Notification .Default == PubSubRedis {
5053 return getPubSubRedis (conf )
54+ } else if conf .Notification .Default == PubSubRedisStreams {
55+ return getPubSubRedisStreams (conf )
5156 } else {
5257 return nil , errors .New ("pubsub type not supported" )
5358 }
5459 } else if featureFlag == SycnFeatureFlagDatafile {
5560 if conf .Datafile .Default == PubSubRedis {
5661 return getPubSubRedis (conf )
62+ } else if conf .Datafile .Default == PubSubRedisStreams {
63+ return getPubSubRedisStreams (conf )
5764 } else {
5865 return nil , errors .New ("pubsub type not supported" )
5966 }
@@ -99,9 +106,92 @@ func getPubSubRedis(conf config.SyncConfig) (PubSub, error) {
99106 return nil , errors .New ("pubsub redis database not valid, database must be int" )
100107 }
101108
109+ // Return original Redis pub/sub implementation (fire-and-forget)
102110 return & pubsub.Redis {
103111 Host : host ,
104112 Password : password ,
105113 Database : database ,
106114 }, nil
107115}
116+
117+ func getPubSubRedisStreams (conf config.SyncConfig ) (PubSub , error ) {
118+ pubsubConf , found := conf .Pubsub [PubSubRedis ]
119+ if ! found {
120+ return nil , errors .New ("pubsub redis config not found" )
121+ }
122+
123+ redisConf , ok := pubsubConf .(map [string ]interface {})
124+ if ! ok {
125+ return nil , errors .New ("pubsub redis config not valid" )
126+ }
127+
128+ hostVal , found := redisConf ["host" ]
129+ if ! found {
130+ return nil , errors .New ("pubsub redis host not found" )
131+ }
132+ host , ok := hostVal .(string )
133+ if ! ok {
134+ return nil , errors .New ("pubsub redis host not valid, host must be string" )
135+ }
136+
137+ passwordVal , found := redisConf ["password" ]
138+ if ! found {
139+ return nil , errors .New ("pubsub redis password not found" )
140+ }
141+ password , ok := passwordVal .(string )
142+ if ! ok {
143+ return nil , errors .New ("pubsub redis password not valid, password must be string" )
144+ }
145+
146+ databaseVal , found := redisConf ["database" ]
147+ if ! found {
148+ return nil , errors .New ("pubsub redis database not found" )
149+ }
150+ database , ok := databaseVal .(int )
151+ if ! ok {
152+ return nil , errors .New ("pubsub redis database not valid, database must be int" )
153+ }
154+
155+ // Parse optional Redis Streams configuration parameters
156+ batchSize := getIntFromConfig (redisConf , "batch_size" , 10 )
157+ flushInterval := getDurationFromConfig (redisConf , "flush_interval" , 5 * time .Second )
158+ maxRetries := getIntFromConfig (redisConf , "max_retries" , 3 )
159+ retryDelay := getDurationFromConfig (redisConf , "retry_delay" , 100 * time .Millisecond )
160+ maxRetryDelay := getDurationFromConfig (redisConf , "max_retry_delay" , 5 * time .Second )
161+ connTimeout := getDurationFromConfig (redisConf , "connection_timeout" , 10 * time .Second )
162+
163+ // Return Redis Streams implementation with configuration
164+ return & pubsub.RedisStreams {
165+ Host : host ,
166+ Password : password ,
167+ Database : database ,
168+ BatchSize : batchSize ,
169+ FlushInterval : flushInterval ,
170+ MaxRetries : maxRetries ,
171+ RetryDelay : retryDelay ,
172+ MaxRetryDelay : maxRetryDelay ,
173+ ConnTimeout : connTimeout ,
174+ }, nil
175+ }
176+
177+ // getIntFromConfig safely extracts an integer value from config map with default fallback
178+ func getIntFromConfig (config map [string ]interface {}, key string , defaultValue int ) int {
179+ if val , found := config [key ]; found {
180+ if intVal , ok := val .(int ); ok {
181+ return intVal
182+ }
183+ }
184+ return defaultValue
185+ }
186+
187+ // getDurationFromConfig safely extracts a duration value from config map with default fallback
188+ func getDurationFromConfig (config map [string ]interface {}, key string , defaultValue time.Duration ) time.Duration {
189+ if val , found := config [key ]; found {
190+ if strVal , ok := val .(string ); ok {
191+ if duration , err := time .ParseDuration (strVal ); err == nil {
192+ return duration
193+ }
194+ }
195+ }
196+ return defaultValue
197+ }
0 commit comments