ISUCON8で予選落ちしたので、Swoole（easyswoole）で書き換えて、感想戦でベンチ走らせてみた話

CyberAgent Developers #2 Advent Calendar 2018
の21日担当の@goodooです
# PHP Conference 2018 LT ではなしたネタの解説です
さる2018/12/15にPHP Conference2018のLTで発表したのですが、まさかの時間切れで、最後十分に話すことができませんでした。
LTの５分の枠では伝えきれなかった部分もあるので、解説していきます

## 発表資料
[ISUCON8で予選落ちしたので、Swoole（easyswoole）で書き換えて、感想戦でベンチ走らせてみた話](https://speakerdeck.com/sgeengineer/isucon8deyu-xuan-luo-tisitafalsede-swoole-easyswoole-deshu-kihuan-ete-gan-xiang-zhan-debentizou-rasetemitahua)

## Swooleとは
一言でいうと、
「PHPでイベント駆動の非同期＆コルーチンベースの並行処理のライブラリ」
です
peclでインストールすることができます

`pecl install swoole`

で入ります

## easyswooleとは
一言でいうと、
「easyswooleは、Swoole Serverをベースとした常駐型メモリベースの分散PHPフレームワーク」
です

ピンとこないかもしれません、自分の最初に思った印象は、
Node.jsと同じイベントループで動くPHPのフレームワークです
コマンドラインでPHPのプログラム(easyswoole)が起動して、指定ポートをListenするWebサーバになります

easyswoole自体は、v3が出ていますが、今回はv2を扱った話になります

## ISUCONの初期実装
ISUCONのPHPの初期実装はSlim3のフレームワークで実装されています
easyswooleがパフォーマンスがでれば、来年のISUCONで、Slim3からeasyswooleに書き換えて、予選を突破する、というのも夢ではありません
そのため、戦略として、Slim3の初期実装から、簡単にeasyswooleに書き換える必要がありました

## Slim3 -> easyswoole
左から右にソースをコピーして動かすべく、最初に目をつけたのが、カスタムルーティングの機構、Route.phpです

実際に記述例を紹介すると

Slim3

```php:app.php
$app->get('/initialize', function (Request $request, Response $response): Response {
  exec('../../db/init.sh');
  
  return $response->withStatus(204);
});
```

easyswoole

```php:Router.php
$routeCollector->get('/initialize', function (Request $request, Response $response) {
  exec('../../db/init.sh');

  $response->withStatus(204);
  $response->end();
});
```

いける。そう思った時が、俺にもありました。

### 問題(1)
easyswooleは、プロセスがずっと起動したままです。通常PHPは、mode_phpだったり、php-fpmだったりで動いており、その２つは、リクエストごとにメモリ空間が別になっています
ところが、easyswooleは、プロセスがずっと起動したままなので、リクエストを跨いで、メモリを共有することができます
通常のWebアプリケーションのようにSessionをつくって、ユーザごとにSession情報を元に、処理をわける、といきたいところだったのですが、Router.phpは、本来、ルーティングを行うのにとどめるべきファイルであるため、Session情報を正しく扱うことができませんでした。
具体的には、Router.phpで生成したSessionは、リクエストするユーザ全員に同じSessionを共有してしまいます
これではアプリケーションが正しく動きません
そのため、最初の戦略であった、簡単に置き換えるは、この時点で断念しました。

easyswooleの標準のディレクトリ構成にあわせて、Controllerを作成することにより、Sessionはユーザごとに独立した状態にできました。

## 問題(2)
easyswooleに用意されているMySQLのラッパーにトランザクション(begin、commit、rollback)が実装されていませんでした
easyswooleに用意されているMySQLのラッパーを無理に使う必要はないように思うかもしれません。
しかし、easyswooleに用意されているMySQLのラッパーは、コネクションプーリングが実装されており、プロセスが常駐している利点を生かした作りで、同時リクエスト数が増えた時に効率的に捌けるように設計されていました。
これを利用しない手はないため、トランザクション部分は自分でつくりました

```php:Mysql.php
    function begin() {
        $this->client->begin();
        $this->isTransaction = true;
    }

    function commit() {
        if ($this->isTransaction) {
            $this->client->commit();
        }
        $this->isTransaction = false;
    }

    function rollback() {
        if ($this->isTransaction) {
            $this->client->rollback();
        }
        $this->isTransaction = false;
    }
```

## こいつ動くぞ
問題をいくつものりこえ、ついにeasyswooleが動きました
![easyswooleの起動.png](https://qiita-image-store.s3.amazonaws.com/0/67626/fbad6ecb-0789-e693-83a8-d54cdf4d02c2.png)

## ベンチマークの結果は如何に！
ちなみに、LTでは肝心の結果を話せませんでした
![easyswoole_pass_iscuon.png](https://qiita-image-store.s3.amazonaws.com/0/67626/9d7b813a-a40a-1a71-9085-c91aa4a5fcc8.png)

ついに、ベンチマークは「pass」に！
・・・
スコアは
・・
・・
・・
![pass_Isucon8.png](https://qiita-image-store.s3.amazonaws.com/0/67626/62c3a8a0-202e-ad05-9a46-06b97c784ec0.png)

**242**?

## 解説
初期実装のベンチ
なんて飾りです！
偉い人には、それが解らんのですよ！

 
今回は初期実装のSlim3をeasyswooleに完全移植してベンチを流したところまでしかできていません
そのため、easyswooleの特徴、非同期やコルーチンを使うところまでは至っていません
しかし、今回のISUCONの問題の性質上、非同期処理で有利になるところはほぼない、良い問題でした

easyswooleの良さを確かめるまでにはいたらなかったですが、置き換えのテクニックは学べました

来年のISUCONはeasyswooleを勝つぞ！

## 最後に参考実装
今回ベンチマークが通ったソースを置いておきます
https://github.com/shirai-suguru/isucon8-easyswoole/tree/easyswoole-modify