github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/cassandra/wrapper/queryretry.go (about) 1 package wrapper 2 3 import ( 4 "github.com/gocql/gocql" 5 log "github.com/sirupsen/logrus" 6 "strconv" 7 "time" 8 ) 9 10 const ( 11 defaultCassandraRetryAttempts = "3" 12 defaultCassandraSecondsToSleepIncrement = "1" 13 14 envCassandraAttempts = "CASSANDRA_RETRY_ATTEMPTS" 15 envCassandraSecondsToSleepIncrement = "CASSANDRA_SECONDS_SLEEP_INCREMENT" 16 ) 17 18 var cassandraRetryAttempts = 3 19 var cassandraSecondsToSleepIncrement = 1 20 21 // Package level initialization. 22 // 23 // init functions are automatically executed when the programs starts 24 func init() { 25 cassandraRetryAttempts, err := strconv.Atoi(getenv(envCassandraAttempts, defaultCassandraRetryAttempts)) 26 if err != nil { 27 log.Errorf("error trying to get CASSANDRA_RETRY_ATTEMPTS value: %s", 28 getenv(envCassandraAttempts, defaultCassandraRetryAttempts)) 29 cassandraRetryAttempts = 3 30 } 31 32 cassandraSecondsToSleep, err := strconv.Atoi(getenv(envCassandraSecondsToSleepIncrement, defaultCassandraSecondsToSleepIncrement)) 33 if err != nil { 34 log.Errorf("error trying to get CASSANDRA_SECONDS_SLEEP value: %s", 35 getenv(envCassandraSecondsToSleepIncrement, defaultCassandraSecondsToSleepIncrement)) 36 cassandraSecondsToSleepIncrement = 1 37 } 38 39 log.Debugf("got cassandraRetryAttempts: %d", cassandraRetryAttempts) 40 log.Debugf("got cassandraSecondsToSleepIncrement: %d", cassandraSecondsToSleep) 41 } 42 43 // queryRetry is an implementation of QueryInterface 44 type queryRetry struct { 45 goCqlQuery *gocql.Query 46 } 47 48 // iterRetry is an implementation of IterInterface 49 type iterRetry struct { 50 goCqlIter *gocql.Iter 51 } 52 53 // Exec wrapper to retry around gocql Exec(). We have a retry approach in place + incremental approach used. For example: 54 // First time it will wait 1 second, second time 2 seconds, ... It will depend on the values for retries and seconds to wait. 55 func (q queryRetry) Exec() error { 56 log.Debug("running queryRetry Exec() method") 57 58 retryAttempts := cassandraRetryAttempts 59 secondsToSleep := 0 60 61 var err error 62 63 attempts := 1 64 for attempts <= retryAttempts { 65 //we will try to run the method several times until attempts is met 66 err = q.goCqlQuery.Exec() 67 if err != nil { 68 log.Warnf("error when running Exec(): %v, attempt: %d / %d", err, attempts, retryAttempts) 69 70 // incremental sleep 71 secondsToSleep = secondsToSleep + cassandraSecondsToSleepIncrement 72 73 log.Warnf("sleeping for %d second", secondsToSleep) 74 75 time.Sleep(time.Duration(secondsToSleep) * time.Second) 76 } else { 77 // in case the error is nil, we stop and return 78 return err 79 } 80 81 attempts = attempts + 1 82 } 83 84 return err 85 } 86 87 // Scan wrapper to retry around gocql Scan(). We have a retry approach in place + incremental approach used. For example: 88 // First time it will wait 1 second, second time 2 seconds, ... It will depend on the values for retries and seconds to wait. 89 func (q queryRetry) Scan(dest ...interface{}) error { 90 log.Debug("running queryRetry Scan() method") 91 92 retries := cassandraRetryAttempts 93 secondsToSleep := 0 94 95 var err error 96 97 attempts := 1 98 for attempts <= retries { 99 //we will try to run the method several times until attempts is met 100 err = q.goCqlQuery.Scan(dest...) 101 if err != nil { 102 if err.Error() == "not found" { 103 log.Warnf("returning not found") 104 return err 105 } 106 107 log.Warnf("error when running Scan(): %v, attempt: %d / %d", err, attempts, retries) 108 109 // incremental sleep 110 secondsToSleep = secondsToSleep + cassandraSecondsToSleepIncrement 111 112 log.Warnf("sleeping for %d second", secondsToSleep) 113 114 log.Warnf("sleeping for %d second", secondsToSleep) 115 time.Sleep(time.Duration(secondsToSleep) * time.Second) 116 } else { 117 // in case the error is nil, we stop and return 118 return err 119 } 120 121 attempts = attempts + 1 122 } 123 124 return err 125 } 126 127 // Iter just a wrapper to be able to call this method 128 func (q queryRetry) Iter() IterInterface { 129 log.Debug("running queryRetry Iter() method") 130 131 return iterRetry{goCqlIter: q.goCqlQuery.Iter()} 132 } 133 134 // PageState just a wrapper to be able to call this method 135 func (q queryRetry) PageState(state []byte) QueryInterface { 136 log.Debug("running queryRetry PageState() method") 137 138 return queryRetry{goCqlQuery: q.goCqlQuery.PageState(state)} 139 } 140 141 // PageSize just a wrapper to be able to call this method 142 func (q queryRetry) PageSize(n int) QueryInterface { 143 log.Debug("running queryRetry PageSize() method") 144 145 return queryRetry{goCqlQuery: q.goCqlQuery.PageSize(n)} 146 } 147 148 // 149 func (i iterRetry) Scan(dest ...interface{}) bool { 150 log.Debug("running iterRetry Scan() method") 151 152 return i.goCqlIter.Scan(dest...) 153 } 154 155 // WillSwitchPage is just a wrapper to be able to call this method 156 func (i iterRetry) WillSwitchPage() bool { 157 log.Debug("running iterRetry Close() method") 158 159 return i.goCqlIter.WillSwitchPage() 160 } 161 162 // PageState is just a wrapper to be able to call this method 163 func (i iterRetry) PageState() []byte { 164 log.Debug("running iterRetry PageState() method") 165 166 return i.goCqlIter.PageState() 167 } 168 169 // Close is just a wrapper to be able to call this method 170 func (i iterRetry) Close() error { 171 log.Debug("running iterRetry Close() method") 172 173 return i.goCqlIter.Close() 174 } 175 176 // ScanAndClose is a wrapper to retry around the gocql Scan() and Close(). 177 // We have a retry approach in place + incremental approach used. For example: 178 // First time it will wait 1 second, second time 2 seconds, ... It will depend on the values for retries 179 // and seconds to wait. 180 func (i iterRetry) ScanAndClose(handle func() bool, dest ...interface{}) error { 181 182 retries := cassandraRetryAttempts 183 secondsToSleep := 0 184 185 var err error 186 187 attempts := 1 188 for attempts <= retries { 189 190 // Scan consumes the next row of the iterator and copies the columns of the 191 // current row into the values pointed at by dest. 192 for i.goCqlIter.Scan(dest...) { 193 if !handle() { 194 break 195 } 196 } 197 198 // we will try to run the method several times until attempts is met 199 if err = i.goCqlIter.Close(); err != nil { 200 201 log.Warnf("error when running Close(): %v, attempt: %d / %d", err, attempts, retries) 202 203 // incremental sleep 204 secondsToSleep += cassandraSecondsToSleepIncrement 205 206 log.Warnf("sleeping for %d second", secondsToSleep) 207 208 time.Sleep(time.Duration(secondsToSleep) * time.Second) 209 } else { 210 // in case the error is nil, we stop and return 211 return err 212 } 213 214 attempts++ 215 } 216 217 return err 218 }