Mesures de l’impact de Sentry sur le démarrage des Lambdas AWS

Chez Local Logic, nous nous appuyons fortement sur les fonctions AWS lambda. Nous surveillons actuellement notre environnement avec Sentry via leur sdk et savons que nous pourrions alternativement utiliser la couche (layer) sentry. Comme un bon développeur, je me suis demandé quels étaient les avantages et les inconvénients. Évidemment, l’un d’entre eux est: quelle option est la plus rapide? Je les ai donc comparées et je partagerai les résultats dans ce billet.

Configuration

Comme toutes les bonnes expériences scientifiques, je vais présenter l’environnement d’installation pour la postérité.

  • exécution : python 3.11
  • mémoire: 1024MB
  • architecture: x86_64

Pour faire un benchmark, j’ai utilisé une lambda réelle que nous avons. Je l’ai reconfigurée pour chacun des tests suivants :

  1. Sans sentry
  2. sentry-sdk 1.45.0 (La version que nous utilisons déjà)
  3. sentry-sdk 2.1.1 (La version dont je viens de découvrir l’existence)
  4. Couche sentry arn:aws:lambda:us-east-2:943013980633:layer:SentryPythonServerlessSDK:112

Pour reproduire la couche sentry, le code python pour initialiser le sdk est le suivant

import os
import sentry_sdk
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration

sentry_sdk.init(
    dsn=settings.os.environ["SENTRY_DSN"],
    integrations=[AwsLambdaIntegration(timeout_warning=true)],
    traces_sample_rate=os.environ["SENTRY_TRACES_SAMPLE_RATE"],
    environment=os.environ["STAGE"],
)

Pour pouvoir mesurer, j’ai modifié la lambda de la manière suivante :

  1. Défini le délai d’expiration (timeout) sur 5 secondes.
  2. Sur la toute première ligne de la fonction handler, logger, avec un logging structuré, « ready » avec la clé/valeur de context.remaining_time_in_milis(). C’était nécessaire pour mesurer le temps passé dans couche sentry pour invoquer notre code.
  3. Sur la ligne suivante, exécuter sleep(6). Cela permet de suspendre la lambda jusqu’à ce qu’elle s’arrête, ce qui facilite les tests car tous les appels effectués pendant cette période déclencheront une nouvelle lambda et donc un démarrage à froid (cold start).

Pour générer des résultats, j’ai exécuté, en bash, la boucle serrée suivante.

for i in {0..20}; do http -A bearer -a $BEARER_TOKEN https://<endpoint that targets the lambda> & done;

Le fait que j’utilise une interface http pour déclencher le test est impertinent. J’aurais pu l’appeler par invocation directe. Cela n’a pas d’importance. Ce qui compte c’est que la boucle envoie chaque appel http en arrière-plan, générant ainsi 21 appels http presque simultanés.

Pour collecter les données, j’ai utilisé AWS logs insights avec la requête suivante.

stats count(@initDuration), min(@initDuration), avg(@initDuration), max(@initDuration), stddev(@initDuration), count(remaining_time_in_milis), min(remaining_time_in_milis), avg(remaining_time_in_milis), max(remaining_time_in_milis), stddev(remaining_time_in_milis) by bin(5m)

Comme vous l’avez peut-être compris à la lecture de cette requête, je devais exécuter les tests à 5 minutes d’intervalle pour ne pas mélanger les résultats. Ça s’est fait implicitement car la mise en place du code, de la couche, des dépendances et le déploiement entre chaque test ont pris à peu près autant de temps. Pour ce qui est des count, comme les journaux sont éventuellement cohérents (eventually consistent), j’ai dû attendre qu’ils atteignent 21 pour m’assurer que j’avais les bons résultats.

Résultats

Configuration SentryMoyenne @initDuration ms (démarrage à froid)Écart Type @initDuration msMoyenne temps jusqu’au handler ms (5 – remaining_time_in_milis)Moyenne totale temps d’initialisation ms
Sans sentry3101.317223.7690.8093102.127
Sentry-sdk 1.45.03485.301251.7201.6193486.920
Sentry-sdk 2.1.13377.584226.0861.7623379.346
Sentry LayerServerless 112963.46079.4563858.6674822.127
Grille des résultats

J’ai omis les comptes, les valeurs minimales, maximales et l’écart-type de ce tableau, car il aurait été trop volumineux pour ce billet.

Interprétation

Éliminons d’emblée le résultat le plus surprenant: La couche est surprenamment… décevante. Avec un temps d’initialisation moyen total supérieur de ~1443ms. Le gagnant est … il n’y a pas de gagnant. En regardant uniquement la moyenne, on pourrait penser que sentry 2.1.1 est le vainqueur, mais en prenant en compte l’écart-type et la taille de l’échantillon, c’est statistiquement peu concluant.

A partir de ce tableau, la nécessité de mesurer le «temps jusqu’au handler» est évidente. Je ne peux que supposer que la couche sentry, au lieu de charger notre application dans la portion démarrage à froid, le fait à l’intérieur de son handler, et donc cela compte dans le temps d’exécution normal. Cela peut être un problème, et j’y ai été confronté en testant, car ce «temps d’exécution normal» compte aussi pour le délai d’expiration, ce qui n’est pas souhaitable. Lorsque j’ai commencé à tester, mon délai d’attente était fixé à 3 secondes et les tests de la couche sentry ont échoué parce qu’ils se sont arrêtés avant d’atteindre mon handler.

L’utilisation de sentry-sdk et son initialisation au niveau du module / espace de noms global, augmente le temps de démarrage à froid, ce qui est normal.

Si vous vous indignez de notre temps de démarrage à froid d’environ 3 secondes, tenez compte du contexte. Ils se produisent pour environ 0,2 % des requêtes. Vous pouvez alors vous demander si cela valait la peine de passer autant de temps sur ces benchmarks. Bien sûr que oui! Tout pour la science!

Mentions

Simon Lemieux a fourni des indications mathématiques sur la fiabilité statistique de «l’avantage» de 100 ms du sdk 2.1.1 par rapport au sdk 1.45.0.

L’image de couverture est Abstract Clock Vortex HD Wallpaper by Laxmonaut

Leave a Reply

Ce site utilise Akismet pour réduire le pourriel. En savoir plus sur comment les données de vos commentaires sont utilisées.