Skip to content

Latest commit

 

History

History
156 lines (125 loc) · 5.34 KB

basic-error-handling.adoc

File metadata and controls

156 lines (125 loc) · 5.34 KB

Basic error handling when using Spring Data Aerospike

Everything fails all the time.
— Werner Vogels

 
Add spring-retry dependency together with spring-boot-starter-aop:

pom.xml
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

You can either specify retry configuration directly via @Retryable properties or create RetryInterceptor bean and set its name in @Retryable interceptor property. For this demo we’ll use simple configuration via @Retryable.

MovieService.java
@Retryable( // (1)
        retryFor = { // (2)
                QueryTimeoutException.class,
                TransientDataAccessResourceException.class,
                OptimisticLockingFailureException.class
        },
        maxAttempts = 5, // (3)
        backoff = @Backoff( // (4)
                delay = 3,
                multiplier = 2,
                random = true // (5)
        )
)
@RequiredArgsConstructor
@Service
public class MovieService {

    private final MovieRepository repository;
    private final AerospikeTemplate template;

    public void createMovie(Movie movie) {
        try {
            template.insert(MovieDocument.builder()
                    .id(movie.getName())
                    .name(movie.getName())
                    .description(movie.getDescription())
                    .rating(movie.getRating())
                    .version(0L)
                    .build());
        } catch (DuplicateKeyException e) {
            throw new IllegalArgumentException("Movie with name: " + movie.getName() + " already exists!");
        }
    }

    public void deleteMovie(String name) {
        repository.deleteById(name);
    }

    public Optional<Movie> findMovie(String name) {
        return repository.findById(name)
                .map(this::toMovie);
    }

    public Movie updateMovieRating(String name, double newRating) {
        return update(name, existingMovie ->
                repository.save(existingMovie.toBuilder().rating(newRating).build()));
    }

    public Movie updateMovieDescription(String name, String newDescription) {
        return update(name, existingMovie ->
                repository.save(existingMovie.toBuilder().description(newDescription).build()));
    }

    private Movie update(String name, Function<MovieDocument, MovieDocument> updateFunction) {
        return repository.findById(name)
                .map(updateFunction)
                .map(this::toMovie)
                .orElseThrow(() -> new IllegalArgumentException("Movie with name: " + name + " not found"));
    }

    private Movie toMovie(MovieDocument doc) {
        return new Movie(doc.getName(), doc.getDescription(), doc.getRating());
    }
}
  1. @Retryable specifies that the method(s) will be retied.

  2. retryFor specifies exception types that should be retried.

  3. maxAttempts specifies number of max attempts of retries.

  4. backoff specifies backoff configuration for the retries.

  5. random = true enables jitter in backoff timeouts.

Add @EnableRetry either into existing AerospikeConfiguration or create separate class AerospikeRetryConfiguration:

AerospikeRetryConfiguration.java
@EnableRetry
@Configuration
public class AerospikeRetryConfiguration {

}
AerospikeConfiguration.java
@Configuration
@EnableAerospikeRepositories(basePackageClasses = MovieRepository.class)
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {

}

Important points

Retryable and non-retryable errors

In the context of retries there are two types of errors: retryable and non-retryable. For example, retrieving value by key may result in DataRetrievalFailureException (key not found), which in most cases should not be retried; whereas in case there are connectivity issues to Aerospike or any network congestion issues you would retry. Consider this when configuring your retry policy.

Backoff

When retrying errors there are two basic backoff policies: fixed and exponential.

Fixed backoff policy does exactly how it’s named, each retry occurs within fixed time interval. This backoff policy is simple and easy for understanding, but is not recommended for production, because in case external resource is overloaded and all clients experience issues, constantly increasing number of requests at the same time may lead to the total resource outage.

To overcome the issues resource should be given some time to heal, and this can be achieved by exponential backoff policy which increases each retry time interval by a specific multiplier. This backoff policy is usually used together with jitter — added randomized time interval into the backoff, which removes retry waves at specific time from multiple clients. With spring-retry you can use org.springframework.retry.backoff.ExponentialRandomBackOffPolicy.

Concurrent saves: optimistic locking

Demo application

To see demo application go to Basic Error Handling Demo.