Certaines erreurs passent toutes seules
Un service cloud répond à chaque appel, et parfois la réponse est une erreur. Un code d’erreur dit de quelle sorte elle est.
Un bucket S3 absent reste absent si vous redemandez. Une table DynamoDB occupée peut répondre une seconde plus tard. Cette leçon apprend à distinguer les deux et à réessayer la bonne.
À la fin de cette leçon
Remarque
Je peux dire quelle erreur il faut réessayer et laquelle il faut corriger.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Code d’erreur | un nom court pour ce qui s’est mal passé | NoSuchKey |
| Code de statut | un nombre qui range une réponse dans une famille | 404 |
| Limitation de débit | un service qui refuse des appels parce que vous en avez trop envoyé | throttling |
| Nouvelle tentative | envoyer de nouveau le même appel | |
| Attente croissante | attendre plus longtemps avant chaque nouvelle tentative | backoff |
| Gigue | une part au hasard de l’attente, pour que les appelants ne bougent pas ensemble | jitter |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur a un code et un statut
Quand un appel échoue, boto3 vous donne une erreur. Elle contient un code de statut, comme 404, et un code d’erreur, comme NoSuchKey.
Testez le code d’erreur et le statut. Ne testez pas le message : sa formulation peut changer d’un service à l’autre, et celle de l’émulateur diffère parfois de la vraie.
Exemple : des erreurs lues dans des réponses capturées
| Appel | Code | Statut | Réessayer ? |
|---|---|---|---|
| get_object, clé absente | NoSuchKey | 404 | Non, corrigez la clé |
| head_object, clé absente | 404 | 404 | Non, il n’y a pas de corps à lire |
| list_objects_v2, bucket absent | NoSuchBucket | 404 | Non, créez le bucket |
| put_item, condition fausse | ConditionalCheckFailedException | 400 | Non, la réponse est la réponse |
| query, mauvais nom de clé | ValidationException | 400 | Non, corrigez l’appel |
| send_message, file absente | AWS.SimpleQueueService.NonExistentQueue | 400 | Non, créez la file |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Exécutez : lire le code et le statut
reponses = [
{"error": {"Code": "NoSuchKey", "Message": "Absente.", "HTTPStatusCode": 404}},
{"error": {"Code": "ConditionalCheckFailedException", "Message": "Non.", "HTTPStatusCode": 400}},
{"error": {"Code": "InternalServerError", "Message": "Oups.", "HTTPStatusCode": 500}},
{"Items": []},
]
for reponse in reponses:
if "error" not in reponse:
print("réussi")
else:
erreur = reponse["error"]
print(erreur["Code"], erreur["HTTPStatusCode"])
Réessayer ce qui peut marcher la prochaine fois
Certaines erreurs disent « pas maintenant ». Une limitation de débit veut dire que vous avez envoyé trop d’appels. Un statut de 500 ou plus veut dire que le service a eu une panne. Les deux peuvent passer si vous attendez.
D’autres erreurs disent « pas cet appel ». Votre appel était faux, donc le même appel échoue à chaque fois. Corrigez-le plutôt.
Erreur fréquente
Attention
Beaucoup réessaient toutes les erreurs, par prudence. Une mauvaise clé échoue alors cinq fois et vous fait perdre du temps à attendre pour rien. Ne réessayez que les limitations de débit et les pannes du service. Corrigez toutes les autres, car le même appel donne la même réponse.
Exécutez : trier réessayer ou corriger
# Les codes de limitation sont ceux de la documentation, non capturés ici.
LIMITATIONS = ["ThrottlingException", "ProvisionedThroughputExceededException", "SlowDown"]
def vaut_un_nouvel_essai(erreur):
if erreur["HTTPStatusCode"] >= 500:
return True
return erreur["Code"] in LIMITATIONS
erreurs = [
{"Code": "NoSuchKey", "HTTPStatusCode": 404},
{"Code": "SlowDown", "HTTPStatusCode": 503},
{"Code": "ThrottlingException", "HTTPStatusCode": 400},
{"Code": "ValidationException", "HTTPStatusCode": 400},
]
for erreur in erreurs:
print(erreur["Code"], vaut_un_nouvel_essai(erreur))
# Ajoutez "RequestLimitExceeded" à LIMITATIONS et testez-le.
L’attente croît à chaque essai
Si un service est occupé, réessayer une milliseconde plus tard le rend plus occupé encore. Attendez donc, et attendez plus longtemps après chaque échec. Doubler l’attente est courant. Fixez un plafond pour que l’attente ne dépasse jamais une limite.
Ajoutez maintenant de la gigue : attendez une part au hasard de ce temps. Sans elle, cent appelants se réveillent ensemble et échouent ensemble de nouveau.
Exemple travaillé : le plafond de chaque attente
| Essai | Doublé | Avec un plafond de 8 | Attente si le tirage est 0,5 |
|---|---|---|---|
| 1 | 0,5 seconde | 0,5 | 0,25 |
| 2 | 1 seconde | 1 | 0,5 |
| 3 | 2 secondes | 2 | 1 |
| 4 | 4 secondes | 4 | 2 |
| 5 | 8 secondes | 8 | 4 |
| 6 | 16 secondes | 8 | 4 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Exécutez : une attente que l’on peut tester
# tirage est un nombre de 0 à 1. Un vrai appelant passe random.random().
# Un test passe un nombre fixe, donc la réponse ne change jamais.
def delai_attente(tentative, base, plafond, tirage):
maximum = min(plafond, base * 2 ** (tentative - 1))
return maximum * tirage
for tentative in range(1, 7):
print(tentative, delai_attente(tentative, 0.5, 8, 1.0), delai_attente(tentative, 0.5, 8, 0.5))
# Passez le plafond de 8 à 2. Quels essais changent ?