Messaging and Queuing
Pourquoi
Depuis que Signalements accepte des photos, chaque POST /signalements fait trois choses : enregistrer le signalement dans la base, déposer la photo dans le bucket, et prévenir par e-mail les agents de la voirie concernés. Après l'orage du mois dernier, une métropole a reçu huit cents signalements en une heure, presque tous avec photo. Les requêtes ont mis plusieurs secondes à répondre, certaines ont expiré côté répartiteur, et des agents ont reçu le même e-mail deux fois parce que l'application mobile avait réessayé une requête qui avait pourtant réussi.
Le problème n'est pas la puissance des instances. C'est que la requête HTTP attend des choses qui n'ont rien à faire dans la requête : redimensionner une photo de 6 Mo en trois tailles, ouvrir une connexion SMTP, attendre la réponse d'un serveur de messagerie. Tant que ces traitements vivent dans la requête, l'utilisateur paie leur lenteur, et une panne de l'un (le serveur de messagerie qui ne répond plus) devient une panne de tout.
La réponse classique est de découpler : la requête enregistre l'essentiel, dépose un message qui décrit le travail à faire, et répond aussitôt. D'autres processus, les consommateurs, lisent ces messages à leur rythme et font le travail. Si un consommateur tombe, les messages attendent. Si la charge monte, on ajoute des consommateurs. Scaleway propose trois services pour transporter ces messages ; cette leçon les distingue, met en place les deux dont Signalements a besoin, et traite la question que l'on oublie toujours : que se passe-t-il quand un message arrive deux fois ?
Les concepts
Trois services, deux modèles
Jusqu'en 2025, Scaleway regroupait ses services de messagerie sous un seul produit, Messaging and Queuing. La documentation indique qu'il a depuis été séparé en trois produits distincts ; la CLI garde la trace de l'ancien nom, puisque les trois se pilotent sous scw mnq (nats, sqs et sns).
| Service | Protocole | Modèle | Ce qu'il fait |
|---|---|---|---|
| Queues | API d'AWS SQS | file | Un message est lu et traité par un consommateur, puis supprimé |
| Topics and Events | API d'AWS SNS | publication-abonnement | Un message publié sur un sujet est poussé à chaque abonné (file, fonction, conteneur, URL) |
| NATS | NATS, avec JetStream | publication-abonnement et flux persistants | Messages par sujet (subject), en temps réel ; flux conservés et relus avec JetStream |
Les deux premiers sont des implémentations maison de protocoles d'AWS : la FAQ de Queues précise que le service n'a aucune dépendance envers l'infrastructure d'AWS, mais qu'il parle la même API, ce qui permet d'utiliser les SDK d'AWS (boto3 en Python) et des outils qui les emploient, comme KEDA. NATS est un logiciel libre de la CNCF, que Scaleway exploite pour vous.
Le choix de modèle compte plus que le choix de produit :
- Une file de messages (message queue) répartit le travail : chaque photo doit être redimensionnée une fois, par n'importe lequel des consommateurs disponibles. Ajouter des consommateurs augmente le débit.
- La publication-abonnement (publish/subscribe, ou pub/sub) diffuse un événement : « un signalement a été créé » intéresse le service d'e-mails, le tableau de bord de la métropole et l'archivage. L'émetteur ne sait pas qui écoute, et chaque abonné reçoit sa copie.
Les deux se combinent souvent : un sujet Topics and Events dont l'un des abonnés est une file Queues. Le sujet diffuse, la file absorbe les pics et répartit le travail entre plusieurs consommateurs.
Au moins une fois
Aucun de ces services ne garantit qu'un message sera livré exactement une fois. La documentation de Queues est explicite : les files standard offrent une livraison « au moins une fois » (at-least-once), et un même message peut, dans de rares cas, être reçu plusieurs fois. Les sujets standard de Topics and Events ont la même garantie.
Ce n'est pas une faiblesse de Scaleway : c'est une propriété de tout système réparti qui ne veut pas perdre de messages. Pour ne rien perdre, le service doit garder le message tant qu'il n'a pas la preuve qu'il a été traité. Or cette preuve (la suppression du message par le consommateur) peut se perdre : le consommateur a fini son travail, puis plante, ou perd le réseau, juste avant de confirmer. Le service, ne voyant rien venir, livre à nouveau. Entre « parfois deux fois » et « parfois jamais », les services de messagerie choisissent le premier, et laissent à l'application le soin de tolérer les doublons.
Le délai de visibilité
Dans une file de type SQS, recevoir un message ne le supprime pas. Le message devient invisible pendant un délai de visibilité (visibility timeout), le temps que le consommateur le traite. Deux issues :
- le consommateur termine et supprime explicitement le message (
DeleteMessage, avec le receipt handle reçu) ; - le délai expire sans suppression : le message redevient visible, et un autre consommateur (ou le même) le reçoit à nouveau.
sequenceDiagram
participant A as API Signalements
participant F as File sig-photos
participant C as Consommateur
A->>F: SendMessage (photo 4812)
C->>F: ReceiveMessage
F-->>C: message + receipt handle
Note over F: invisible pendant le délai (120 s)
C->>C: redimensionne la photo
C->>F: DeleteMessage (receipt handle)
Note over F: message supprimé
Le délai se règle par file. Chez Scaleway, il vaut 30 secondes par défaut et va de 1 seconde à 12 heures. Il doit couvrir largement le temps de traitement le plus long que vous attendez : trop court, le message réapparaît pendant qu'on le traite encore, et deux consommateurs font le même travail en parallèle.
Warning
Chez AWS, un consommateur dont le traitement s'allonge peut prolonger le délai de visibilité d'un message en cours (ChangeMessageVisibility), ce que la documentation d'AWS recommande sous forme de « battement de cœur ». La page des actions prises en charge par Scaleway Queues indique que cette action n'accepte que deux valeurs : 0 (rendre le message visible tout de suite) et le délai courant de la file. On ne peut donc pas prolonger un message au cas par cas. Dimensionnez le délai de la file pour le pire cas, ou découpez les traitements longs.
La file de lettres mortes
Un message qui fait planter le consommateur à chaque fois (une photo corrompue, un format inattendu) reviendrait indéfiniment, consommerait des ressources à chaque tentative et retarderait les autres. La file de lettres mortes (dead-letter queue, DLQ) y met fin : après un nombre maximal de réceptions (maximum receive count, de 1 à 1 000), le message est déplacé dans une autre file, où il attend qu'une personne l'examine.
La documentation de Scaleway impose que la file de lettres mortes soit du même type (standard ou FIFO), dans le même projet et dans la même région que les files qu'elle sert. Une même file peut servir de lettres mortes à plusieurs files. Elle compte dans le quota de stockage du projet : si ce quota est atteint, les messages ne sont plus déplacés vers elle.
FIFO, et ce qu'il coûte ici
Les files standard ne garantissent pas l'ordre. Les files FIFO (first in, first out) garantissent l'ordre et l'absence de doublon, avec une option de déduplication par contenu qui calcule un identifiant à partir du corps du message.
Chez AWS, l'ordre d'une file FIFO est garanti par groupe (MessageGroupId) : les messages de groupes différents se traitent en parallèle. La FAQ de Scaleway Queues indique que MessageGroupId n'est pas pris en charge, et que, pour garantir l'ordre, une file FIFO ne laisse qu'un seul message en cours de traitement à la fois. Une file FIFO de Scaleway est donc séquentielle : son débit est celui d'un seul consommateur. Réservez-la aux flux qui l'exigent vraiment et restent modestes.
L'idempotence
Puisque les doublons sont possibles, le consommateur doit être idempotent : traiter deux fois le même message doit produire le même résultat que le traiter une fois. Trois techniques, souvent combinées :
- Un résultat déterministe. Le redimensionnement de la photo
photos/2026/10/4812.jpgécrit toujours dansminiatures/2026/10/4812-400.jpg. Le refaire écrase le fichier par un fichier identique : aucun dommage. - Une vérification préalable. Avant d'envoyer l'e-mail « nouveau signalement 4812 », regarder si la base indique déjà
notifie_lepour ce signalement. - Une table des messages traités. Enregistrer l'identifiant du message (ou une clé métier) dans une table avec une contrainte d'unicité, dans la même transaction que l'effet. Un doublon échoue sur la contrainte et est ignoré.
La troisième est la seule qui tienne quand l'effet n'est pas naturellement idempotent (un débit, un envoi d'e-mail) ; encore faut-il que l'effet et l'enregistrement soient atomiques. Un e-mail parti ne se reprend pas : le mieux que l'on puisse faire est de réduire la fenêtre où un doublon est possible.
Les identifiants
Ces services n'utilisent pas les clés d'API IAM du projet. Chacun a ses propres identifiants (credentials), créés par projet et par région :
- pour Queues et Topics and Events, une clé d'accès et une clé secrète au format AWS, avec trois permissions combinables : publier (
can-publish), recevoir (can-receive), gérer les files ou sujets (can-manage) ; - pour NATS, un fichier
.credspar compte NATS, qui donne tous les droits sur ce compte : la documentation précise que ces identifiants ne sont pas granulaires.
La création et la suppression des identifiants passent, elles, par l'IAM de Scaleway : il faut un jeu de permissions sur la messagerie dans le projet.
En pratique
L'objectif pour Signalements :
- une file standard
sig-photos, alimentée par l'API à chaque photo, consommée par un processus de redimensionnement, avec sa file de lettres mortessig-photos-dlq; - un sujet
sig-evenements, sur lequel l'API publie « signalement créé », avec deux abonnés : une filesig-notificationslue par le processus d'envoi d'e-mails, et le point d'entrée HTTPS du système d'information d'une métropole.
Les commandes scw ci-dessous ont été vérifiées avec l'aide de la CLI 2.62 ; la création des files et des sujets passe par l'API SQS et SNS, donc par l'AWS CLI ou boto3. Nous travaillons dans le projet signalements-preprod d'abord.
Activer le service et créer des identifiants séparés
$ scw mnq sqs activate region=fr-par
$ scw mnq sqs get-info region=fr-par -o json | jq -r .sqs_endpoint_url
La seconde commande affiche l'adresse de l'API SQS de la région, de la forme documentée https://sqs.mnq.fr-par.scaleway.com. Créez ensuite trois jeux d'identifiants, un par rôle :
$ scw mnq sqs create-credentials name=sig-admin-files \
permissions.can-manage=true region=fr-par -o json > .tmp/sqs-admin.json
$ scw mnq sqs create-credentials name=sig-api-publie \
permissions.can-publish=true region=fr-par -o json > .tmp/sqs-api.json
$ scw mnq sqs create-credentials name=sig-worker-recoit \
permissions.can-receive=true region=fr-par -o json > .tmp/sqs-worker.json
$ chmod 600 .tmp/sqs-*.json
sig-admin-filessert à créer et régler les files ; il ne quitte pas votre poste.sig-api-publieest donné à l'API : elle peut déposer des messages, pas en lire ni supprimer de files.sig-worker-recoitest donné aux consommateurs : ils lisent et suppriment des messages, sans pouvoir en publier.
La réponse JSON contient access_key et secret_key (champs de la structure SqsCredentials du SDK) ; la clé secrète n'est affichée qu'à la création. Le jour où la clé de l'API fuit, l'attaquant peut remplir la file, pas lire les photos des autres ni vider la file.
Créer la file et sa file de lettres mortes
Configurez l'AWS CLI avec les identifiants d'administration, le temps de cette étape :
$ export AWS_ACCESS_KEY_ID=$(jq -r .access_key .tmp/sqs-admin.json)
$ export AWS_SECRET_ACCESS_KEY=$(jq -r .secret_key .tmp/sqs-admin.json)
$ export AWS_DEFAULT_REGION=fr-par
$ SQS=https://sqs.mnq.fr-par.scaleway.com
La file de lettres mortes d'abord, puisque la file principale doit la désigner :
$ aws sqs create-queue --endpoint-url $SQS --queue-name sig-photos-dlq \
--attributes MessageRetentionPeriod=1209600
$ DLQ_URL=$(aws sqs get-queue-url --endpoint-url $SQS --queue-name sig-photos-dlq \
--query QueueUrl --output text)
$ DLQ_ARN=$(aws sqs get-queue-attributes --endpoint-url $SQS --queue-url "$DLQ_URL" \
--attribute-names QueueArn --query Attributes.QueueArn --output text)
La rétention de 1 209 600 secondes (14 jours, le maximum) laisse le temps d'examiner les messages en échec. Puis la file principale :
$ aws sqs create-queue --endpoint-url $SQS --queue-name sig-photos --attributes \
"VisibilityTimeout=120,MessageRetentionPeriod=345600,RedrivePolicy={\"deadLetterTargetArn\":\"$DLQ_ARN\",\"maxReceiveCount\":\"5\"}"
VisibilityTimeout=120: un redimensionnement prend quelques secondes, rarement plus de trente ; deux minutes laissent une marge pour une photo énorme ou une instance chargée, sans retarder de trop la nouvelle tentative après un plantage.MessageRetentionPeriod=345600: quatre jours. La valeur par défaut de Scaleway est de 60 secondes, beaucoup plus courte que celle d'AWS : une file créée sans ce réglage perd tout message non lu au bout d'une minute, ce qui transforme la moindre panne des consommateurs en perte de données.RedrivePolicy: après cinq réceptions sans suppression, le message part danssig-photos-dlq.
Vérifiez les réglages :
$ aws sqs get-queue-attributes --endpoint-url $SQS --attribute-names All \
--queue-url "$(aws sqs get-queue-url --endpoint-url $SQS --queue-name sig-photos \
--query QueueUrl --output text)"
La réponse liste les attributs pris en charge, dont VisibilityTimeout, MessageRetentionPeriod, RedrivePolicy et ApproximateNumberOfMessages.
Publier depuis l'API
Dans l'API, la route qui crée un signalement dépose un message après avoir validé la transaction en base. Le message ne contient que ce qu'il faut pour retrouver le travail, pas la photo : les messages sont limités à 256 Ko, et une photo vit dans le bucket.
import json
import os
import boto3
sqs = boto3.client(
"sqs",
endpoint_url=os.environ["SQS_ENDPOINT"],
region_name="fr-par",
aws_access_key_id=os.environ["SQS_ACCESS_KEY"],
aws_secret_access_key=os.environ["SQS_SECRET_KEY"],
)
FILE_PHOTOS = os.environ["SQS_FILE_PHOTOS"]
def demander_redimensionnement(ident: int, cle_photo: str) -> None:
"""Dépose le travail de redimensionnement, après la validation en base."""
sqs.send_message(
QueueUrl=FILE_PHOTOS,
MessageBody=json.dumps({"signalement": ident, "cle": cle_photo}),
)Remarquez l'ordre : d'abord la transaction en base, ensuite le message. Dans l'ordre inverse, un consommateur rapide pourrait recevoir le message avant que le signalement n'existe. Il reste une fenêtre : la base est validée, puis l'API plante avant d'envoyer le message. Le signalement existe sans miniature. Pour la fermer complètement, on écrit le message dans une table de la base, dans la même transaction, et un processus séparé le publie (le motif transactional outbox) ; pour des miniatures, une tâche de rattrapage nocturne qui redemande celles qui manquent suffit souvent.
Un consommateur idempotent
Voici le consommateur complet. Il lit par lots, traite chaque message, supprime ce qui a réussi, et laisse le reste revenir après le délai de visibilité :
"""Consommateur de la file sig-photos : produit les miniatures des photos."""
import json
import logging
import os
import signal
import boto3
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
journal = logging.getLogger("sig-photos")
sqs = boto3.client(
"sqs",
endpoint_url=os.environ["SQS_ENDPOINT"],
region_name="fr-par",
aws_access_key_id=os.environ["SQS_ACCESS_KEY"],
aws_secret_access_key=os.environ["SQS_SECRET_KEY"],
)
s3 = boto3.client("s3", endpoint_url="https://s3.fr-par.scw.cloud", region_name="fr-par")
FILE = os.environ["SQS_FILE_PHOTOS"]
BUCKET = os.environ["BUCKET"]
TAILLES = (400, 1200)
continuer = True
def arreter(signum, frame):
"""SIGTERM : finir le lot en cours, puis s'arrêter proprement."""
global continuer
continuer = False
signal.signal(signal.SIGTERM, arreter)
def cle_miniature(cle: str, taille: int) -> str:
"""Nom déterministe : refaire le travail réécrit le même objet."""
base, _, _ = cle.rpartition(".")
return base.replace("photos/", "miniatures/", 1) + f"-{taille}.jpg"
def deja_fait(cle: str) -> bool:
"""Vrai si la plus grande miniature existe déjà (message en double)."""
try:
s3.head_object(Bucket=BUCKET, Key=cle_miniature(cle, TAILLES[-1]))
return True
except s3.exceptions.ClientError:
return False
def traiter(corps: dict) -> None:
cle = corps["cle"]
if deja_fait(cle):
journal.info("déjà traité : %s", cle)
return
original = s3.get_object(Bucket=BUCKET, Key=cle)["Body"].read()
for taille in TAILLES:
miniature = redimensionner(original, taille)
s3.put_object(Bucket=BUCKET, Key=cle_miniature(cle, taille), Body=miniature,
ContentType="image/jpeg")
journal.info("miniatures produites : %s", cle)
def redimensionner(donnees: bytes, largeur: int) -> bytes:
"""Redimensionne une image JPEG (Pillow)."""
from io import BytesIO
from PIL import Image
image = Image.open(BytesIO(donnees))
image.thumbnail((largeur, largeur))
sortie = BytesIO()
image.convert("RGB").save(sortie, format="JPEG", quality=85)
return sortie.getvalue()
while continuer:
reponse = sqs.receive_message(QueueUrl=FILE, MaxNumberOfMessages=10, WaitTimeSeconds=20)
for message in reponse.get("Messages", []):
try:
traiter(json.loads(message["Body"]))
except Exception:
journal.exception("échec, le message reviendra : %s", message["MessageId"])
continue
sqs.delete_message(QueueUrl=FILE, ReceiptHandle=message["ReceiptHandle"])Les points qui comptent :
WaitTimeSeconds=20active la scrutation longue (long polling), prise en charge par Scaleway : l'appel attend jusqu'à vingt secondes qu'un message arrive au lieu de répondre vide immédiatement. Sans elle, une boucle de consommateurs inactifs enchaîne les requêtes vides.- La suppression vient après le traitement, et seulement s'il a réussi. Une exception laisse le message revenir après le délai de visibilité ; au cinquième échec, il part en lettres mortes.
deja_faitrend le traitement idempotent à peu de frais. Même sans elle, le résultat serait correct (les noms sont déterministes), mais on referait un travail coûteux.- SIGTERM est intercepté : quand systemd ou l'orchestrateur arrête le processus, il finit le lot en cours au lieu d'abandonner des messages au milieu de leur traitement (voir Les processus et les signaux).
- Le client S3 prend ses identifiants dans l'environnement standard d'AWS (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY), qui portent ici une clé d'API Scaleway limitée au stockage objet : ce ne sont pas les identifiants de la file.
Tip
Au lieu d'un processus qui tourne en permanence, le consommateur peut être un conteneur serverless déclenché par la file : Serverless Containers et Functions proposent des déclencheurs Queues, qui appellent le conteneur avec le contenu du message (leçons 5 et 6). La documentation précise qu'une réponse de code 300 ou plus provoque jusqu'à trois nouvelles tentatives. Le conteneur doit rester idempotent : le déclencheur ne change rien à la garantie « au moins une fois ».
Diffuser un événement avec Topics and Events
Pour les notifications, l'API publie un événement sur un sujet, et les abonnés s'organisent. Activez le service et créez des identifiants de la même façon :
$ scw mnq sns activate region=fr-par
$ scw mnq sns get-info region=fr-par -o json | jq -r .sns_endpoint_url
$ scw mnq sns create-credentials name=sig-admin-sujets \
permissions.can-manage=true permissions.can-receive=true region=fr-par -o json > .tmp/sns-admin.json
La permission can-receive sert ici à configurer des abonnements (c'est ainsi que l'aide de la CLI la décrit pour ce service). Créez le sujet, la file des notifications, et abonnez la file au sujet :
$ SNS=https://sns.mnq.fr-par.scaleway.com
$ TOPIC_ARN=$(aws sns create-topic --endpoint-url $SNS --name sig-evenements \
--query TopicArn --output text)
$ aws sqs create-queue --endpoint-url $SQS --queue-name sig-notifications \
--attributes VisibilityTimeout=60,MessageRetentionPeriod=345600
$ NOTIF_ARN=$(aws sqs get-queue-attributes --endpoint-url $SQS --attribute-names QueueArn \
--queue-url "$(aws sqs get-queue-url --endpoint-url $SQS --queue-name sig-notifications \
--query QueueUrl --output text)" --query Attributes.QueueArn --output text)
$ aws sns subscribe --endpoint-url $SNS --topic-arn "$TOPIC_ARN" \
--protocol sqs --notification-endpoint "$NOTIF_ARN"
Ces deux dernières commandes utilisent les deux jeux d'identifiants d'administration : passez de l'un à l'autre en changeant les variables AWS_*, ou définissez deux profils dans ~/.aws/config, comme le propose la documentation de Scaleway. La file abonnée doit être dans le même projet et la même région que le sujet.
L'abonnement du système de la métropole est un abonnement HTTPS :
$ aws sns subscribe --endpoint-url $SNS --topic-arn "$TOPIC_ARN" \
--protocol https --notification-endpoint https://si.metropole.example/webhooks/signalements
Un abonnement HTTP ou HTTPS reste en attente (Pending) tant qu'il n'est pas confirmé : le service envoie à l'adresse un message de confirmation qui contient une SubscribeURL, et quelqu'un doit la visiter (ou la saisir dans la console). C'est une protection : sans elle, n'importe qui pourrait abonner une adresse tierce et l'inonder de messages.
Note
Le domaine example utilisé ici est réservé aux exemples par la RFC 2606 ; remplacez-le par l'adresse réelle du destinataire.
Ce que l'abonné HTTPS doit vérifier
Le système de la métropole reçoit des requêtes POST sur une adresse publique. Il doit s'assurer qu'elles viennent bien de Scaleway. Comme avec SNS, chaque message est signé, et porte l'adresse du certificat de signature (SigningCertURL). La documentation de Scaleway donne des consignes précises :
- ne pas faire confiance directement au certificat désigné par
SigningCertURL, mais vérifier qu'il a été émis par l'autorité de Scaleway, dont la chaîne de confiance est publiée pour chaque région (valable jusqu'en 2032) ; - mettre ce certificat en cache, en utilisant l'identifiant de l'URL comme clé, et ne le revalider que si l'URL change ;
- s'attendre à une rotation au moins annuelle, et accepter pendant la transition des messages signés par l'ancien et le nouveau certificat.
Un point d'entrée qui accepte tout ce qui arrive en POST est une porte ouverte : n'importe qui peut y injecter de faux signalements.
NATS, quand l'utiliser
NATS se gère différemment : on crée un compte NATS, puis des identifiants qui sont un fichier .creds :
$ NATS_ID=$(scw mnq nats create-account name=sig-temps-reel region=fr-par -o json | jq -r .id)
$ scw mnq nats get-account "$NATS_ID" region=fr-par -o json | jq -r .endpoint
$ scw mnq nats create-credentials "$NATS_ID" name=sig-tableau region=fr-par \
-o json | jq -r .credentials.content > .tmp/sig-tableau.creds
$ chmod 600 .tmp/sig-tableau.creds
$ scw mnq nats create-context --help
La structure NatsCredentials du SDK porte le contenu du fichier dans credentials.content ; la commande create-context prépare un contexte pour l'outil nats, l'interface en ligne de commande officielle du projet NATS. Ensuite, tout se fait avec les outils de NATS : publier sur un sujet (nats pub), s'abonner (nats sub), créer un flux JetStream (nats stream add).
NATS brille pour le temps réel et les messages nombreux et petits : le tableau de bord de la métropole qui affiche les signalements au fil de l'eau, par exemple, abonné à signalements.metropole-nord.>. Avec JetStream, un flux conserve les messages et permet de les relire, ce qu'une file SQS ne fait pas. La documentation de Scaleway signale deux contraintes : les flux en mémoire ne sont pas pris en charge (stockage File obligatoire), et une politique de rétention autre que Work Queue fait payer les messages conservés. Et un fichier .creds donne tous les droits sur le compte : un compte NATS par usage limite les dégâts d'une fuite.
Pour Signalements, le choix est simple : Queues pour le travail (photos, e-mails), Topics and Events pour la diffusion d'événements à des systèmes tiers, et NATS seulement si le besoin de temps réel apparaît.
Nettoyer
$ aws sqs delete-queue --endpoint-url $SQS --queue-url "$DLQ_URL"
$ scw mnq sqs list-credentials region=fr-par
$ scw mnq sqs delete-credentials <id> region=fr-par
Supprimez de même les autres files, le sujet (aws sns delete-topic), le compte NATS et les identifiants dont vous n'avez plus besoin, puis désactivez les services inutilisés (scw mnq sqs deactivate). Effacez les fichiers de .tmp/.
Sous le capot
Pourquoi une file ne pousse pas. Une file SQS est tirée (pull) : c'est le consommateur qui demande des messages. Le service n'a pas à connaître ses consommateurs, à savoir s'ils sont vivants ou à gérer leur débit : un consommateur saturé ne demande simplement plus rien. Topics and Events, au contraire, pousse (push) vers ses abonnés ; il doit donc gérer les échecs de livraison et les nouvelles tentatives, et l'abonné doit pouvoir encaisser le débit. Abonner une file à un sujet combine les deux : le sujet pousse vers la file, qui ne refuse jamais, et les consommateurs tirent de la file à leur rythme.
Ce que signifient les compteurs. L'attribut ApproximateNumberOfMessages compte les messages visibles, en attente ; ApproximateNumberOfMessagesNotVisible, ceux qui sont en cours de traitement. Leur nom dit « approximatif » : dans un système réparti, le compte exact à un instant donné n'existe pas. La page des attributs pris en charge par Scaleway précise un comportement particulier : un message livré une fois reste compté comme « non visible » jusqu'à sa suppression, même après l'expiration de son délai de visibilité. Ne pilotez pas une alerte sur ce second compteur sans le savoir.
Une file FIFO séquentielle. Garantir l'ordre global d'une file, sans notion de groupe, oblige à ne livrer le message suivant qu'une fois le précédent supprimé : si deux consommateurs traitaient deux messages en parallèle, rien ne garantirait l'ordre de leurs effets. C'est pourquoi l'absence de MessageGroupId rend une file FIFO de Scaleway strictement séquentielle, quel que soit le nombre de consommateurs.
Les identifiants au format AWS. Les SDK d'AWS signent chaque requête avec l'algorithme Signature Version 4, à partir de la clé secrète, de la date, de la région et du service. C'est pourquoi la région doit être renseignée (fr-par) même si l'adresse du service est explicite : elle entre dans le calcul de la signature, et une région incohérente produit une erreur de signature, pas une erreur de connexion.
Pièges courants
La rétention de 60 secondes. Une file créée sans MessageRetentionPeriod garde ses messages une minute. Le premier redémarrage un peu long des consommateurs vide la file sans bruit. Fixez toujours la rétention à la création.
Un délai de visibilité plus court que le traitement. Le message réapparaît pendant qu'on le traite ; deux consommateurs le traitent ; un troisième aussi au tour suivant ; et, faute de suppression à temps, il finit en lettres mortes alors que le traitement réussit. Le symptôme typique : des doublons de travail et une file de lettres mortes qui se remplit de messages parfaitement valides.
Supprimer avant de traiter. Un consommateur qui supprime le message dès sa réception, « pour éviter les doublons », perd tous les messages en cours au moindre plantage. On supprime après avoir terminé.
Mettre la donnée dans le message. Une photo encodée en base64 dépasse vite la taille maximale (256 Ko pour Queues) et coûte en volume facturé. Le message transporte une référence (clé de l'objet, identifiant en base), la donnée reste dans le stockage.
Une erreur de signature avec un SDK pourtant bien configuré. Vérifiez la région (fr-par, pas eu-west-3), l'adresse du service (sqs.mnq... pour les files, sns.mnq... pour les sujets) et le jeu d'identifiants : ceux de Queues ne valent pas pour Topics and Events.
Compter sur l'ordre d'une file standard. « Signalement créé » puis « signalement clos » peuvent arriver dans l'ordre inverse. Le consommateur doit le tolérer, par exemple en comparant une date ou un numéro de version de l'objet plutôt qu'en appliquant aveuglément le dernier message reçu.
Attendre une connexion depuis le réseau privé. La documentation indique que Queues et NATS ne sont pas compatibles avec les VPC de Scaleway, au 5 octobre 2026 : les consommateurs y accèdent par leur adresse publique, ce qui suppose une sortie Internet (la passerelle publique de la leçon 6 du cours précédent, par exemple).
Sécurité
- Un jeu d'identifiants par rôle, avec la seule permission nécessaire : l'API publie, les consommateurs reçoivent, l'administration gère. Une fuite de la clé de l'API ne permet pas de lire les messages.
- Les identifiants sont des secrets : ils ne vont ni dans le code, ni dans les données utilisateur de cloud-init. Rangez-les dans Secret Manager (leçon 8) et injectez-les au démarrage.
- Le contenu des messages n'est pas chiffré par l'application. N'y mettez pas de données personnelles inutiles : une référence (« signalement 4812 ») plutôt que le nom et le téléphone de l'habitant. Une file de lettres mortes conserve ses messages quatorze jours : ce qui y entre doit pouvoir y rester.
- Vérifier la signature des webhooks, côté abonné HTTPS, selon la procédure de Scaleway : sans elle, le point d'entrée accepte des messages de n'importe qui.
- Un message est une entrée non fiable. Le consommateur valide le contenu (types, longueur, clé d'objet qui commence bien par
photos/) comme il le ferait pour une requête HTTP : un message forgé ne doit pas lui faire lire ou écrire n'importe où dans le bucket. - Les fichiers
.credsde NATS donnent tous les droits sur le compte : un compte par usage, et une révocation (delete-credentials) au moindre doute.
En production
- Surveillez la profondeur des files et leurs lettres mortes. Chaque service a un tableau de bord dans Cockpit (leçon 14). Deux alertes suffisent pour commencer : une file principale qui grossit sans redescendre (les consommateurs ne suivent plus, ou sont arrêtés), et une file de lettres mortes non vide (un message à examiner).
- Les quotas sont bas. Au 5 octobre 2026, la page des quotas indique pour Queues 100 Mo de stockage pour toutes les files d'un projet, 50 files par projet, 256 Ko par message ; pour NATS, 10 comptes par organisation, 300 Mo de flux par compte (100 Mo avec trois répliques) ; pour Topics and Events, 50 sujets et 50 abonnés par sujet. Une file de lettres mortes pleine bloque les déplacements ; une panne prolongée des consommateurs peut atteindre la limite de stockage : dimensionnez la rétention en conséquence.
- Faites varier le nombre de consommateurs avec la profondeur de la file. Sur Kubernetes, KEDA, compatible avec Queues d'après la documentation, ajoute des réplicas quand la file grossit. Avec des déclencheurs serverless, la plateforme s'en charge.
- Prévoyez le rejeu. Un outil (un petit script) qui relit la file de lettres mortes, affiche les messages et les renvoie dans la file principale une fois le défaut corrigé. Testez-le avant d'en avoir besoin.
- Facturation. Queues est facturé au volume de messages, Topics and Events au volume envoyé aux abonnés, NATS au volume et à la persistance. Les prix sont sur la page tarifs de Scaleway ; pour Signalements, quelques dizaines de milliers de messages de quelques centaines d'octets par mois représentent un volume minime.
- Chez Lyneko, la règle retenue est la même que pour Signalements : un traitement qui peut prendre plus d'une seconde, ou qui dépend d'un service externe, ne vit pas dans la requête.
Exercices
1. File ou sujet ? (niveau 200). Pour chacun des besoins suivants, dites s'il faut une file, un sujet, les deux, ou NATS, et justifiez. (a) Générer le PDF mensuel de chaque métropole, un par métropole. (b) Prévenir l'archivage, le tableau de bord et le service d'e-mails qu'un signalement a été clos. (c) Afficher sur un écran du centre technique, en direct, la position des équipes. (d) Envoyer les e-mails de notification sans dépasser le débit autorisé par le service d'e-mails.
Solution
(a) Une file : chaque PDF est un travail à faire une fois, réparti entre consommateurs ; un déclencheur CRON (leçon 6) peut déposer les messages. (b) Un sujet : trois abonnés indépendants veulent chacun l'événement ; chaque abonné qui fait un travail lourd peut être une file abonnée. (c) NATS : des messages petits, nombreux, en temps réel, dont la valeur disparaît en quelques secondes ; une file serait inutilement lourde. (d) Une file devant le service d'e-mails : les consommateurs tirent à un rythme qu'ils maîtrisent, et la file absorbe les pics. Le sujet de (b) peut alimenter cette file.
2. Les doublons d'e-mails (niveau 200). Les agents reçoivent parfois deux fois le même e-mail. Le consommateur de sig-notifications envoie l'e-mail, puis supprime le message. Proposez deux causes plausibles, et une correction qui rende l'envoi idempotent autant que possible.
Solution
Causes : un envoi qui dure plus longtemps que le délai de visibilité (60 secondes), si le serveur de messagerie est lent, et le message réapparaît pendant l'envoi ; ou un plantage, une perte de réseau ou un arrêt du processus entre l'envoi et la suppression ; ou, plus rarement, une double livraison par le service. Correction : avant d'envoyer, insérer dans une table notifications_envoyees une ligne avec une contrainte d'unicité sur (signalement, destinataire, type) ; si l'insertion échoue, le message est un doublon, on le supprime sans rien envoyer. Envoyer seulement après une insertion réussie, puis supprimer le message. Il reste une fenêtre (plantage après insertion et avant envoi : e-mail jamais envoyé) qu'une tâche de rattrapage peut couvrir en relisant les lignes sans date d'envoi. Augmenter aussi le délai de visibilité au-delà du pire temps d'envoi.
3. Lire les lettres mortes (niveau 200). Écrivez, avec boto3, une fonction qui lit au plus dix messages de sig-photos-dlq, affiche leur identifiant et leur corps, et, si on le lui demande, les renvoie dans sig-photos puis les supprime de la file de lettres mortes. Quel jeu d'identifiants faut-il, et dans quel ordre faire renvoi et suppression ?
Solution
def rejouer(sqs, url_dlq: str, url_file: str, renvoyer: bool = False) -> None:
reponse = sqs.receive_message(QueueUrl=url_dlq, MaxNumberOfMessages=10, WaitTimeSeconds=1)
for message in reponse.get("Messages", []):
print(message["MessageId"], message["Body"])
if renvoyer:
sqs.send_message(QueueUrl=url_file, MessageBody=message["Body"])
sqs.delete_message(QueueUrl=url_dlq, ReceiptHandle=message["ReceiptHandle"])Il faut des identifiants avec can-receive (lire et supprimer dans la file de lettres mortes) et can-publish (renvoyer dans la file principale) ; créez-en un jeu dédié à cet outil, utilisé ponctuellement. Renvoyer avant de supprimer : un plantage entre les deux produit au pire un doublon (que le consommateur idempotent tolère), alors que l'ordre inverse peut perdre le message. Sans renvoi, les messages lus redeviennent visibles dans la file de lettres mortes après son délai de visibilité.
4. FIFO ou pas (niveau 200). Un collègue propose de passer sig-photos en FIFO « pour éviter les doublons ». Que répondez-vous, au vu de la documentation de Scaleway ?
Solution
Une file FIFO de Scaleway ne laisse qu'un message en cours à la fois, faute de MessageGroupId : le redimensionnement deviendrait séquentiel, avec un seul consommateur utile, au moment précis où l'on a besoin de débit (les pics après un orage). Les doublons sont déjà sans effet grâce aux noms déterministes et à la vérification préalable. L'ordre des photos n'a aucune importance. Il faut garder une file standard et un consommateur idempotent.
Récapitulatif
- Découpler sort de la requête ce qui est lent ou dépend d'un tiers : la requête dépose un message, des consommateurs font le travail à leur rythme.
- Scaleway propose Queues (API SQS, file : un message, un consommateur), Topics and Events (API SNS, publication-abonnement poussée vers des files, fonctions, conteneurs ou URL) et NATS (temps réel, flux persistants avec JetStream).
- La livraison est au moins une fois : le consommateur doit être idempotent (résultat déterministe, vérification préalable, table des messages traités).
- Un message reçu devient invisible pendant le délai de visibilité, puis revient s'il n'est pas supprimé ; chez Scaleway, il ne se prolonge pas message par message. Après plusieurs échecs, la file de lettres mortes le met de côté.
- La rétention par défaut est de 60 secondes : à fixer à la création. Les files FIFO de Scaleway sont strictement séquentielles.
- Des identifiants par rôle (publier, recevoir, gérer), des messages qui transportent des références et pas des données, des webhooks dont on vérifie la signature.
Pour aller plus loin
- La page des actions SQS prises en charge par Scaleway Queues : la lire avant d'adopter une bibliothèque qui suppose une fonction d'AWS.
- La documentation de JetStream sur le site du projet NATS, pour les politiques de rétention et d'acquittement.
- Le guide du développeur d'Amazon SQS, dont les chapitres sur le délai de visibilité et les files de lettres mortes décrivent le même protocole.
- La leçon 5, pour déclencher un conteneur à partir d'une file, et la leçon 14, pour alerter sur la profondeur des files.
- Le cours Files de messages : NATS, RabbitMQ, pour les courtiers que l'on exploite soi-même.
Sources
- Scaleway, Queues : concepts
- Scaleway, Queues : actions de l'API SQS prises en charge
- Scaleway, Queues : FAQ (MessageGroupId et files FIFO)
- Scaleway, Topics and Events : concepts et abonnements
- Scaleway, Topics and Events : vérifier les webhooks
- Scaleway, NATS : concepts et limites
- Scaleway, quotas d'une organisation (NATS, Queues, Topics and Events)
- Scaleway, Serverless Containers : déclencheurs
- AWS, Amazon SQS Developer Guide : visibility timeout
- NATS, documentation de JetStream : streams
- scaleway/scaleway-cli, commandes mnq
- Boto3, documentation du client SQS