Le Blog Amazon Web Services

Amélioration des performances des fonctions AWS Lambda Java grâce à la compilation hiérarchisée

La série d’articles (en anglais) Operating Lambda : Performance Optimization aborde des sujets importants pour les développeurs, les architectes et les administrateurs système qui gèrent des applications à l’aide des fonctions AWS Lambda. Cet article explique comment réduire le temps d’initialisation d’un nouvel environnement d’exécution Java.

cold start

De nombreuses fonctions Lambda sont conçues pour fournir des réponses rapides aux applications synchrones ou asynchrones. Il peut s’agir par exemple d’API publiques pour fournir du contenu dynamique à des sites Web ou d’un pipeline de données en quasi temps-réel effectuant un traitement par lots (batchs) de petite taille.

Pour passer à l’échelle automatiquement, Lambda crée de nouveaux environnements d’exécution. Lorsqu’un nouvel environnement est créé et utilisé pour la première fois, des tâches complémentaires peuvent être effectuées pour le préparer au traitement d’un événement.

Pour améliorer le temps de réponse, vous pouvez minimiser l’impact de ces tâches. L’un des moyens de réduire le temps nécessaire à la création d’un nouvel environnement d’exécution Java consiste à configurer la Machine virtuelle Java (JVM). Elle peut être optimisée spécifiquement pour les applications qui ont des durées d’exécution courtes.

Un exemple est l’utilisation d’une fonctionnalité de la JVM appelée compilation hiérarchisée (ou « Tiered compilation »). À partir de la version 8 du Java Development Kit (JDK), les deux méthodes de compilation just-in-time C1 et C2 ont été utilisés en combinaison. La méthode de compilation C1 est conçue pour être utilisée côté client et pour permettre de courtes boucles de rétroaction pour les développeurs. La méthode de compilation C2 est conçue pour être utilisée côté serveur et pour obtenir des performances supérieures après le profilage (ou « profiling »).

La hiérarchisation permet de déterminer quel compilateur utiliser pour obtenir de meilleures performances. Les méthodes de compilation sont représentés par cinq niveaux :

Les 5 niveaux de compilations

Le profilage entraîne une surcharge au niveau du bytecode et les améliorations de performances ne sont uniquement obtenues qu’après qu’une méthode ait été invoquée plusieurs fois, la valeur par défaut étant de 10000. Les utilisateurs d’AWS Lambda qui souhaitent améliorer les temps de démarrage peuvent utiliser le niveau 1 avec peu de risque de réduire les performances de démarrage à chaud. L’article (en anglais) « Startup, containers & Tiered Compilation » explique plus en détails la compilation hiérarchisée.

Pour les utilisateurs qui effectuent des traitements très répétitifs, cette configuration peut ne pas convenir. Les applications qui répètent les mêmes chemins de code plusieurs fois bénéficient de l’optimisation effectuée par la JVM sur ces chemins avec le profilage. Deux exemples concrets seraient l’utilisation de Lambda pour exécuter des simulations de Monte Carlo ou des calculs de hachage. Vous pouvez exécuter les mêmes simulations des milliers de fois et le profilage de la JVM peut réduire considérablement le temps d’exécution total.

Optimisation des performances

L’exemple ci-dessous est une application basée sur Java 11 utilisée pour analyser l’impact de ce changement. L’application est appelée par Amazon API Gateway, puis enregistre une donnée dans Amazon DynamoDB. Pour comparer la différence de performances induite par cette modification, nous avons deux fonctions Lambda: une standard et une avec les modifications complémentaire. Il n’y a aucune différence au niveau du code.

Vous pouvez télécharger le code source de cet exemple depuis le projet GitHub : https://github.com/aws-samples/aws-lambda-java-tiered-compilation-example.

Pour installer les logiciels pré-requis :

  1. Installez AWS CDK.
  2. Installez Apache Maven ou utilisez votre IDE préféré.
  3. Construisez et packagez l’application Java dans le répertoire software :
    cd software/ExampleFunction/
    mvn package
  4. Affichez la synthèse de CDK. Vous pourrez vérifier les modifications prévues sur votre compte avant de les appliquer :
    cd infrastructure
    cdk synth 
  5. Déployez les fonctions Lambda :
    cdk deploy --outputs-file outputs.json

L’URL du point de terminaison API Gateway est affichée dans la sortie et enregistrée dans un fichier nommé outputs.json. Le contenu est similaire à ce qui suit :

ExampleTieredCompStack.apiendpoint = https://{YOUR_UNIQUE_ID_HERE}.execute-api.eu-west-1.amazonaws.com

Utilisation d’Artillery pour tester les changements

Tout d’abord, installez les logiciels pré-requis :

  1. Installez jq et Artillery Core.
  2. Exécutez les deux commandes suivantes dans le répertoire infrastructure :
    artillery run -t $(cat outputs.json | jq -r '.ExampleTieredCompStack.apiendpoint') -v '{ "url": "/without" }' loadtest.yml
    
    artillery run -t $(cat outputs.json | jq -r '.ExampleTieredCompStack.apiendpoint') -v '{ "url": "/with" }' loadtest.yml

Vérification des résultats avec Amazon CloudWatch Insights

  1. Accédez à Amazon CloudWatch.
  2. Dans Journaux, Sélectionnez Logs Insights.
  3. Sélectionnez les deux groupes de journaux (log groups) dans la liste déroulante :
    /aws/lambda/example-with-tiered-comp
    /aws/lambda/example-without-tiered-comp
  4. Copiez la requête suivante puis cliquez sur Exécuter la requête :
    filter @type = "REPORT"
        | parse @log /\d+:\/aws\/lambda\/example-(?<function>\w+)-\w+/
        | stats
        count(*) as invocations,
        pct(@duration, 0) as p0,
        pct(@duration, 25) as p25,
        pct(@duration, 50) as p50,
        pct(@duration, 75) as p75,
        pct(@duration, 90) as p90,
        pct(@duration, 95) as p95,
        pct(@duration, 99) as p99,
        pct(@duration, 100) as p100
        group by function, ispresent(@initDuration) as coldstart
        | sort by function, coldstart

Vous devriez obtenir :

Voici un tableau simplifié des résultats :

Settings Type

# of invocations

p0 (ms)

p90 (ms)

p95 (ms)

p99 (ms)

with Warm start

35581

8.14

28.4821

40.3863

78.6629

with Cold start

408

1808.52

2235.3309

2257.7851

2291.89

without Warm start

35193

9.63

65.9781

109.2131

299.2438

without Cold start

804

4354.64

5354.5817

5495.555

5674.1661

Les résultats proviennent du test de 120 demandes simultanées sur 5 minutes à l’aide d’un projet logiciel open source Artillery. Vous trouverez des instructions sur la façon d’exécuter ces tests dans le projet GitHub indiqué précédemment. Les résultats montrent que pour cette application, les démarrages à froid (ou « Cold start ») pour 90 % des appels s’améliorent de 3238 ms (58 %). Ces résultats sont spécifiques à cette application et votre application peut se comporter différemment.

Configuration de la fonction Lambda étape par étape

Cette modification peut être configurée à l’aide d’AWS SAM (Serverless Application Model : modèle d’application serverless), du CLI AWS (interface de ligne de commande), d’AWS CloudFormation ou depuis la console AWS.

Depuis la console AWS :

  1. Accédez à AWS Lambda.
  2. Sélectionnez Fonctions puis choisissez la fonction Lambda que vous souhaitez configurer.
  3. Depuis le menu à gauche, sélectionnez l’onglet Configuration puis Variables d’environment. Cliquez sur Modifier.
  4. Cliquez sur Ajouter des variables d’environment. Puis ajoutez les informations suivantes :
    • Clé : JAVA_TOOL_OPTIONS
    • Valeur : -XX:+TieredCompilation -XX:TieredStopAtLevel=1
    • Cliquez sur Enregistrer. Vous pouvez vérifier que les modifications ont bien été prises en compte en vérifiant les logs CloudWatch. Vous devez retrouver la ligne : Picked up JAVA_TOOL_OPTIONS: -XX:+TieredCompilation -XX:TieredStopAtLevel=1

Conclusion

L’arrêt de la compilation hiérarchisée au niveau 1 réduit le temps que la JVM consacre à l’optimisation et au profilage de votre code. Cela pourrait contribuer à réduire les temps de démarrage des applications Java qui nécessitent des réponses rapides, lorsque l’application ne répond pas aux exigences pour bénéficier du profilage.

Vous pouvez réduire davantage le temps de démarrage à l’aide de GraalVM. Pour en savoir plus sur GraalVM et le framework Quarkus, consultez cet article (en anglais) sur dans la catégorie Architecture. Consultez l’exemple de code à l’adresse https://github.com/aws-samples/aws-lambda-java-tiered-compilation-example pour savoir comment l’appliquer à vos fonctions Lambda.

Article original écrit par Mark Sailes, Specialist Solutions Architect Serverless et Richard Davison, Senior Partner Solutions Architect et adapté en français par Mickaël Bayé, Partner Solutions Architect dans l’équipe AWS France.