がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

queue:work の --memory 引数が……ちょっと……

php artisan queue:work には、色々な引数があるようです。
バージョンにもよるんだろうなぁ、と思うのですが、とりあえず手元の 8.83.27 のバージョンでお話を進めます。

とりあえず引数の一覧は、vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php によれば

    protected $signature = 'queue:work
                            {connection? : The name of the queue connection to work}
                            {--name=default : The name of the worker}
                            {--queue= : The names of the queues to work}
                            {--daemon : Run the worker in daemon mode (Deprecated)}
                            {--once : Only process the next job on the queue}
                            {--stop-when-empty : Stop when the queue is empty}
                            {--delay=0 : The number of seconds to delay failed jobs (Deprecated)}
                            {--backoff=0 : The number of seconds to wait before retrying a job that encountered an uncaught exception}
                            {--max-jobs=0 : The number of jobs to process before stopping}
                            {--max-time=0 : The maximum number of seconds the worker should run}
                            {--force : Force the worker to run even in maintenance mode}
                            {--memory=128 : The memory limit in megabytes}
                            {--sleep=3 : Number of seconds to sleep when no job is available}
                            {--rest=0 : Number of seconds to rest between jobs}
                            {--timeout=60 : The number of seconds a child process can run}
                            {--tries=1 : Number of times to attempt a job before logging it failed}';

こんな感じのようです。
うんまぁ例えばsleepとかtimeoutとか、triesとかもまぁバッチ的には「あるし必要だしわかるし」って感じなのですが。

memoryが、ぶっちゃけまぁ「直感的ではないんじゃないかねぇ?」とか思うわけでございます。
「The memory limit in megabytes(メガバイト単位のメモリ制限)」とかあるわけ、なのですが、ちょっとこれ語弊があるんじゃないかねぇ? とか思ったりします。

先に答えをゲロると
・「ここで指定したメモリ以上のメモリを食うとバッチを止める」実装はある
PHPのmemory_limitを引き上げるような動作はしない
・--onceが指定されたら、メモリチェックはしない
となるようです。

1番目はまぁそりゃそうなのですが、多分引っかかりそうなのが2番目の「PHPのmemory_limitを引き上げるような動作はしない」。3番目は……そもこの引数「使われてるのかね?」とか思うんですが、使ってるとすると、もしかしたらちょろっと気になるかも。
なんか普通にこの引数を考えると「この引数まではメモリを確保してくれる」ような動作を期待したいような気がするのですが、実際には「やってないと思われます」。

コードを雑に追いかけている限り

vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php

    public function handle()
    {
        if ($this->downForMaintenance() && $this->option('once')) {
            return $this->worker->sleep($this->option('sleep'));
        }

        // We'll listen to the processed and failed events so we can write information
        // to the console as jobs are processed, which will let the developer watch
        // which jobs are coming through a queue and be informed on its progress.
        $this->listenForEvents();

        $connection = $this->argument('connection')
                        ?: $this->laravel['config']['queue.default'];

        // We need to get the right queue for the connection which is set in the queue
        // configuration file for the application. We will pull it based on the set
        // connection being run for the queue operation currently being executed.
        $queue = $this->getQueue($connection);

        return $this->runWorker(
            $connection, $queue
        );
    }

から

    protected function runWorker($connection, $queue)
    {
        return $this->worker->setName($this->option('name'))
                     ->setCache($this->cache)
                     ->{$this->option('once') ? 'runNextJob' : 'daemon'}(
            $connection, $queue, $this->gatherWorkerOptions()
        );
    }

から
vendor/laravel/framework/src/Illuminate/Queue/Worker.php

    public function daemon($connectionName, $queue, WorkerOptions $options)
    {
        if ($supportsAsyncSignals = $this->supportsAsyncSignals()) {
            $this->listenForSignals();
        }
(中略)
            // Finally, we will check to see if we have exceeded our memory limits or if
            // the queue should restart based on other indications. If so, we'll stop
            // this worker and let whatever is "monitoring" it restart the process.
            $status = $this->stopIfNecessary(
                $options, $lastRestart, $startTime, $jobsProcessed, $job
            );

            if (! is_null($status)) {
                return $this->stop($status);
            }
        }
    }

から

    protected function stopIfNecessary(WorkerOptions $options, $lastRestart, $startTime = 0, $jobsProcessed = 0, $job = null)
    {
        if ($this->shouldQuit) {
            return static::EXIT_SUCCESS;
        } elseif ($this->memoryExceeded($options->memory)) {
            return static::EXIT_MEMORY_LIMIT;
        } elseif ($this->queueShouldRestart($lastRestart)) {
            return static::EXIT_SUCCESS;
        } elseif ($options->stopWhenEmpty && is_null($job)) {
            return static::EXIT_SUCCESS;
        } elseif ($options->maxTime && hrtime(true) / 1e9 - $startTime >= $options->maxTime) {
            return static::EXIT_SUCCESS;
        } elseif ($options->maxJobs && $jobsProcessed >= $options->maxJobs) {
            return static::EXIT_SUCCESS;
        }
    }

から

    public function memoryExceeded($memoryLimit)
    {
        return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit;
    }

ってな感じぽいです。
ちな、コードほじくってて気づいたのですが、
vendor/laravel/framework/src/Illuminate/Queue/Listener.php

    public function runProcess(Process $process, $memory)
    {
        $process->run(function ($type, $line) {
            $this->handleWorkerOutput($type, $line);
        });

        // Once we have run the job we'll go check if the memory limit has been exceeded
        // for the script. If it has, we will kill this script so the process manager
        // will restart this with a clean slate of memory automatically on exiting.
        if ($this->memoryExceeded($memory)) {
            $this->stop();
        }
    }
(略)

    public function memoryExceeded($memoryLimit)
    {
        return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit;
    }

ってコードもあるんですよねぇ……まぁおいといて。

なので。
多分なんとなく「PHPががっつり落としてくる前に手前でコントロールできている間にバッチを止める」ってのを意図している一方で「メモリの上限は特に上げてくれない」っぽいんですよねぇ。
あと、細かい所で「明示的に --once の時は多分、上述のメモリチェックもしない」感じですねぇおそらく。

おそらくなんですが、意図としては「バッチがdaemonのようにグルグル動き続ける時に、PHPメモリリークとか含めて"メモリ大量消費"されるのを防ぎたい」って感じ、なんじゃないんですかねぇおそらく???
ただまぁ、だとしたらもうちょっと命名を変えたほうがいいような気がせんでもないのですが……まぁ、まぁ。

軽くググってみた限りだと、意外に記事がなかったぽいので、ちょっくら一筆。