7
votes

Les notifications Laravel mises en file d'attente restent bloquées sur AWS SQS

J'ai un travailleur sur AWS qui gère les notifications Laravel en file d'attente. Certaines notifications sont envoyées, mais d'autres restent bloquées dans la file d'attente et je ne sais pas pourquoi.

J'ai regardé les journaux dans Beanstalk et j'ai vu trois types d'erreur différents:

2020/11/03 09:22:34 [emerg] 10932#0: *30 malloc(4096) failed (12: Cannot allocate memory) while reading upstream, client: 127.0.0.1, server: , request: "POST /worker/queue HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/www.sock:", host: "localhost"

Je vois également un problème de mémoire insuffisante sur Bugsnag, mais sans trace de pile.

Une autre erreur est celle-ci:

2020/11/02 14:50:07 [error] 10241#0: *2623 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: , request: "POST /worker/queue HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/www.sock", host: "localhost"

Et c'est le dernier:

2020/11/02 15:00:24 [error] 10241#0: *2698 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 127.0.0.1, server: , request: "POST /worker/queue HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/www.sock:", host: "localhost"

Je ne comprends pas vraiment ce que je peux faire pour résoudre ces erreurs. Il s'agit simplement d'une configuration de base Laravel / EBS / SQS, et la seule chose que la file d'attente doit faire est de gérer les notifications. Parfois, quelques dizaines à la fois. J'utilise un t2.micro et je suppose que cela suffit pour envoyer quelques e-mails? J'ai augmenté l'environnement à un t2.large mais en vain.

Je remarque que les messages finissent dans la file d'attente, puis obtiennent le statut «Messages en vol», mais se heurtent ensuite à toutes sortes de problèmes du côté de Laravel. Mais je n'ai pas d'erreurs utiles sur lesquelles travailler.

Tout le code d'implémentation semble aller bien, car les premières notifications sortent comme prévu et si je ne fais pas du tout la file d'attente, toutes les notifications sont envoyées immédiatement.

Les notifications en file d'attente génèrent finalement deux exceptions différentes: MaxAttemptsExceededException et une Out of Memory FatalError , mais aucune ne m'amène au problème sous-jacent réel.

Où dois-je chercher plus loin pour déboguer?


MISE À JOUR

Voir ma réponse pour le problème et la solution. La transaction de base de données n'était pas terminée avant que le travailleur ait tenté d'envoyer une notification pour l'objet qui devait encore être créé.


4 commentaires

Qu'avez-vous dans /var/log/fpm-php.www.log?


L'avez-vous configuré avec le superviseur? laravel.com/docs/8.x/queues#supervisor-configuration


@mirza Je n'ai pas ce fichier journal, mais j'ai /var/log/php-fpm/error.log si c'est ce que vous vouliez dire. Et là, je vois que les cycles de fpm is running, pid 30428 -> ready to handle connections -> systemd monitor interval set to 10000ms -> Terminating ... -> exiting, bye-bye!


@jeremykenedy Je ne pense pas que Supervisor soit configuré, c'est donc quelque chose que je peux examiner.


5 Réponses :


3
votes

Quelle est la limite de mémoire actuelle attribuée à PHP? Vous pouvez le déterminer en exécutant cette commande:

php -i | grep php.ini

Vous pouvez augmenter cela en exécutant quelque chose comme:

sed -i -e 's/memory_limit = [current-limit]/memory_limit = [new-limit]/g' [full-path-to-php-ini]

Remplacez simplement [current-limit] par la valeur affichée dans la première commande et [new-limit] par une nouvelle valeur raisonnable. Cela peut nécessiter des essais et des erreurs. Remplacez [full-path-to-php-ini] par le chemin complet du php.ini utilisé par le processus qui échoue. Pour le trouver, exécutez:

php -i | grep memory_limit


1 commentaires

J'ai ajouté une réponse qui explique le problème et la solution. Merci pour l'aide!



1
votes

Assurez-vous d'abord que vous avez augmenté max_execution_time et aussi memory_limit
Assurez-vous également que vous définissez l'option --timeout
Ensuite, assurez-vous de suivre les instructions pour Amazon SQS comme indiqué dans laravel doc

La seule connexion de file d'attente qui ne contient pas de valeur retry_after est Amazon SQS. SQS relancera la tâche en fonction du délai d'expiration de la visibilité par défaut qui est géré dans la console AWS.

Expirations et délais de travail


6 commentaires

Cela semble être la solution! Nous allons tester cette semaine et ensuite j'attribuerai la prime. :-)


Nous l'avons fait et cela a semblé fonctionner, mais maintenant nous rencontrons à nouveau des problèmes similaires. Nous avons augmenté la valeur de 30 à 100. Doit-il être -1? Ou quelle autre valeur raisonnable pour le temps d'exécution sur un worker?


Eh bien, cela devrait être le temps maximum pendant lequel vous pensez que votre travail prend du temps à se terminer. Amazon ne prend en charge que 12 heures au maximum


Le problème est que cela ne devrait prendre que quelques secondes. Ce que j'ai fait maintenant, c'est réduire le nombre de connexions dans la configuration de Beanstalk Worker de 10 à 1, puis les résultats s'améliorent un peu. Quand j'attends 200 mails, j'en reçois 199 et il ne manque que le premier. Des idées?


Eh bien malheureusement c'est tout ce que je sais


J'ai ajouté une réponse qui explique le problème et la solution. Merci pour l'aide!



1
votes

Si vous êtes sûr que certains des événements mis en file d'attente sont correctement reçus et traités par le worker Laravel, alors comme d'autres l'ont dit, il s'agit principalement d'un problème de mémoire PHP.

Sur beanstalk, voici ce que j'ai ajouté à mes ebextensions pour obtenir une plus grande mémoire pour PHP (c'était pour les problèmes de mémoire du compositeur):

Notez qu'il s'agit d'une instance t3.medium EC2 avec 4go, dédiée à l'API laravel uniquement.

02-environment.config
commands:
   ...

option_settings:
  ...

  - namespace: aws:elasticbeanstalk:container:php:phpini
    option_name: memory_limit
    value: 4096M
    
  - namespace: aws:ec2:instances
    option_name: InstanceTypes
    value: t3.medium

Vous pouvez donc essayer d'augmenter la limite en utilisant davantage de RAM max d'instance disponible, et déployer à nouveau afin que beanstalk reconstruise l'instance et configure PHP memory_limit .

Remarque: la configuration réelle contient d'autres fichiers de configuration et bien sûr des contenus plus tronqués.

Comme vous l'avez dit, vous envoyez juste un e-mail, donc ça devrait aller. Cela se produit-il lorsqu'il y a une rafale d'e-mails en file d'attente? Y a-t-il, à la fin, de nombreux événements dans le deadLetterQueue SQS? Si tel est le cas, cela peut être dû à une rafale d'e-mails en file d'attente. Ainsi, SQS "inondera" la route / worker pour exécuter vos travaux. Vous pouvez vérifier l'utilisation du serveur à partir de la console AWS, ou htop comme les outils CLI à surveiller, et également vérifier l'interface SQS pour voir si de nombreux travaux ayant échoué arrivent au même moment (rafale).

Edit: pour le beanstalk élastique, j'utilise dusterio / laravel-aws-worker , peut-être vous aussi car votre journal mentionne la route /worker/queue


1 commentaires

Ce n'était pas un problème de mémoire après tout. :-) J'ai ajouté une réponse qui explique le problème et la solution. Merci pour l'aide!



1
votes

Mémoire

La quantité de mémoire par défaut allouée à PHP peut souvent être assez petite. Lorsque vous utilisez EBS, vous voulez utiliser les fichiers de configuration autant que possible - chaque fois que vous devez SSH et changer des choses sur le serveur, vous allez avoir plus de problèmes lorsque vous devez redéployer. J'ai ceci ajouté à ma configuration EBS /.ebextensions/01-php-settings.config :

class LongRunningJob implements ShouldQueue
{
    use CanExtendSqsVisibilityTimeout;

    //...

    public function handle()
    {
        // some other processing, no loops involved

        // now the code that loops!
        $last_extend_at = time();
        foreach ($tasks as $task) {
            $task->doingSomething();

            // make sure the processing doesn't time out, but don't extend time too often
            if ($last_extend_at + $this->defaultBackoff - 10 > time()) {
                // "heartbeat" to extend visibility timeout
                $this->extendBackoff();
                $last_extend_at = time();
            }
        }
}

Cela a suffi lors de l'exécution d'un t3.micro pour faire tout mon traitement de notification et d'importation. Pour un traitement simple, il n'a généralement pas besoin de beaucoup plus de mémoire que la mémoire par défaut, mais cela dépend un peu de votre cas d'utilisation et de la façon dont vous avez programmé vos notifications.

Temps libre

Comme indiqué dans cette réponse , la file d'attente SQS fonctionne un peu différemment en ce qui concerne les délais d'expiration. Voici un petit trait que j'ai écrit pour aider à contourner ce problème:

<?php

namespace App\Jobs\Traits;

trait CanExtendSqsVisibilityTimeout
{
    /** NOTE: this needs to map to setting in AWS console */
    protected $defaultBackoff = 30;

    protected $backoff = 30;

    /**
     * Extend the time that the job is locked for processing
     *
     * SQS messages are managed via the default visibility timeout console setting; noted absence of retry_after config
     * @see https://laravel.com/docs/7.x/queues#job-expirations-and-timeouts
     * AWS recommends to create a "heartbeat" in the consumer process in order to extend processing time:
     * @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html#configuring-visibility-timeout
     *
     * @param int $delay  Number of seconds to extend the processing time by
     *
     * @return void
     */
    public function extendBackoff($delay = 60)
    {
        if ($this->job) {
            // VisibilityTimeout has a 12 hour (43200s) maximum and will error above that; no extensions if close to it
            if ($this->backoff + $delay > 42300) {
                return;
            }
            // add the delay
            $this->backoff += $delay;
            $sqs = $this->job->getSqs();
            $sqsJob = $this->job->getSqsJob();
            $sqs->changeMessageVisibility([
                'QueueUrl' => $this->job->getQueue(),
                'ReceiptHandle' => $sqsJob['ReceiptHandle'],
                'VisibilityTimeout' => $this->backoff,
            ]);
        }
    }
}

Ensuite, pour un travail en file d'attente qui prenait beaucoup de temps, j'ai changé un peu le code pour trouver où je pourrais insérer un «battement de cœur» sensible. Dans mon cas, j'avais une boucle:

option_settings:
  aws:elasticbeanstalk:container:php:phpini:
    memory_limit: 256M

Superviseur

Il semble que vous deviez peut-être examiner de manière un peu plus détaillée la manière dont vous exécutez vos collaborateurs.

Faire fonctionner Supervisor pour aider à redémarrer vos travailleurs est un must, je pense. Sinon, si le ou les travailleurs cessent de fonctionner, les messages mis en file d'attente finiront par être supprimés à leur expiration. C'est un peu compliqué de bien travailler avec Laravel + EBS - il n'y a pas vraiment beaucoup de bonne documentation autour de cela, c'est pourquoi ne pas avoir à le gérer est l'un des arguments de vente de Vapor!


1 commentaires

Oui, nous allons regarder un superviseur. En attendant, nous avons résolu le problème. J'ai ajouté une réponse qui explique le problème et la solution. Merci pour l'aide!



0
votes

Nous avons finalement découvert quel était le problème, et ce n'était ni la mémoire ni le temps d'exécution.

Dès le début, je trouvais étrange que la mémoire par défaut ou le temps d'exécution par défaut ne suffisent pas pour envoyer un e-mail ou deux.

Notre cas d'utilisation est le suivant: un nouvel Article est créé et les utilisateurs reçoivent une notification.

Quelques indices qui ont conduit à la solution:

  • Nous avons remarqué que nous avons généralement des problèmes avec la première notification.
  • Si nous créons 10 articles en même temps, nous manquons la première notification sur chaque article.
  • Nous avons défini les connexions HTTP Max dans le Worker sur 1. Lors de la création simultanée de 10 articles, nous avons remarqué que seul le premier article manquait la première notification.
  • Nous n'avons pas reçu de messages d'erreur utiles du Worker, nous avons donc décidé de configurer notre propre EC2 et d'exécuter manuellement la php artisan queue .

Ce que nous avons ensuite vu a tout expliqué: Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Article]

Il s'agit d'une erreur que nous n'avons jamais reçue de EBS Worker / SQS et qui a rapidement conduit à la solution:

La notification est traitée avant que l'article n'atteigne la base de données.

Nous avons ajouté un délai au travailleur et n'avons pas eu de problème depuis. Nous avons récemment ajouté une transaction de base de données au processus de création d'un article, et la création de la notification se produit dans cette transaction (mais à la toute fin). Je pense que c'est pourquoi nous n'avons pas eu ce problème avant. Nous avons décidé de laisser la création de la notification dans la transaction et de gérer simplement les notifications avec un délai. Cela signifie que nous n'avons pas besoin de faire un correctif pour résoudre ce problème.

Merci à tous ceux qui se sont joints pour aider!


0 commentaires