Introduce a retry policy with exponential backoff and jitter for extractor and loader errors, with configurable max attempts and delay caps.
102 lines
2.0 KiB
Go
102 lines
2.0 KiB
Go
package custom_errors
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"git.ksdemosapps.com/kylesoda/go-migrate/internal/app/config"
|
|
"git.ksdemosapps.com/kylesoda/go-migrate/internal/app/models"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type ExtractorError struct {
|
|
Partition models.Partition
|
|
LastId int64
|
|
HasLastId bool
|
|
Msg string
|
|
}
|
|
|
|
func (e *ExtractorError) Error() string {
|
|
return e.Msg
|
|
}
|
|
|
|
func ExtractorErrorHandler(
|
|
ctx context.Context,
|
|
retryConfig config.RetryConfig,
|
|
chErrorsIn <-chan ExtractorError,
|
|
chPartitionsOut chan<- models.Partition,
|
|
chJobErrorsOut chan<- JobError,
|
|
wgActivePartitions *sync.WaitGroup,
|
|
) {
|
|
for {
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
|
|
case err, ok := <-chErrorsIn:
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if err.Partition.RetryCounter >= retryConfig.Attempts {
|
|
wgActivePartitions.Done()
|
|
jobError := JobError{
|
|
ShouldCancelJob: false,
|
|
Msg: fmt.Sprintf("Partition %v reached max retries (%d)", err.Partition.Id, retryConfig.Attempts),
|
|
Prev: &err,
|
|
}
|
|
|
|
select {
|
|
case chJobErrorsOut <- jobError:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
|
|
continue
|
|
} else {
|
|
jobError := JobError{
|
|
ShouldCancelJob: false,
|
|
Msg: fmt.Sprintf("Temporal error in partition %v (retries: %d)", err.Partition.Id, err.Partition.RetryCounter),
|
|
Prev: &err,
|
|
}
|
|
|
|
select {
|
|
case chJobErrorsOut <- jobError:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
|
|
newPartition := err.Partition
|
|
newPartition.RetryCounter++
|
|
|
|
delay := computeBackoffDelay(
|
|
newPartition.RetryCounter,
|
|
retryConfig.BaseDelayMs,
|
|
retryConfig.MaxDelayMs,
|
|
retryConfig.MaxJitterMs,
|
|
)
|
|
|
|
if err.HasLastId {
|
|
newPartition.ParentId = err.Partition.Id
|
|
newPartition.Id = uuid.New()
|
|
newPartition.LowerLimit = err.LastId
|
|
newPartition.IsLowerLimitInclusive = false
|
|
}
|
|
|
|
requeueWithBackoff(ctx, delay, func() {
|
|
select {
|
|
case chPartitionsOut <- newPartition:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|