Le Blog Amazon Web Services

Une alternative à AWS Step Function pour des traitements simples

Cet article a été co-écrit par Louis PETITCOLAS, Développeur Cloud chez ATYOS en collaboration avec les équipes Partner Solutions Architect d’AWS France. Atyos se sert des technologies Cloud pour que leurs clients disposent de solutions leur permettant d’accélérer l’innovation propre à leur business.

Lorsqu’on doit distribuer un traitement simple AWS Lambda est un choix qui vient rapidement à l’esprit : large choix d’environnements d’exécution, pas d’infrastructure à gérer et uniquement la logique métier à gérer. Différents types de pattern consistent à orchestrer et coordonner l’exécution de ces fonctions Lambda avec AWS Step Function (machine à état). Malheureusement, cette approche peut ne pas être adapté à toutes les situations. Par exemple :

  • Le temps de changement d’état entre deux tâche excède 5 minutes,
  • Une tâche doit attendre le résultat d’une autre et attend un retour synchrone ou asynchrone,
  • Une tâche n’est pas idempotente et ne peut être exécutée qu’une seule fois (exactly-once).

AWS Step Function Standard permet bien d’adresser ces points, mais dispose d’un modèle de tarification différent : 25$ pour 1 million de changement d’états entre les tâches contre 1$ ainsi que la durée d’exécution du workflow pour la version Express. Vous trouverez dans la documentation la liste de toutes les différences fonctionnelles.

Nous avons également remarqué que pour certains traitements nécessitant peu de coordination et d’intégration avec d’autres services AWS, adopter Step Function était un investissement de temps parfois trop important pour nos clients.

Voici une architecture permettant de distribuer un calcul simple tout en générant un évènement à la fin de tous les traitements :

diagrame d'architecture

La première fonction Lambda (Input) est destinée à décomposer l’ensemble des traitements (1) dans de multiples éléments envoyés dans une file d’attente Amazon SQS (3), dont le nombre total est stocké dans une table DynamoDB, le compteur (2).

La Lambda 1 permet de réaliser le traitement de chaque élément (4). Pour chaque élément traité, le compteur stocké dans la table DynamoDB est décrémenté (5).

Une fois que le compteur atteint zéro (6), un évènement DynamoDB Stream exécute la fonction Lambda de fin de traitement qui notifie d’autres composants applicatifs (7).

Mise à jour du compteur dans une table DynamoDB

A la fin du traitement des données d’un élément, vous pouvez utiliser ce type de code pour mettre à jour le compteur dans la table DynamoDB :

response = input_batch_monitoring_table.update_item(
    Key={
        "PK": batch_id,
        "SK": "METADATA",
    },
    UpdateExpression="set data_left = data_left - :val",
    ExpressionAttributeValues={
         ':val': 1
    },
    ReturnValues="UPDATED_NEW"
 )

Notification de fin de traitement s’appuyant sur un évènement DynamoDB

Le traitement de l’ensemble des évènements est terminé une fois que le compteur est à zéro.

La création d’un déclencheur Lambda basé sur un évènement DynamoDB peut-être réalisé directement depuis la console AWS Lambda :

Sélectionner la fonction Lambda sur laquelle vous souhaitez ajouter un déclencheur puis “Ajouter un déclencheur”.

Une fois sur la page de configuration du déclencheur, choisir “DynamoDB” puis définir les différents paramètres (table, taille du lot etc).

Enfin dans les paramètres supplémentaires ajouter un critère de filtrage :
{ "dynamodb": { "NewImage": { "data_left": { "N": ["0"] } } } }

Une fois ajouté, on obtient ce résultat dans les configurations du déclencheur.

AWS SAM (Serverless Application Model) est une solution open source permettant la définition rapide de ressources AWS serverless et leurs déploiement. AWS SAM s’occupe de la création du template CloudFormation pour les différentes ressources. Voici le même exemple en utilisant SAM :

Events:
  DynamoStreamEvent:
    Type: DynamoDB
    Properties:
      BatchSize: 1
      StartingPosition: LATEST
      Stream:
        !GetAtt SEInputsBatchMonitoringTable.StreamArn
      FilterCriteria:
        Filters:
          - Pattern: '
            {
              "dynamodb": {
                "NewImage": {
                  "data_left": { "N": ["0"] }
                }
              }
            }'

Prise en compte des erreurs de traitement

Comme toujours, il est important de construire des architectures résilientes et de suivre les bonnes pratiques d’architectures. L’utilisation d’une file SQS permet de stocker les messages en erreurs dans une autre file de type « dead-letter » (DLQ).

Sans traitement correct des erreurs, certains traitements pourraient ne jamais se terminer.

Voici une amélioration de l’architecture précédente prenant en compte les traitement des erreurs.

La seconde fonction Lambda permet de décrémenter le compteur pour que le traitement global puisse se terminer.

Par ailleurs, cette fonction peut aussi permettre de stocker des informations complémentaires qui seront utiles à des fins de dépannage ou de reporting global.

Pour aller plus loin

Il est possible de stocker chaque élément de la DLQ dans une table DynamoDB pour pouvoir rejouer un traitement ou l’analyser à posteriori.

La fonction Lambda de fin de traitement peut aussi servir à consolider les erreurs dans un rapport qui sera transmis en complément de la notification.

Conclusion

Nous avons exploré dans cet article comment créer simplement une machine à état simplifié avec des fonctions Lambda et en utilisant DynamoDB pour stocker l’état de chaque exécution.