Mezzio swoole/openswoole – determine if we are in a worker or task worker process

I’m a huge fan of swoole/openswoole for php as It solves a lot of hard scalability problems for us that is hard to solve otherwise in php. One of these problems is connection pooling.

Why use connection pools for your redis or mysql connections? – but for speed of course! it takes relatively significant amount of time to setup a db connection just to tear it down at the end of the request and repeat for the next request and next and next… forever – which is why this is practically a requirement for large enterprise applications with significant number http of requests or database queries. You want those < 200ms response times right?

With mezzio php framework – I typically initialize connection pool in WorkerStartEvent listener.

However – with swoole/openswoole – one of the cool things is we can use “web workers” for regular http requests and “task workers” for any async tasks that don’t have to complete in the context of the http request. This is very nice as we can offload any blocking jobs to be processed in async way!!! This is cool and all – but we only have WorkerStartEvent and not TaskWorkerStartEvent. Which means mysql/redis connection pools would have to be initialized in the same listener…

“And so what” you may say? “Why do I need to care?” The likely-hood is – you actually want to be careful about sizing your database connection pools so not create excessively large connection pool for no good reason. Or you gonna be like me reading this message from our ops:

Hey bro. I thought we were friends – and friends don’t do this to friends. What are you doing with nonprod env and why are we suddenly getting hammered with AWS alerts for NNN number of connections – this wasn’t like this yesterday.

sincerely – Scotty

Which brings me to my usecase here – my task workers don’t need to have mysql connection pools as large as web workers. In fact – my task workers could have tiny connection pools comparably, saving connections count for where they are needed . Thus when launching a connection pool in mezzio framework inside of WorkerStartEvent listener – I want to know where am I at – a web worker or a task worker?

Which was entirely unnecessarily long winded way of explaining why would I need to do this…

In short – you can get all the information from mezzio’s WorkerStartEvent and do a quick calculation:

        //Retrieve some information from the event
        $workerId  = $event->getWorkerId();
        $server    = $event->getServer();
        
        //Total number of workers enabled:
        $workerNum = $server->setting['worker_num'];

        //This - worker vs task worker
        $isTaskWorker = $workerId >= $workerNum;

This works because swoole always starts web workers first.

Full code example:

If you have enabled the worker start event listener like this (in your module’s config.php for example):

use Mezzio\Swoole\Event\WorkerStartEvent;

return [
    'mezzio-swoole' => [
        'swoole-http-server' => [
            'listeners' => [
                // Listener to start connection pools
                WorkerStartEvent::class => [
                    \App\Listener\WorkerStartListener::class,
                ],
            ],
        ],
    ],
];

Your listener factory might look like this:

<?php

declare(strict_types=1);

namespace App\Listener;

use Psr\Container\ContainerInterface;

class WorkerStartListenerFactory
{
    public function __invoke(ContainerInterface $container): WorkerStartListener
    {
        return new WorkerStartListener(
            $container->get('handle-factory'),
            $container->get('logger-app')
        );
    }
}

And actual worker start event listener may look like this:

<?php

declare(strict_types=1);

namespace App\Listener;

use Mezzio\Swoole\Event\WorkerStartEvent;
use Psr\Log\LoggerInterface;

class WorkerStartListener
{

    private $handleFactory;
    private $logger;

    public function __construct(
        $handleFactory,
        LoggerInterface $logger
    ) {
        $this->handleFactory = $handleFactory;
        $this->logger = $logger;
    }

    public function __invoke(WorkerStartEvent $event): void
    {
        //Retrieve some information from the event
        $workerId  = $event->getWorkerId();
        $server    = $event->getServer();
        
        //Total number of workers enabled:
        $workerNum = $server->setting['worker_num'];

        //Is this a task worker or a regular worker:
        $isTaskWorker = $workerId >= $workerNum;

        //Pool size
        $poolSize = $isTaskWorker ? 'micro' : 'default';

        $this->logger->notice(sprintf('WorkerStartListener: %s initPools', $isTaskWorker ? 'task-worker' : 'web-worker'));

       // Init pools with appropriate sizing:
       // $this->handleFactory->initPools($poolSize);
    }
}

Thanks for reading!

Leave a Comment