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  }