読者です 読者をやめる 読者になる 読者になる

PhpStorm 2016.3 の例外インスペクションどうしたらいい?

こんな記事を書いたんですが、正直、PhpStorm で RuntimeException の後処理がうまく表現できなくて困っています。

qiita.com

Runtime も含んでどんな例外でもそのままスローだと @throws \Exception が必要って言われる。主語がでかすぎてやだ。 f:id:tanakahisateru:20161208192012p:plain

Java は構文的に、例外の捕捉忘れがないことが明確にわかるので、この catch には RuntimeException レベルのものしか来ないはずってことがハッキリわかる。なので同じことやっても throws Exception が必要とは言われない。

irof.hateblo.jp

いろふさん thanks

横流しするだけなのに throw と書くからそこに反応するの? ってことで、try-finally で書いてみた。そしたら今度は到達しない行があることを認識してくれない。これバグ?

f:id:tanakahisateru:20161208192006p:plain

どうも try-catch の構文があってようやく大域脱出があることを認識してくれる。けど IDE が未成熟なせいで回避するために実効力のあるコードに余計なものが入るのはいや。(doc コメントならしかたないけど)

f:id:tanakahisateru:20161208193443p:plain

究極的にはこれが正解? しんどい。

f:id:tanakahisateru:20161208192005p:plain

うーん、せっかくあるこれオフにするか、それとも... f:id:tanakahisateru:20161208201854p:plain

f:id:tanakahisateru:20161208202153p:plain

ちゃうねん、Exception 型の throw は RuntimeException | LogicException を意図した可能性があるから 単に無視して欲しい。@throws \Exception なんて書いても、PHP だと誰も幸せにならないから。

うーん... https://youtrack.jetbrains.com/issues/WI?q=throws%20%23Bug

まあ、アプリケーションレベルでは RuntimeException まわりのややこしい問題扱う時点で負けで、それは横断的な関心として分離するのが正解なんで、今のややこしい状況は逆に関心分離のチャンスかもですけどね。

f:id:tanakahisateru:20161208204837p:plain

PhpStorm 2016.3 入門動画を作りました

祝 PhpStorm 2016.3 リリース 2016/11/24、な感じのネタです。

PhpStorm 2016.3 is now released! | PhpStorm Blog

先日、株式会社ロックオン 様の社内勉強会に招待されて、PhpStorm のワザをいろいろ共有してきました。EC-CUBE3のソースを開いてインスペクションかけては「あ、ここ型検査効いてませんね...」「依存はmixed型で取ってくるんじゃなくて型を持ったフィールドに注入しないと...」とかひどいことやりました申し訳。

そのとき簡単な PHP のプログラムを通しで作るデモを雑に作っていったんですが、これ実はいい出来なんじゃないかということで、ちゃんと仕上げて公開したいなと思い...

というわけで、約40分の PhpStorm 初心者向け (PHP 初心者とは言っていない) ライブコーディングですどうぞ。


PhpStorm 2016.3 live coding demo

動画でやっていることはだいたいここに文字で書いてあります。説明は 2016.2 用ですがご容赦。

おまけで Xdebug も...


PhpStorm 2016 3 xdebug

余談: EAP で作業してたらちょうどその最中に PhpStorm 2016.3 正式リリースの通知出てちょっとーってなるつらみとうれしみ。

PHPのGCは循環参照を回収できる

PHPで親子関係のオブジェクトが相互に参照を持つ ($parent->children がありかつ $child->parent がある) ケースの話をしていたとき、循環参照の話題が出たのでふと気になって調査してみました。

結論からいうと、PHPは5.2まで、単純な参照カウンタ方式のGCのみを採用していました。5.3からは、参照カウンタ方式に加えて、循環参照を回収するGCも併用するようになりました。

PHP: 循環の収集 - Manual

PHPの変数は、基本的には参照カウンタが0になった時点でメモリを解放します。が、それだけでは、循環参照があると0までカウンタが落ち切らない変数が発生します。かといって、毎回循環参照をチェックするとパフォーマンス低下が発生します。そこで、GC監視下の変数が一定数 (コンパイル時のGC_ROOT_BUFFER_MAX_ENTRIES定数、通常は1万) を超えたときに初めて、循環参照を片付け始めるという戦略を取ります。

この循環参照用のガベージコレクタを使うかどうかは、 php.ini または gc_enable() / gc_disable() で切り替えられます。普通は有効にすべきですが、試しに無効にしてみて、循環参照がどうなるかを確認してみました。

CLIで実行時に php.ini の設定を一部変更して比較。この結果をグラフにしてみました。

f:id:tanakahisateru:20160628150429p:plain

循環参照GCを無効にすると、いちど確保されたメモリがいつまで経っても解放されないのがわかります。有効だと、だいたい4.5MBほど使ったところで循環参照がGCされて、650KBまで落ちています。

長時間かかるバッチ処理などで、複雑なOOPをすると参照カウンタが落ちないんじゃないかと心配する必要があったのは5.2までの話でした。今はもう忘れていいですよ、という確認でした。

いろいろあって Elastic Cloud がオススメな件

MySQLのインデックスの代わりにElasticsearchを使おうと思い立っていろいろやってみた結果、Elastic社のホスティングけっこうオススメなんじゃないかってなった話です。これです:

www.elastic.co

経緯としては、AWSにのっけたサービス、とりあえずMySQLとRedisだけでやってきた仕組みが、そろそろノーキャッシュ新規クエリ単発で1秒以上かかる場合が出てきたというのがあります。

アプリケーションで決まったパターンの問い合わせだけやってるぶんには、問い合わせのパターン数だけ複合インデックを作ればいいし、負荷分散したければリードレプリカが簡単、ということでほとんどの場合MySQLでいいのですが...

  • MySQLは個別のインデックス勝手に組み合わせてくれない、全パターン定義しないといけない
  • 管理者が使う検索機能のよっては、想定したインデックスにうまくヒットしない条件になる
  • どうしてもORが必要でほぼフルスキャンになることがある
  • B-Treeの末端が5件だったり10万件だったりとカーディナリティの偏りがひどい
  • 日本語ではMySQLのフルテキストがつらい

などあって、状況に応じてMySQLのキメキメインデックスとElasticsearchを使い分けたらどうかと。

で、手元で検証したかぎりでは、これがものすごくうまくいきました。HTTPレスポンスの段階で10倍ぐらい速くなりました。日本語についても、形態素解析がんばらなくても、固定長のN-Gramでバラしてmatch_phraseで当てれば、SQLでいうLIKEと同じ結果を得られました。

あとは実プロダクトに使うだけで幸せになれるぜと、同じAWSがやっている Elasticsearch Service でやってみることに。ちょっと認証まわりでひと苦労ありましたが、どうにか接続はできました。

tanakahisateru.hatenablog.jp

プラグインとかはどうするんだろう、と思いつつも、まあ目的がB-Tree一本勝負の代替だし追加機能とかなくていいや、これでもう勝ったなと思うじゃないですか。思ったんですよ。けど現実は厳しいんですよ。

こんなんじゃ1リクエストで2回問い合わせたらもうMySQLフルスキャンと同じ...

@johtani さんに助けてもらいつつ...

Elasticsearchインデックス作成におけるパフォーマンス考慮事項 | Elastic

EBSとインスタンスストレージで比較したり、ノード数を減らしてみたりしたものの、ほとんど結果は同じ。まあ並列書き込みはすごく安定しているのでありがたいんですが、もしかしたら何か用途が間違っているのではないかと気付きはじめます。

Elasticsearchの結果に含まれる took 値は、クラスタ内で何ミリ秒で処理が終わったかを指す値、それと実際のHTTPレスポンスの差を比較するといいみたい。もしかしてこれ、ログとかを大量に書き込んで後でまとめて解析するためのサービスなのでは...

そこで試しに、同じVPC内に同等スペックのEC2インスタンスを立てて、2ノードのクラスタを組んでみたら、あっさり10倍性能出ました。めでたしめでたし。

では終わらないですよ。

これだけ助けてもらってトライアルしないとかダメでしょ絶対。ものは試しだえーい。

f:id:tanakahisateru:20160601175239p:plain

うわ、なにこれこんな簡単にクラスタ組めるの? スペック決めてTokyo選んでシャード数決めただけ。管理ツールとかプラグインとか全部入りで最新バージョンでセキュリティ設定もブラウザで可能で、ふむふむ接続はElasticsearch標準のHTTP認証だからクライアントライブラリもそのまま使えて...

とは言ってもインターネット越しだからEC2からEC2よりは遅いよね... え、ええっ!! 同等レイテンシー?!

はい

The Official Hosted Elasticsearch & Kibana Offering on AWS

on AWS なのです。Tokyoってのはつまり、AWSのTokyoリージョンのデータセンターのこと。EC2にがんばって黒い画面でクラスタ組んだのと同じ。うわぁ...

高いスペックを要求すればかなりの価格になりますが、単純にコンテンツの全文検索を速くしたいぐらいのニーズなら、最低スペックかその次ぐらいで十分で、そのあたりはお値段もかなりリーズナブルです。一番安いプランだと、たとえば月あたり1万円かけずに100〜200万件の全文検索を20〜80msでできる(ざっと見積りでそれぐらい)感じ。スロークエリのピーク負荷逃がし用のリードレプリカを1個つ増やすより断然安いですよね。

大規模なサービスの全トラフィックを集めるとかではなく、素朴に、コンテンツ検索でMySQLのフルテキストインデックスの代わりに使うとかなら、たぶんディスクは16GBもあれば十分。ユーザーの全行動追跡みたいなのは無理でも、直近3年のショッピングカートの出し入れ傾向ぐらいならぜんぜん余裕なんじゃないでしょうか。足りなくなったら上げればいいんだし。てか、これスペック上げるって時点でもう黒字サービスなはずですよきっと。というわけで、管理の簡単さもあって、AWSでスロースタートするのにとてもやさしいサービスだなと思いました。

ちょっと微妙な点としては、メモリとディスクが別々に選べないってところです。まあ多分スケール単位が仮想マシンじゃなくて、ノードなんだろうな、ってことで難しそうですが、もしCPU/メモリ/ディスク/IOを細かく指定できたら、もっと価格に柔軟性が出そうです。

自分の場合、意味のあるデータとそれへの検索クエリに対して、あまり使わないけど入れとかないといけない眠ったデータが大量にあって、ああこれ、CPUとメモリをケチってディスクを大きくできたらなーってのがあり、今回はEC2の自作クラスタを使うことにしました。

逆にいうと、データの重要度に偏りがなくてメモリ/ディスクのバランスがマッチするなら、どう考えても Elastic Cloud でスタートするのまじでオススメ、ってことです。とくに受託でやってる人とか、黒い画面での管理じゃなくて、アカウントとブラウザのGUIで引き継げるからってことで、だいぶチャレンジしやすいんじゃないでしょうか。

まとめ

  • AWSのElasticsearch Searviceほど素のElasticsearchと違わない
  • クラスタ構築をGUIでできる
  • プラグインや管理ツールが標準で入ってる
  • 小規模(100万件程度)で使うならすごくリーズナブル
  • 画面があるので引き継ぎやすい
  • クラスタAWSの中にある
  • さすが本家の公式

毎日SQLのパフォーマンスに苦しんでいる人、Elasticsearch チャレンジしてみてはどうでしょう?

え? トライアル期間が14日しかないのに習得するのはつらい? いやいやAWSなら t2.micro を1年使えますよね。てか、技術的に学びたいだけなら、zipで落として自分のJavaで動かせばいいんですよ。クラウドにしかないサービスと違って、これ基本OSSですよ。それ済んでからの14日トライアルぜひ。

PHPのarray操作はどれが一番速いか

なになに

けど配列のフェッチと新しい配列への格納でPHPオペコード増えるし、組み込み関数のほうが速いんじゃないの?

検証してみた:

> php -d memory_limit=-1 array-spped-test.php

Ginq + Closure: 782.2070ms x1.00
Ginq + function: 785.6706ms x1.00
array_map + Closure: 99.5564ms x7.86
array_map + function: 67.2468ms x11.63
foreach with key: 52.4326ms x14.92
foreach without key: 46.9100ms x16.67

array_map と foreach でだいたい2倍差。PHPは配列を操作するオペコード増加ぶんより、関数を呼ぶほうがずっと重いようです。で、関数よりクロージャーのほうがさらに重い。

で、それらに比べて高級なライブラリは、もっと違うオーバーヘッドがかかるので10倍とか変わってくる、と。PSRでコレクションAPIインターフェースの標準化っていう考えは、ちょっとアレですね。そこは性能差が出すぎるから微妙って感じ。

いや、だからって Ginq みたいなのが役立たずかってことはなくて、遅延とかバッチとか yield な感じのイテレーションでは大事ですよ。あと、100件ページに出す程度なら誤差の範囲ですよ。こんだけ高級なのに、100万要素で1秒以内なら、十分じゃないですかね。

ところで、array_map で確実な差が出る関数とクロージャー、Ginqでほとんど違わないのはちょっと不思議です。

ま、とは言っても自分は性能差関係ないとこなら普通に

<?= Hoge::widget([
  'data' => array_map(function($e) use($nanika) {
     return $nanika->format($e->hogefuga());
  }, $rawData),
]) ?>

とかやりますけどね。ただ、foreach より array_map のほうがカッコいいって言っちゃう人の持ち点はこれでひとつ消えたな、と。

AWS Elasticsearch Service を IAM ロールで認証する

Elasticsearch Service を使おうと思ったら、VPCでセキュリティグループに入れるのができないみたいで。無制限かIPで制限がIAMロールか... HTTPのAPIなのでどうやらS3やDynamoDBみたいに使うものってことらしいですね。

Signing an Amazon Elasticsearch Service Search Request — AWS SDK for PHP documentation

AWS SDKのこれを使えというやつはPSR-7のRequestインターフェースを想定しているので、本当にIAMの認証が通るか試してみました。

Credentialsがハードコードなのはダメって言われてるので、じっさいはdotenvとか使いましょう。

それにしても、もっとこう生curlとか古いGuzzleとかの勝手なやつでもいける形にはならなかったのかな、って、SignatureV4::signRequest() の中見たら、おおぅ、そりゃたしかに RequestInterface みたいなやつじゃないとダメだわこれ、ってなりました。

世の中PSR-7以前はもうレガシー扱いか... Guzzle を 6 に上げると Codeception が 2.1 じゃないとダメでいろいろ巻き添え。最新を追い続けられるようメンテしてないプロジェクトだと、こういう新しいサービスをぱっと使うのがホント難しいですね。

PHPカンファレンス福岡2016雑感

PHPカンファレンス福岡大会 2016 に行ってきました。

phpcon.fukuoka.jp

今年はPHPカンファレンスが日本国内で年4回開催されるという、前代未聞の年となりました。

また、2016年は、PHP5.1.2という重要なマイルストーンのリリースからちょうど10年という時が経っています。

5.1.2というバージョン番号は、PHP5対応のOSSの多くが、最低サポートするバージョンはここだよという基準として、もっとも多く採用していたバージョンです。たしか、各LinuxディストリもここをPHPの最初の5系として動き出したような...

最初のPHPリリースからみると約20年、そう考えるともう、PHPの歴史のうちほぼ半分が、PHP5が主流だった歴史なわけです。PHP5への転換から10年の節目で、これだけPHPカンファレンスを開催したいと考える地域が増えたのは、本当にPHPが良くなっていった10年だったんだなって、感慨深くなりますね。

あーあー校長先生の挨拶でした。つづきましてー

いやまあ、そんなのもあってですね、今流行りの10年やってわかったことネタでちょっといちびってみました。タイムテーブルにはないけど確実に懇親会LTはある、と見込んで仕込んできたネタがこちらになります。

あくまで夜のやつですからね、お酒入ってからのやつですから... あ... ええ、いろいろすんません。

昼間のちゃんとしたやつはスライドまとめを見て下さい。

techstars.jp

聴講して印象的だったのはロリポップでした。

やっぱり、PHPを積極的に採用していったレンサバの中の人はすごいって感想です。 ロリポを使う人とロリポを使わない人とロリポを作っている人の関係って、じつはPHP文化に 相似なのかもしれないな、共用やPaaSも見逃せないぞと思いました。

フレームワークについては、福岡という土地柄で、やはりCakePHPが目立ちました。 CakeはPHP4時代との互換を重視しての2へのメジャーバージョンアップがあったぶん、今っぽいMVCになるのがもっとも遅かったフレームワークと言えます。ので、以前からPHP5.1以降に登場したフレームワークを使っている自分のような者にとって、ちょっと物足りなかったかなと感じました。まあ、このタイミングだとどうしても、2までと3との違い重要なのでしょうがないんですけどね...

次回あたりは「もう全員3にしましたよね、CakePHPも他の自由にOOPできるフレームワークと足並みが揃いました、じゃあこの次はSOLIDをふまえてパッケージ原則ですが...」「私は、今のCakePHPの方が何か赤い宝石のアレよりも大規模エンプラへの耐性が高いのではないかと感じ、これが本当なら歴史的に感慨深いことで...」みたいなぶっちぎりのをやってくれるセッションが1つぐらいあってもいいかなと思います。

注目のPSR-7について思うところは、また時を改めて。

質問タイムに質問する人がいつもの面々で、それぞれ自分の担当って感じのところで質問を出してくれていいなあって反面、初参加の人ももっと質問していけたらなとも思いました。発表慣れしていない人の練習の場が大事というのはたしかで、もしかしたらそのついでに、「質問慣れをする機会」というのはあってもいいんじゃないかとか思ったり。どうやったらいいのかってアイデアはないですが。

今回の改善点:

去年は、前泊と2日目を別のホテルにしていたら、2日目のホテルのチェックインが遅れてキャンセルされ、宿がなくなる(しかもネカフェさえ満室)という事態に陥ってしまいました。今年は金曜日からの2連泊でホテル予約し、懇親会後の安心感を確保しました。

今回の課題点:

前夜祭終わりの時点では問題なかったのに、翌朝、寝る前より激しい酔いに襲われました。単純に飲み過ぎたわけではないはず、だったら夜の時点でそれはわかってるはず...と分析してみたところ、もしかしたら、新大阪を出発するとき食べた100倍カレーが、胃粘膜にバックドアを作ったのではないかという可能性が考えられました。(心理的に)再現困難なため検証できていませんので、この不具合を再現できる方がおられましたらぜひお知らせください。

ウコン重要。

反省点:

今回は自分で行こうと思ってた店には運悪くことごとく行けず、 なりゆきで行けるラーメンに流れてしまった(それはそれで美味しかったけど)のが心残りでした。

さ、次は7月16日、関西の番です。折り返しからの後半戦、盛り上がってまいりましょう。(告知)

conference.kphpug.jp