ISUCON3の予選を何とか通過し、 本戦へと参戦してきました。
大会中の方針とか考えたこととかメモ。
お題 Tw○tter–likeな画像投稿サービス ユーザをフォローできる フォローしたユーザが画像を投稿すると、タイムラインに画像が流れる 公開範囲を全員に公開・フォロワーのみに公開・自分だけに公開から選べる タイムラインはロングポーリングを使ってリアルタイム反映 JSON-APIが用意されていて、Javascriptから叩く 使用できるサーバは5台 画像を扱うお題と聞いて、会場がざわめきました。
MySQLのクエリを見てみる 開始直後、鍵を用意したり、gitのレポジトリを立てたりなんだりした後、 一回目の計測。
topコマンドで走っているプロセスを見ていると、大量のconvertが!! プロセス名とお題から考えるに、こいつら確実にImage Magickだ・・・。 CPUのほとんどが画像の変換にくわれていたので、 まずは「どこかでキャッシュする」作戦をとることに。 キャッシュするならフロントに近いほうがいいだろうということで、 フロントのnginxでキャッシュする作戦をとることにしました (アクセス制限があるimageは難しいかもしれないけど、全部publicなiconならすぐできるだろうとこのときは思ってました)。
僕はnginxがconvertを駆逐してくれると信じて、MySQLに投げているクエリを中心にPerlのコードを見てました。 役割分担はこんな感じ。
サーバの設定とか(@mackee_wさん) nginxでキャッシュする設定(@9reさん) コード読む、主にMySQLに投げてるクエリとか(@shogo82148) 毎回、ひどいクエリが仕込まれているようなイメージがあったけど、 今回はそこまでひどくない。 クエリチューニング全然効果なさそうと判断して、次の作戦を考えることにしました。
No Image Magick, use Imager! やっぱり一番のボトルネックは画像変換。 nginxでキャッシュするとはいえ軽いほうがいいよね、ということで、 外部プロセスで実行している画像変換をImagerを使ってPerlと同じプロセスでやる作戦。
Imagerに置き換え後ベンチにかけたら、若干スコアが・・・上がった・・・ような・・・? しかし、画像が変化していると怒られて、スコアは無効。 画像エラーを修正するコストと、スコアの上がり具合を見て、Image Magickのままにすることにしました。
予選でも同じように外部プロセス起動している部分をPerlのライブラリにしたけど、 その時はあっさり動いた。 あれは外部プロセス起動をやめたらスコア上がると思い込ませるための布石だったんだ・・・。 (今回の場合、プロセスの起動より画像の変換のほうが重いので、スコアが上がらないのは当たり前)
いろいろ諦めてPerl側でファイルキャッシュ Imagerはテストを通らず、nginxの設定キャッシュ設定も上手く動作しなかったので、 Perlでファイルキャッシュする方針に変更。 convertの結果にmvで適当な場所にコピーして保存。 これだけでスコアが5倍くらいに跳ね上がり、一気に上位に浮上! 最初からやっておくべきだった・・・。 もうちょっと早ければ特別賞もらえたかもしれないのに。
rsync! rsync! ファイルキャッシュの作業をやっている間に、@mackee_wさんがnfsの設定をやってくれたので、 アップロードされたファイルやキャッシュファイルの保存先をnfsに変更。
あとは物量作戦でいくしかないだろうということで、rsyncで他のサーバにコピーして調整を繰り返してた。 (並行してnginxのキャッシュ設定にも再チャレンジしてたけど、nginx力が足りなかった)
最終結果 テストFAILした!! No Score!!
なんかこんなの前もあった!
反省点 画像変換をGETでやってたけど、POSTでやったほうがよかったかも nginxについて勉強しよう nfsについて勉強しよう
気づかぬ間に第10回6さいカンファレンスが開催されていました。
第10回6さいカンファレンス「C言語のポインタ復習」 くいなちゃんSNS上で行われ、ログも残っているのでそちらを参照。 「6さいカンファレンス」のカテゴリから辿れるように記事にしておきます。
こんばんは、最近シングルトン恐怖症になっているいっちーです。 Redis::Namespaceと Redis::Keyをリリースしました。
Redis::Namespace 「Redis::NamespaceのPerl版書いた」 で紹介したモジュールをCPANizeしました。 コマンドのキー名に当たる部分に、自動にプレフィックスをつけてくれる賢い奴です。
use Redis; use Redis::Namespace; my $redis = Redis->new; my $ns = Redis::Namespace->new(redis => $redis, namespace => 'fugu'); $ns->set('foo', 'bar'); # $redis->set('fugu:foo', 'bar'); my $foo = $ns->get('foo'); # my $foo = $redis->get('fugu:foo'); RedisにはKey-Value Storeなんてかっこいい名前が付いているけど、 結局はシステム全体で使えるグローバル変数なわけです。 グローバル変数は駆逐するべきです。 いちいちプレフィックスつけて名前の衝突を回避するなんて人間のやることとは思えません。
せめてモジュールローカルとか、クラスローカルとかある程度スコープを制限したいですよね。 Redis::Namespaceを使えば簡単に実現できます。
Redis::Key Redis::Key は Redisのキーの簡単なラッパークラスです。 毎回毎回「接続先のRedisサーバ」と「キーの名前」を指定するのは面倒です。 この2つをセットにして、一つのオブジェクトとして扱うことができます。
use Redis; use Redis::Key; my $redis = Redis->new; my $key = Redis::Key->new(redis => $redis, key => 'hoge'); $key->set('fugu'); # $redis->set('hoge', 'fuga'); $key->get; # $redis->get('hoge'); 普通に使っている限りは他のキーにアクセスすることができなくなるので、 Redis::Keyのオブジェクトを他のクラスに渡す、とかしても安心です。
Redis::Fastをcpanizeしました!
さらに!早速不具合が見つかったので0.01から0.02にアップデートしました!
CPANに上げてから24時間も経たないうちにpull requestがやってきてCPAN怖いところです。
最初のバージョンである0.01ではタイムアウト処理をちゃんと書いていなかったので、 タイムアウト時に無限ループに陥る不具合がありました。 LinuxとMacとでコネクションを張るのに失敗したときの挙動が違うらしく、 Linuxでは問題なくテストが通るのに、Mac上でのテストでは再現するという面倒バグでした。 さらに面倒なことにRedisの起動のタイミングによって、 Macでもテストが通ったり通らなかったりするという・・・。
主に開発はLinux上でやって、Linux上でしかテスト動かしてなかったので全く気がついていませんでした。 CPANデビューのモジュールがネットワーク関連でXSで少しハードルを上げ過ぎた感じがします。 環境依存な部分が多くてつらいです。
pull requestを送ってくださったsyohexさん、実際にインストールを試みてテストが通らないことを教えてくださったみなさん、ありがとうございました。 アップデートした0.02では、タイムアウト時の処理を少し書きなおして、pull requestも取り込みました。 Mac上でも問題なくテストが通ってインストールできるはずです(きっとね)。
こんにちは、いつの間にかチームぽわわ2のメンバーになっていたいっちーです。
@9reさんと @mackee_wさんとでISUCON3の予選に参加してきました。 主にアプリの書き換えを担当していたので、やったことを残しておきます。 チーム全体の方針とか役割分担とかはまこぴー先生の#isucon 予選でとりあえず10位だったを参照。
お題 gistみたいなWebアプリ。 社内ISUCONのときと似たようなお題ですね。 違いは…
スターは無い Recent Postsのサイドバーが無い代わりに、ページングしてたどっていけるページがある privateな投稿ができる Markdown形式で投稿できて、表示はHTMLでレンダリングされる 詳しくは、れもんさんの#isucon 2013年予選問題の解説などを参照。
やったこと 一言で言えば、Redisにキャッシュするようにしました。
RecentをRedisのリストで管理 Recentの表示で日付順ソートしているのが重たそうだったので、 公開メモのソート結果をあらかじめRedisのリストに入れておく作戦。
RedisのSORTコマンドが高機能で面白いなーって思ってたので使ってみました。 リストにはメモのIDだけ入れておいて、メモの実体は別のキーを参照する、なんてことができます。 このコマンド、SORTって名前なのに「ソートしない」ってオプションあるところがいいですよね!
MySQLがボトルネックになっているのはこれで解消できました。
bin/markdownを使わない&レンダリング結果をキャッシュ Markdownのレンダリングを外部コマンド叩いてやっていたので、 Text::Markdown::Discountを使ってレンダリングするように変更。 qx{hoge}って記法はじめて見ました。Perlってやつはいろんな書き方があってよくわからないです。
Markdownの文法って亜種が結構あるので、レンダラをかえるのはちょっと怖かったんですが、全く問題なし。 スコアも3000くらい上がってかなり効果がありました。
さらにレンダリング結果をRedisに入れてキャッシュで+1000くらい。
Recentのレンダリング結果をキャッシュ RecentをRedisでさばくようにしたけど、そもそも100要素もあるHTMLのレンダリングそうとう重いはず。 と、いうわけでここもRedisにキャッシュするようにしました。 公開メモが投稿されたらRecent/:pageのキャッシュを全部削除。 Postのたびにキャッシュクリアされるのであんまり効果ないかなーと思っていたけど、わりと効果あったみたい? (正確なスコアよく見てなかった)
Redis::Fast!! 残り時間も少なくなり時間内にできることも限られれきたので、最後の最後でRedis::Fastを投入。 これで+1000くらい上がったらしい。(正確なスコアよく見てなかった)
s/Redis/Redis::Fast/ するだけの簡単なお仕事の予定が、githubからのインストールに一番手間取った。 cpanfileにgitのレポジトリを書くと(非公式だけど)インストールできるよ!ってどこかで見た気がするけどなかなかうまく行かず、 自分でgit cloneしてそのディレクトリを指定してインストール(したってまこぴー先生が言ってた)。 (hiredis.hが無い!って叫んでいたから、cartonがsubmoduleをうまく処理できていなかったと予想。 非公式の機能に頼るの良くないね。)
できなかったこと my.cnf?なにそれ美味しいの? SQLクエリをいじる余裕がなかった Newer/Olderのクエリが残念なのはわかってたけど、結局いじってない Nginxでキャッシュしたい 必要なモジュールは事前にCPANにあげておこう。 まとめ 結果は13192.1点で10位でした。 特に問題がなければこのまま予選突破できるはず・・・!
ところで、魔王軍が学生枠を制圧していて恐ろしいですね。 てか、僕らのチームとの差、500点程度しか無いじゃないですか。怖!!! これ以上の侵攻はなんとしてでも食い止めなければ。
hiredisをPerlから扱うためのライブラリとして Redis::hiredisってのがあるけど、 なんだか微妙だって聞いたので自分でPerlのhiredisバインディング書いてみたよ。
https://github.com/shogo82148/Redis-Fast (READMEからRedis.pmをそのまま持ってきたことがまるわかりですね。なんとかしよう。)
使い方 Redis.pmと全く同じインターフェースなので、 そのまま置換できる、はず。
use Redis::Fast; my $redis = Redis::Fast->new; ### synchronize mode $redis->set('hoge', 'piyo'); print $redis->get('hoge'); # piyo ### asynchronize mode $redis->get('hoge', sub { my ($result, $error) = @_; print $result; # piyo }); $redis->wait_all_responses; ### pubsub $redis->publish('fugu', 'fuga'); $redis->subscribe('fugu', sub { my ($message, $topic, $subscribed_topic) = @_; }); my $timeout = 10; $redis->wait_for_messages($timeout) while 1; 以前作った、Redis::Namespaceにもそのまま使えます。
use Redis::Fast; use Redis::Namespace; my $redis = Redis::Fast->new; my $ns = Redis::Namespace(redis => $redis, namespace => 'fugu'); $ns->set('foo', 'bar'); # $redis->set('fugu:foo', 'bar'); my $foo = $ns->get('foo'); # my $foo = $redis->get('fugu:foo'); ベンチマーク Redis.
前回のポストにつづいてYAPC二日目。 聞いたトークの内容を簡単にメモ。
Perl で書く結合テスト 前半はSWET(Software Engineer in Test), TE(Test Engineer)といった業種の話。 後半はテスト手法の分類(誰がする?テストの対象は?方法は?目的は?)について。
スライドはこちら→[Perlで書く結合テスト(]http://ikasama.hateblo.jp/entry/2013/09/22/234521)
これからのPerlプロダクトのかたち 世界一高速な処理系を目指して開発中のgperlと、 その過程でできたツールの紹介。 PerlをLLVMにコンパイルすることがで、高速動作するらしい。 恐ろしい・・・。
Perlは文脈によってトークンの意味が変わってしまうから、トークナイザーを作るのに苦労したとのこと。 (例えば、hoge * fuga とあったときに、*が掛け算なのかブロブなのかわからない) コンパイルの高速化のために文法を工夫しているKuinを見習って欲しいですね。
Emacs実践入門 Perl編 typester先生によるEmacs入門。 PerlCompletion とか helm とか便利そう。 あんまりEmacsカスタマイズできていないので、今度いろいろ入れて遊んでみよう。
Perlでレコメンデーション 登壇者はJubatusのPerlモジュールを書いたりしているらしい。 Jubatus に触ってみようと考え始めてからどれだけの月日が経っただろう・・・ そのうち触ってみます。そのうち。
中規模チャットサービスの運用事例 handlename先生のLobi運用のお話。 今日もcronのメールが迷惑メールフィルタによって闇に葬りさられる悲しいことがあったので、 cronの結果をIRCに飛ばすのとか参考にして何とかしたい。
PhantomJSによる多岐にわたる広告枠の確実な表示テスト 最近の広告はJavascriptを使った遅延読み込みをするので、 ちゃんと表示されるかを静的に判断することができない。 そこで PhantomJS を使ってテストするお話。
フルテストも50msで終わらせたい 〜 FreakOutの取り組み 〜 さすがにフルテストは50msで終わりません。 Ukigumoを使って複数台のサーバでテストを分散実行する取り組みを紹介。
スライド→http://yapcasia.org/2013/talk/show/767463b0-d8fd-11e2-971a-72936aeab6a4
LT 前日にアイデアだけLTで紹介したHTTP::Body::Builderが、別の人の手によって実現されていたのには驚いた。 YAPC恐ろしいところだ・・・。
HUB 懇親会参加しない組だったので、 @sasaplus1 さん, @kazuph さん, @aokcub とHUBで飲み会。 なぜ学内にHUBがあるんだ・・・?
NDS勢やNiigata.pm勢、あと何故かスタッフになっていた @jewel_x12 とも会えて楽しかったです!
YAPCの一日目に行ってきたよ。
いまどきのカジュアルなデータベース関連開発 Songmu先生のセッション。
DBIx::Schema::DSL とか GitDDL::Migrator とかの説明や、 DBのスキーマ設計、Redisの紹介なんかがありました。 自分もMySQLやRedisを触る機会が増えて、DB周りでつらい思いをしたことが何度かあるので (外部キー制約でデッドロック起こしたり、無駄なインデックスを必死に削除したり・・・) 大いに参考に参考にさせていただきます。
スライドはこちらから→いまどきのカジュアルなデータベース関連開発
学術分野におけるPerlの活用例 Perlを使ったアンケートの結果と、PerCUDAの紹介。 GPGPUをPerlのコードで実現しようとのお話。
大規模Perl初心者研修を支える技術 :DeNAさんが行った研修の紹介です。 顔覚えられない、 研修生の状況把握が大変、 信頼関係を作るのが大変 といった問題をどうやって解決したかについてのお話がありました。
トークの中で紹介された本何冊か持っているけど、全然読んでない・・・。 というか研修生みんなこれ読んだんですか。
スライドはこちらから→大規模Perl初心者研修を支える技術
mod_perlの展望とApacheの超絶技巧 最近僕の周辺ではあまり Apache の話題を聞かなくなってしまいましたね。 しかし、その知名度の高さからか、他のオープンソースのプロダクトはダメでも、 Apache はOKという案件があるらしい。 「Apache使いました!」っていうために、mod_perl で代替品を作ろう、というお話。 おそろしい・・・。
スライドはこちらから→mod_perlの展望とApacheの超絶技巧
0から学んだポストモダンPerl ルーティングとかORMはWAFにはいらない。 blessで十分!これぞ、ポスト・モダンPerl!とのことでした。
僕もフルスタックのフレームワークより、 各機能が別になっているほうが好きですね。 (でもblessよりはクラスを扱うためのライブラリ使ったほうがよいと思う) まあ、あんまり大規模なWebアプリ作ったこと無いので、 実際に作ってみると意見が変わるかもしれませんが。
スライドはこちらから→0から学んだポストモダンPerl
Dist::Zilla 英語のトークに紛れ込んでしまい、正直良くわからなかった。 英語能力全く向上していない。
モジュールを作成、テスト、アップロード等の管理をするためのプログラムらしい。 Redis::Namespace でつらい思いをしたので、 次モジュールを作りたくなったら試してみよう。
perl な web application のためのテスト情報 スライドの順番が正しいか、今使っているのは本当にマイクなのかのテストが必要ですね! 335さん自らテストの必要性を教えてくれました。 「なぜテストが必要か」言葉では語らず行動で示す335さんかっこいい。
Test::Deep は Redis::Namespace のテストでも一部使っていますが、これ便利ですね。 Test::More の is_deeply はちょっと不便だと思っていたので、今後も使っていこうと思います。
Redis のキーにプリフィックスつけるの面倒だなー自動的につけてくれないかなーと思い、 調べてみると Ruby に Redis-Namespace というものがあるらしい。 だけども、Perl では探しても見つからなかったので書いてみた。
レポジトリはこちら→Redis::Namespace
使い方 インターフェースは Perl Redis と一緒。 コマンドのキー名に当たる部分に、自動的にプレフィックスをつけてくれる。
use Redis; use Redis::Namespace; my $redis = Redis->new; my $ns = Redis::Namespace(redis => $redis, namespace => 'fugu'); $ns->set('foo', 'bar'); # $redis->set('fugu:foo', 'bar'); my $foo = $ns->get('foo'); # my $foo = $redis->get('fugu:foo'); 大体のコマンドには対応したつもり。 別のプレフィックスがついたキーには基本的にアクセスできなくなるので、 キー名の管理が少し楽になると思います。
でも、flushdb とか flushall すると全部消えるので気をつけてね!
最近Redis を使ったコードを書くようになったのですが、 キー名を毎回指定するのがだるいです。 Ruby には redis-objects というのがあって、 Redisのキーをオブジェクトとして扱うことができるようです。 きっと、Perl にも似たようなのあるだろ、って思って調べてみました。
ほしいもの 低レベルなRedisのライブラリはたいていメソッドとRedisのコマンドが一対一対応していて、 次のようなコードになると思います。
$redis->set('key-name', 'piyopiyo'); $redis->get('key_name'); でも、Redisに何か操作をしたいわけじゃなくて、 Redisのキーに対して操作をしたいので、 次のように書けるべきだと思うんです。
my $key = key($redis, 'key-name'); $key->set('piyopiyo'); $key->get(); Redis::Hash, Redis::List Redis::Hashと Redis::Listは Perlのハッシュや配列と同じ操作で Redis にアクセスできるようにするライブラリ。
use utf8; use warnings; use strict; use 5.014; use Redis::Hash; tie my %my_hash, 'Redis::Hash', 'hash_prefix', (server => 'localhost:6379'); # set hash_prefix:hogehoge piyopiyo # set hash_prefix:fugafuga fugufugu $my_hash{hogehoge} = 'piyopiyo'; $my_hash{fugafuga} = 'fugufugu'; # get hash_prefix:hogehoge piyopiyo say $my_hash{hogehoge}; # piyopiyo # keys hash_prefix:* say join ',', keys %my_hash; #fugafuga,hogehoge # keys hash_prefix:* # get hash_prefix:fugafuga # get hash_prefix:hogehoge say join ',', values %my_hash; #fugufugu,piyopiyo # del hash_prefix:hogehoge delete $my_hash{hogehoge}; tie とかよくわかない。 Perl の黒魔術を見た気がしました。