なんかかきたい

プログラミングなどの個人的なメモやサークル「ゆきいろパラソル」の情報を載せてます

真剣にRedisを使ってみようという気持ちになったのでRedisについて知っていることを書く

年末ですね。更新がおろそかになっていたので、たまにはちゃんとした話をしたいなと思い、前々から書こうと思っていたRedisの話を書いてみました。

検証に使用したRedisは3.2ですので、新しくバージョンが変わると以下の話は変わってくるかもしれませんが今のところそのようなことはしばらくなさそうです。

以下長々とRedisを本当に使えるように設定したり調べたり検証したりした内容を書いていきます。

続きを読む

MySQL InnoDB memcached pluginを使ってみようとしてやめた話

追記 MySQL 5.7.19か20あたりで取り込まれたパッチにより以下の問題は発生しなくなりました。つまりはバグでした。

InnoDB memcached pluginはMySQL 5.6から搭載された機能で、Memcacheプロトコルを使ってInnoDBにデータを読み書きできる便利機能です。 簡単なプロトコルなためSQLより高速に動作する点、InnoDBに記録できる点、MySQLレプリケーションが利用できる点など、 うまく使えば便利な仕組みですが、結論を先に書いてしまうと使えなかったという話をします。

使えなかった理由

MySQL memcached pluginを使ったInnoDBへのインターフェイスが使えなかった理由はクラッシュが多発するためです。 高速な動作、レプリケーション機能などはうまく動作しているのですが、 しばらく動作させていると get 操作の際に SIGABRT となりMySQLが動作停止します。

Program received signal SIGABRT, Aborted.
[Switching to Thread 0x7ffcf2ffd700 (LWP 722)]
0x00007ffff67e2067 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
...

MySQL5.7.14での実行結果ですが、5.6.32, 5.7.15, 8.0 でも同じように停止します。 アサーションに引っかかることによる停止なのですが、InnoDBからデータを取得する際に意図しない形でデータが取得されていて停止するようです。

MySQL 8.0 ではフォーラムで報告されていた memcached plugin 周りのいくつかの修正が取り込まれており、コードもそれなりに変更されているのですが、この問題はまだ修正されていないようです。 ネット検索では同じように停止する報告を見つけられなかったので本格的に使おうと思った人がいないのでは?という不安もあり、あまりにも簡単に停止するので検証を断念しました。

チューニングパラメータ

使えないと書いた上で紹介するのも変な話ですが、いくつかのパラメータを調整するとInnoDB memcached pluginを高速に動作させることができます。

https://dev.mysql.com/doc/refman/5.6/ja/innodb-memcached-tuning.html

単にmemcachedの代替として使用する場合、innodb_doublewrite=0innodb_flush_log_at_trx_commit=2 のように設定すると高速に動作します。

トランザクションオーバーヘッドの削減 パラメータ daemon_memcached_r_batch_sizedaemon_memcached_w_batch_size は共に大きな値を設定すると、 ロックが多発しパフォーマンスが大幅に低下してしまうので、エラーログにロックの記載がある場合はどちらも1に設定したほうがいい性能が出ることもあります。

余談

これだけ簡単に停止させられるのであれば、Amazon RDSのMemcacheプラグインも停止するのでは?と思い同じ条件で試してみましたがRDSは停止しませんでした。 うまく調整されているのか謎です。

ISUCON6予選問題のPHP実装を担当しました

こんにちはの方もはじめましての方もエントリーを読んでいただきありがとうございます。

昨日から開催されている ISUCON6 の予選にてPHP実装のコーディングを担当させていただきました t-cyrill と申します。 日頃はWeb屋さんでインフラのお仕事をやっています。

今回はいつものような適当なエントリーではなく少ししっかりとしたエントリーで担当しましたPHP実装について、いくつか簡単な補足をさせていただきます。

はじめに

ISUCON6予選参加者のみなさま、お疲れさまでした。 たくさんのチームの方にPHP実装を触っていただけたようでありがとうございます。

ISUCONということで多くの人に触れていただくコードになるだろうと思い、どのチームの方にも得意不得意の影響があまりないようになるべくよく知られたライブラリを使ったり、元からPHPがうまく動作するような環境を用意できるように準備したつもりです。

いくつか至らない点もあったと思いますが、もし困るところがあまりなかったようでしたら幸いです。

実装に使用したライブラリなど

composer.lockを見ていただればわかることですが、今回はなるべく初期のPerl実装に近くなるようにライブラリを選びました。

軽量フレームワークSlim3、HTTPクライアントの Guzzle6、テンプレートエンジンの Twig などPHPな方にはよく見るようなライブラリを元にして作っていきました。

Twigを選択したのは元になったPerlテンプレート内でテンプレート継承を使用していたためですが、今思えば特に必要ではなかったような気もします。

独自実装を使ったところなど

Perl実装から移す際にPHPで用意できなかったrandom_stringなど一部の関数は元の実装コードとそれほど違和感がないように独自に実装しました。 これらの関数は glue.php にまとまっています。

本質的なところではないのですが、 random_string の実装はかなり雑で、Perl実装のように柔軟なランダム文字列を生成することはできません。

pcreの制限について

webapp/Isuda/app.php の L49 に怪しげな // NOTE: avoid pcre limitation "regular expression is too large at offset" というコメントを見かけたPHPの方は嫌な気持ちになったかもしれません。

あまりこのケースに当てはまることがないので意外と知られていないかもしれませんが、PHPpreg系関数が内部で使っている正規表現ライブラリpcreにはデフォルトで渡せる正規表現パターン文字列の長さに制限があります。この制限を変更するには pcre ビルド時にオプションを指定する必要があります。

今回初期実装の通りに実装すると正規表現文字数が pcre の制限にかかります。Perl実装から移すときにここはかなり悩みました。

というのも、正規表現を使わない場合PHP実装がやや有利になってしまいますし、pcreを使うためには独自にpcreを用意する必要があり、初期ビルド以外のPHPを用意したチームが本質的ではないところでトラブルに遭遇するのは避けたかったのです。

結局、間を取ってハッシュの生成には preg_replace_callback を分割した正規表現で複数回呼び出して生成し、本来の置換処理はループ後の strtr にてまとめて行うように実装しました。

余談ですが、Perl実装の動作は正規表現のevalでPHPでは廃止された機能です。

Slim Containerの拡張

細かなところですが各エンドポイントで $this->dbh$this->htmlify のような呼び出しを実現するため、$app コンテナを継承した独自のSlim Containerを用意しています。

PHP7で入った無名クラスの機能をこのような形で使えたのは少し面白かったです。

stash

Perlではテンプレートへの変数渡しにstashを使っており、これを模倣するためコンテナのPimpleを直接突っ込んでおきました。

Slimの依存に含まれていたのでいい感じの入れ物として使いましたがこういう使い方は普段しないのであまりよくないような気もしています。

実装ミスについて

途中で気付かれたチームから報告がありましたが、初期のPHP実装にはベンチマーク結果に影響のない2つのわかりやすい実装ミスがあります。

一つは nginx.php.conf での mime.types ロード忘れでブラウザで閲覧した際に多くのブラウザでスタイルが適用されないなど見た目の問題が発生します。

+    include       mime.types;
+    default_type  application/octet-stream;

もう一つは POST keyword/keyword を呼び出すと必ず500エラーが発生するというもので、ミドルウェアの指定個所に間違いがあります。 また、同エンドポイントはkeywordのパース方法も間違っています。

- $keyword = $req->getParsedBody()['keyword'];
+ $keyword = $req->getAttribute('keyword');

- })->add('authenticate')->add($mw['set_name']);
+ })->add($mw['authenticate'])->add($mw['set_name']);

しかしながら、ベンチマーカーはPOST keyword/keywordが成功するのを試さないので、ベンチマークの結果に影響はありませんでした。

ベンチマーカーがPOST keyword/keywordを試していないことが公開になると他言語実装でも有利になってしまいますし、(特に1日目2日目で情報が違うことになる)

ベンチマークの結果自体には影響のないものでしたので、ルールにのっとり「ベンチマーカーの結果が全てです」という回答としました。 GoやPython実装のバグとは違いこのバグに対してパッチを提供しなかったのはこのような理由です。

2点ともすぐに気づけるようなミスなだけにPHP実装を試していただいた方には申しわけなかったです。

初期実装のサーバについて

初期実装のサーバは予選時には nginx + phpfpm の構成としました。

もともとはビルトインサーバを使用して簡単に呼び出す予定でしたが、並列処理性に問題があるためベンチの初期チェックを通過できなかったため、Ruby実装がunicornを用意したタイミングでphp-fpmを使うようにsystemd unitを置き換えました。

pmも"static"に設定されたのでPerlほどではありませんがPHPの初期スコアはそれなりに出るようになっていたと思います。

実装の最適化について

いくつかのチームから解説が出るでしょうし、PHP実装もそちらを参考にしていただければ同じように速くなりますが、初期実装でわざと遅くなりそうにした個所のみ補足します。

PHP-FPMのtcp呼び出し

初期実装で php-fpm90009001 ポートでlistenしており、nginxからプロキシする形で用意されています。 php-fpm にはunix domain socketでlistenする機能がありますので置き換えることでパフォーマンスがわずかに改善します。

最後に

本選でPHP実装を担当することはおそらくありませんが、本選でもPHP実装が上位チームに入ってくれることを期待しています。 それでは本選でお会いしましょう。

C90夏コミありがとうございました&サークル活動の休止について

C90でゆきいろパラソルのブースに来てくださったみなさま、ありがとうございました。

今回も無事「ひだまりPHPシリーズ」の新刊を出すことができました。

あと、10部ほどコピー本も用意してみました。Kinko'sさんで初めて作ってみたのですが、無事刷れてよかったです。超大変でした!中身はもちろん、今回初めて表紙絵描きました!(前日まで間に合うよ!すごいね)

ImageMagickで作った(!)巨大PDFをUSBに入れて持っていくだけで Kinko's お兄さんがいい感じにプリンタ設定してくれました。すごいね。

そんなこんなで三度目となるサークル参加となったC90も楽しい3日間でした。

今後のサークル活動についてですが、ゆきいろパラソルとしての活動は一旦おやすみとします。

ひだまりPHPシリーズが一通り書けたというのと、今後の広がりがあまり想像できなくなったというのが理由です。すみません。

ほかに書くことが見つかるまで、こちらのブログは引き続き更新していきますのでお付き合いいただければ幸いです。

MySQL 5.6の InnoDB バッファープールのプリロード機能が優秀で頭悪いクエリを投げなくても良くなった

再起動を高速化するための InnoDB バッファープールのプリロード

特に大きなバッファプールを扱う場合にMySQLを再起動すると、起動直後はメモリにDBのデータが乗っていないので大きなディスクIOが必要になるようなイカれたクエリを投げるとめちゃくちゃ遅いという現象が起きます。

MySQL5.5以前はユーザリクエストを振る前にイカれたクエリを実行しておくことでバッファプールに乗せるという手法がそれなりに有効でした。

ただ、イカれたクエリを投げる方法ではバッファプールが再起動前と同じ状態になるわけではないので遅いクエリはどうしても出てしまうし、再起動前に近い状態になるにはそれなりに長い時間が必要でした。

InnoDB バッファプールのプリロード機能は、バッファプールの復元に必要な情報をファイルから復元することでバッファプールを再起動前の状態に復元できる機能です。

バッファプール復元のための情報は終了時か任意のタイミングでファイルに書き出せます。バッファプールの中身をすべて書き出すわけではないので、それほどディスクを圧迫することはありませんし、IO負荷も少なめです。

ファイルにはテーブルスペースIDとページのIDが書き出されます。

シャットダウン時にバッファプールを書き出すには SET GLOBAL innodb_buffer_pool_dump_at_shutdown=ON; を実行しておきます。 すぐに書き出すには SET GLOBAL innodb_buffer_pool_dump_now=ON; とするだけです。

デフォルトではデータディレクトリに ib_buffer_pool という名前で保存されます。 データディレクトリに ib_buffer_pool がある状態で SET GLOBAL innodb_buffer_pool_load_now=ON; を実行すると、バッファプールの復元が始まります。

ib_buffer_pool はテーブルスペースIDとページIDを記録しているだけなので、他のサーバで生成したものを使うこともできます。

(innodb_buffer_pool_sizeが違っていても動作します。メモリから溢れるだけかな・・・いや嘘ですわかりません適当なこと書きました)

mikeda.hatenablog.com

(できそうな気はしてたので検索したらやってる方がいました。)

これが便利で 本運用しているデータベースサーバで ib_buffer_pool を書き出し -> 再起動済みのデータベースサーバにコピー -> バッファプールのロード と実行すれば、 本運用しているサーバと同じようなバッファプールの状態が復元でき、いきなりイカれたクエリが飛んできてもそれなりに高速に処理できるようになっています。

ウォームアップのためにイカれたクエリを投げなくてもよくなって平和になりました。 メモリやSSDも数年前と比べ安価で高速になり大規模データベースを扱いやすくなっていますね。大きなバッファプールを扱わないといけない機会が辛いめでたい。

Vagrant 1.7からinsecure_private_keyが置き換えられる仕様になっていることに今更ながら気づいた

ごく日常的にvagrantを使っている分には気にならないと思いますが、vagrant 1.7から vagrant ssh する際に仕様される秘密鍵vagrant up の際に置き換えられる仕様になっています。 (1.6以前はInsecure Private Keyと呼ばれる共有の秘密鍵を使っていた)

Insecure Private Keyを使ってvagrantで立てたVMSSHをする方法を取っているとvagrantのバージョンをあげたときに突然エラーになって「おや?」ってなります。 vagrant upしてvagrant sshすると問題なくsshできるので原因に気づきづらい。

ssh -i ~/.vagrant.d/insecure_private_key vagrant_vm_ip # sshできない
vagrant ssh # sshできる

置き換える秘密鍵.vagrant/machines/default/virtualbox/private_key に置かれるので、 こちらを指定するように変更するか、Vagrantfileconfig.ssh.insert_key = falseの記述を追加すれば以前の動作と同じになります。

普段使いでは問題ないですが、自動テストなどで使っているとはまりますね。というかはまりました。

C90入稿完了したので、夏コミ本出ます。お品書きはまた後日出します。よろしくお願いします。

コミックマーケット90情報です

夏コミの時期が来ましたよ!!!

今回は3日目(日曜日)西f26aのスペースをいただきました。

シンフォニーちゃんが活躍する「ひだまりPHP」シリーズの新刊と既刊の「ひだまりPHP vol.2」を用意する予定です!

あと余裕があればWebサーバの運用周りで何か書ければいいなーと考えてますので、3日目西f26aの「ゆきいろパラソル」をよろしくお願いします!