devphpswooleasyncconcurrencyperformance
(updated ) ~11 min (2111 words)

Swoole: PHP with superpowers

PHP Swoole Superhero image

You probably thought fast concurrency powers were only possible with NodeJs or maybe Go, even Rust? Well, think again! Here's how to do it with PHP


Look mom it's fast!

Roughly a year ago I had my first encounter with a relatively new PHP extension called Swoole.

I was looking into Nodejs performance stories and was wondering how much the difference compared to current PHP implementations would be at the time.

PHP was making quite some progress but still, using the synchronous approach of PHP-FPM including I/O blocking lead to people leaving for Nodejs or other platforms.

After reading one interesting article, I read some of the comments. Swoole was mentioned in one of them.

Besides, there was a long discussion on GitHub about the performance including comparing it to Nodejs and other PHP implementations alike.

Also, Vesko Kenashkov did a lot of different tests comparing swoole to a similar Apache+PHP setup.

So I gave it a try. And, you already guessed it, I wasn't disappointed.

My simple test app outputting a list of randomly (per request) chosen strings won against most of the competitors, even Nodejs:

use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;

include_once __DIR__ . '/vendor/autoload.php';

$server = new Server('0.0.0.0', 8080);

$server->set([
    'log_level' => 3,
    'log_file' => '/dev/stdout',
    'enable_coroutine' => true,
]);

$languageList = ['java', 'css', 'go', 'javascript', 'typescript', 'rust', 'php', 'swoole', 'html', 'sql'];

$getLang = static function ($list) {
    $active = [];

    for ($i = 0; $i < 3; $i++) {
        $max = count($list) - 1;
        $index = floor(random_int(0, $max));
        if (!\in_array($list[$index], $active, true)) {
            $active[] = $list[$index];
        }
    }

    return $active;
};

$server->on("request", function (Request $request, Response $response) use ($getLang, $languageList) {
    $response->header('Connection', 'close');
    $response->header('Content-Type', 'application/json');

    $result = [
        'message' => 'The languages of the day are',
        'list' => $getLang($languageList)
    ];

    $response->end(json_encode($result));
});

$server->start();

What are we doing here? First, we create the HTTP swoole server via PHP object instantiation. We set some configuration vars for logging and making sure the coroutines are enabled.

Having done the configuration, we define our callback function (which could be some class, of course) for returning the randomized list with a maximum of three items which in this case is just a list of some programming languages.

This function will be run with an unsorted list as input on each request. The request callback sets some headers as well and returns the result via the swoole specific Response::end method.

You can find this example code at Github as well including a docker-compose based container configuration. You can easily test the app's performance via ab.

Yes, but...

Simply put, the Swoole developers adapted to the async non-blocking I/O concept Nodejs and Go were using as well.

However, by opting for the Coroutine (if you're interested in this, also read about the related concepts of green threads and fibers) concept, it's possible to keep the way of implementing your applications mostly like you've been used to in PHP. Without having to take care of too many callbacks or other async logic, as a plus.

And, on top of it, they implemented multiprocessor support. So no wonder we could see these high concurrency results.

After starting the Master Process the swoole instance will create several reactors (defaults to numbers of CPU and can be defined via config). The reactors will be used for handling the network connections and network I/O.

Also, there are worker processes that handle the data from reactor threads as well as executing the business logic. These workers then are, where the actual application code will be run with. Read More about how it works...

That's why swoole really turned out to be a worthwhile competitor to other platforms.

Therefore I wanted to give it a try and presented my findings to the team since we were planning on creating a new microservice API that perfectly fit the high concurrency requirements swoole seemed to solve.

How to use

If you've developed Nodejs or Go based web applications and as you can see from the above example, the structure of swoole applications should be quite familiar.

You create and start a new server (i.e. a webserver) and listen for connections/requests using a callback.

Handling a WebSocket message might look like the following snippet:

$server->on('message', function(Server $server, Frame $frame) {
    $server->push($frame->fd, json_encode(["got ya message", time()]));
});



Dev things

If you want your editor to handle swoole as well, look no further. There also is a plugin for PHPStorm.

Making it even faster

There's the concept called Swoole Tables. It's an internal cache system (Key-Value Store) sharing all the cached data through all the processes and threads.

Therefore, once you have filled the table with data, each worker can access it at any request.

What else can it do

Among even more possibilities we have the concept of Tasks, WebSockets or even UDP and TCP service implementations at our hands.

Therefore you can use PHP with swoole support for almost every network-based service you can think of. What about I/O?

What about external Datasources?

Yes, this is handled as well. You can use database connections within coroutines which enables you to do more requests at once.

There are quite some clients for MySQL, Redis and Postgres reimplementing their existing APIs. The same goes for handling files.

Although, of course, the limiting factor still will be how many concurrent handles you can have and how long the data needs to process on the other end. So while you can improve certain I/O tasks here, common website logic like reading data from the database and sending it to the browser will only be slightly faster without caching.

The downsides

In my opinion, the technical implementation is very good and the extension very stable. Apart from creating exceptions or using the API in the wrong way, I haven't encountered any issues so far.

There's one little downer, though: The documentation. While it covers the most important concepts and API usages it sometimes lacks details and useful examples.

In addition to that, you have to be quite careful (although, IMHO you always should be!) when writing your code since having these long-running processes will reveal any possible memory leaks which would otherwise not surface in a traditional stateless setup. So be aware of possible memory leaks and check for them regularly.

Also, just reloading after code changes won't work because the app code gets cached in memory when starting the application. So you will have to restart the PHP process.

Using Xdebug and/or Xhprof won't work, either. Although, regarding debugging, there are alternatives like yasd.

The book

To get a deeper understanding, I bought the book, though. I hoped for a bit more extensive explanations compared to the web documentation.

I'm currently still in the progress of reading it and so far I enjoyed reading it.

This is quite a new book about swoole. It has been written by Bruce Dou, one of swoole's main developers.

He starts with describing the whys and how doing async I/O instead of the so far standard stateless PHP-FPM can be an improvement.

Then, he follows up by describing the concurrency model they chose for swoole and dives a bit into different concurrency concepts and their pros and cons.

After that, it's all about swoole, what it can be used for and how to use it.

Although the content is very good in helping to get an overview of the technologies and concepts used in developing swoole, the book, in my opinion, could have needed some proof-reading before publishing.

No offence! I'm not a native English speaker as well, so this might sound a bit harsh. But I really don't mean it that way since the book really helped me to get a deeper understanding so far.

That being said, there are a lot of syntactical errors which sometimes make sentences difficult to understand at first read.

Alternatives

While I think swoole's performance is outstanding there are quite a lot of competitors even in the PHP platform space which shouldn't be unmentioned.

To name a few: amphp, reactphp, workerman and a partly ratchet.

Also, as mentioned before, there are Go, Rust (I'm currently in the progress of learning that), Nodejs, Kotlin, Java and so on, which, depending on your preferences and problem at hand can be a good pick as well.

So, choose what you're familiar with and what seems to be best suited for your project. However, consider not just dropping PHP and giving PHP + Swoole a chance!

PHP devs don't sleep, either, and along with the language improvements in PHP 7 and higher, swoole adds a lot of features many applications are in need of, today.

Another thing, coming up with PHP 8.1 will be PHP fibers, making another step into the async world.

Who's using it?

Swoole is used by Chinese companies like Tencent (WeChat etc.).

Also, swoole is in use for the laravel octane project. For even more laravel related insights and swoole details, you might want to read this article by Mohamed Said.

Obviously, especially in the laravel community, swoole seems to be used quite often.

Frameworks

There already exist some frameworks using swoole, making creating fast APIs and websites even easier.

Here's a non-conclusive list:

- Crow

- Swoft

- EasySwoole

- Igni

Conclusion

Depending on your goals you really should consider swoole for your next application. Particularly when you'll be in need of high concurrency, swoole delivers.

So, if you're, let's say, going to write a microservice in need of handling a lot of requests at least give swoole a try.

That being said if you're just going to create another website or blog the old PHP-FPM based approach might really be enough. In fact, using just a static site generator might really be the best option for that purpose.

What do you think? Write to me on Twitter.

Attribution

The image used as the article picture was based on Designed by macrovector / Freepik.