The Integration of Laravel with Swoole (Part 4)

In the previous articles you have learnt how to accelerate your Laravel applications with Swoole. But there's another bottleneck in most real use cases. You can't avoid any I/O waiting operations in your application, eg. DB queries, network requests or other file I/Os. Your requests are still synchronous, so the maximum concurrency amount highly depends on your worker number.

Asynchronous

Therefore, we need asynchronous clients like in Node.js. In traditional PHP, all I/O clients are based on synchronous communication mode. The problem is that
the server is low concurrency, easy to congestion and low efficiency.

With asynchronous clients, the performance can be improved at the same time, and the concurrency is well solved, which greatly provide the capability for high concurrency requests.

Swoole also provides many asynchronous clients, but it's hard to integrate them with Laravel since callback functions will break the whole lifecycle of Laravel.

In the future Swoole versions, async clients will be possibly abandoned. Instead, coroutine clients are recommended by official develop team.

Coroutine

What is Coroutine? In simple words, you can program your async operations in synchronous style to avoid annoying callback function hell.

Before Swoole 4.0, coroutine features are not mature yet. For example, you can't use coroutine clients in call_user_func, array_map and many magic functions. Now in Swoole 4.0, you can use these coroutine clients in anywhere you want so that we can also use them in Laravel.

Integration of MySQL Coroutine Client

We hope we can use MySQL coroutine client with Eloquent directly without modifying our original codes. Hence, to support customized Eloquent connector, the best way is to make it compatible with PDO.

The original usage of Swoole's MySQL coroutine client looks like:

co::create(function() {  
    $client = new co\MySQL();
    $server = [
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => 'root',
        'database' => 'test',
    ];

    $ret1 = $client->connect($server);
    $stmt = $client->prepare('SELECT * FROM userinfo WHERE id=?');
    $ret2 = $stmt->execute([10]);
});

It is similar to PDO but unfortunately it's not compatible with PDO. So we need to implement two wrappers for PDO and PDO Statement.

With customized Swoole MySQL connector, we can simply make our DB queries called asynchronously like below:

DB::connection('mysql-coroutine')->select('sleep(1)');  

There's a benchmark comparison for sync and coroutine clients. We simulate the DB I/O waiting for 1 second and do the pressure test with only one worker.

wrk -t4 -c10 http://laravel-swoole.tw:1215/sync

Running 10s test @ http://laravel-swoole.tw:1215/sync  
 4 threads and 10 connections
 Thread Stats Avg Stdev Max +/- Stdev
  Latency 1.08s 0.00us 1.08s 100.00%
  Req/Sec 0.11 0.33 1.00 88.89%
 9 requests in 10.10s, 8.14KB read
 Socket errors: connect 0, read 0, write 0, timeout 8

Requests/sec: 0.89  
Transfer/sec: 825.20B  
wrk -t4 -c10 http://laravel-swoole.tw:1215/coro

Running 10s test @ http://laravel-swoole.tw:1215/coro  
 4 threads and 10 connections
 Thread Stats Avg Stdev Max +/- Stdev
  Latency 1.03s 7.91ms 1.05s 72.22%
  Req/Sec 2.17 2.84 9.00 85.71%
 72 requests in 10.10s, 65.00KB read

Requests/sec: 7.13  
Transfer/sec: 6.43KB