Wprowadzenie
Po nieudanym przetworzeniu żądania DynamoDB zgłasza błąd. Każdy błąd składa się z następujących elementów: statusu HTTP, nazwy wyjątku oraz komunikatu błędu. W tym wpisie przejdziemy przez kilka przykładówych błędów oraz omówimy możliwości związane z implemetancją mechanizmu powtarzania żądania oraz spójrzmy na rekomendowany przez AWS algorytm backoff - w bardzo dużym skrócie: jest to koncepcja polegająca na stopniowym wydłużaniu czasu oczekiwania pomiędzy kolejnymi próbami w odpowiedzi na błędy.
Statusy i komunikaty
Wyjątki dzielą się na różne statusy HTTP. Te zaliczane do grupy 4xx oraz 5xx związane są z błędami wynikającymi z niepoprawnych żądań oraz z samym AWS’em. Do tej grupy możemy zaliczyć poniższe wyjątki:
- AccessDeniedException - klient nie wysłał poprawnego żądania;
- ConditionalCheckFailedException - zdefiniowane warunek, którego wynikiem jest wartość false, np. próba wykonania warunkowej aktualizacji elementu się nie powiodła ponieważ rzeczywista wartość atrybutu nie była zgodna z wartością oczekiwaną;
- IncompleteSignatureException - sygnatura żądania nie zawiera wszystkich wymaganych komponentów;
- ItemCollectionSizeLimitExceededException - dla tabeli z lokalnym indeksem pomocniczym, grupa elementów z tą samą wartością klucza partycji przekroczyła maksymalny limit rozmiaru 10GB.
Po pełną listę wyjątków oraz szczegółowy opis obsługi błędów tradycyjnie odsyłam do oficjalnej dokumentacji: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html
Błędy związane ze statusem 5xx są każdemu dobrze znane i nie wymagają większego opisu. Zaliczamy do nich:
-
Internal Server Error - DynamoDB nie było w stanie przetworzyć naszego żądania;
-
Service Unavailable - DynamoDB jest (obecnie) niedostępne – powinien być to stan tymczasowy, który jest dobrym przykładem na zaimplementowanie mechanizmu ‘ponawiania żądania’.
Przykład praktyczny
Pomijając błędy generowane przez użytkownika…możemy również spotkać się z błędami związanymi z samą infrastrukturą AWS. Typowym rozwiązaniem wspierającym niezawodność naszego systemu jest wykorzystanie mechanizmu ponawiania żądań, z angielskiego: retry mechanism. Wszystkie komponenty infrastruktury AWS mają zaimplementowaną tę logikę a my możemy ustawić parametry ponawiania tak, aby dopasować je do potrzeb naszej aplikacji.
Jednym z takich parametrów jest maxErrorRetry, który pozwala nam na zatrzymanie prób ponawiania w momencie wystąpienia określonej liczby błędnych odpowiedzi – zanim przejdziemy do naszego przykładu możecie zerknąć tutaj: https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/retries-timeouts.html
Dodatkowym aspektem obsługi błędów jest wspominany wcześniej algorytm backoff zalecany przez Amazon. Polega on na stopniowym zwiększaniu czasu pomiędzy kolejnymi próbami i ostatecznym zatrzymaniu mechanizmu po dość krótkim okresie czasu. Warto pamiętać, że SDK wykona automatyczne próby ponowienia żądania ale bez implementacji wykładniczego wzrostu tej wartości.
Spójrzmy na przykład z komentarzami w kodzie:
public async Task<ActionResult<string>> ErrorHandling()
{
int retries = 0;
bool retry = false;
int maxRetries = 5;
try
{
do
{
// utworzenie żądania
var request = new GetItemRequest()
{
TableName = TableName1,
Key = new Dictionary<string, AttributeValue>() { { "Id", new AttributeValue { N = "1" } }, }
};
// Pobranie odpowiedzi (lista atrybutów: response.Items)
var response = await _amazonDynamoDB.GetItemAsync(request);
// Sprawdzenie kilku błedów wspominanych we wpisie
// Jeżeli wszystko jest OK - brak potrzeby ponawiania żądania
if (response.HttpStatusCode == HttpStatusCode.OK)
retry = false;
else if (response.HttpStatusCode == HttpStatusCode.NotFound)
retry = true;
else if (response.HttpStatusCode == HttpStatusCode.BadRequest)
retry = true;
else if (response.HttpStatusCode == HttpStatusCode.InternalServerError)
retry = true;
else
retry = false; // zatrzymujemy wykonanie na jakikolwiek inny błąd
} while (retry && (retries++ < maxRetries));
}
catch (Exception ex)
{
// Tutaj bez żadnej obsługi/logowania - tylko na potrzeby wpisu
// Przykład błędu:
// - wywołanie metody GetItemAsync na nieistniejącej tabeli
// Message = "Cannot do operations on a non-existent table"
}
return $"Status retry: {retry}";
}