Paweł Łukasiewicz
2024-06-10
Paweł Łukasiewicz
2024-06-10
Udostępnij Udostępnij Kontakt
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}";
}