ISUCON4の予選に参加してきた #isucon
ISUCON4の予選にkyokomiとyasuunとチームを組んで参加してきました。
1日目に参加し、最終送信結果は15107、benchmark v2では17124と予選通過には程遠いスコアでした。
しかも、AMI審査方法の通りに実行してみたら見事にレギュレーション違反!
来年リベンジするためにも記録を残しておこうと思います。
予選当日までにやったこと
ISUCON初参加だったので、感覚をつかむために前回予選のAMIを使って3人で練習。
正直、この練習をやってなければもっとひどい結果になっただろうと思えるくらい、問題点や準備しなければならないことが山ほど出てきて泣きそうでした。
アクセスログやMySQLのスローログの分析方法を事前に調べて手順化しておき、当日に慌てないように準備することにしました。
使う言語もこの時に決定。得意言語はバラバラで、Perl2人、Go1人だったんですが、作業分担的にGoにすることに。
リポジトリはGitHubのプライベートリポジトリ、テキストでのやりとりはSlackを使いました。ISUCON用のチャンネルを作って、そこにGitHubにpushした内容などを通知させるように設定。
予選当日 午前
まずは当日のマニュアルに目を通し、起動したインスタンスのIPをSlackに貼る。
鍵は練習で使用したものを流用。.ssh/configにIPと鍵の場所を書いて簡単にログイン出来るようにした。
ログインしたらまず/home/isucon配下でgit initして remote add & push。
最初は修正は行わずに、アプリケーションの内容やミドルウェアなどの情報を調べることに。
アプリにログインしたあと何も出来なかったので、この機能が動くように修正するのか!?と勘違いしましたが、すぐにログインゲーだと気づく。
次にnginxのログとMySQLのスローログ(long_query_time=0)を出力する設定にしてベンチ初回実行。
access.logはApache::Log::Parserを使って集計、MySQLのスローログはmysqldumpslowを使って集計。
bench.shを作成。内容は、今あるnginxのログとMySQLのスローログをrenameして空にし、nginx、MySQL、supervisordの再起動して、ベンチを実行という感じ。
昼
ひと通り見たので修正方針の話し合い。
login_logテーブルで禁止IPの判断、禁止Userの判断、前回ログイン情報の取得と色々やってたので、これを分割することにする。
禁止IPカウントテーブル、禁止Userカウントテーブルを作り、それをメモリに載せることにした。役割分担は以下。
- waniji: 初期データ投入スクリプト作成
- kyokomi: メモリに載せるようにアプリ修正
- yasuun: nginxの設定周りを修正
午後
禁止IPカウントテーブルと禁止Userカウントテーブルの初期データ投入をPerlで作成。
Perlのアプリ実装を見てみたらlocal配下にDBIx::Sunnyがいたので、perl -I でそこを指定して初期データ投入スクリプトで使った。
禁止IP、禁止Userのカウント情報をメモリに持つ修正が出来てきたので計測したらスコアが12304。
更にnginxで静的ファイルをさばく設定がきて16002。
この時にcannot assign requested addressがでたけど、ローカルポートを食いつぶしてるとはわからず……。
次に、前回今回ログイン情報を持つテーブルを作ってlogin_logを廃止するとこに。
しかし、初期データ投入スクリプトが1分を超えてしまってウンウン悩んである間に時間がなくなってあえなく中断……。
ユーザーテーブルの情報をメモリに持つようにしたが、スコアが下がったので戻し。
この辺りで時間がなくなったのでworkload数を調整して、再起動して問題ないか確認をしてたらタイムアップ。
反省点
- レギュレーション違反
データをメモリ上にしか持っていなかったので、再起動後のreport内容が不一致となってOUTでした。問題外なので次はこういったことがないように気をつけたい。
- login_logにインデックスを貼らなかった
スローログを見て遅いのは分かっていたのに、どうせ無くすテーブルだからとインデックスを貼らなかったのが間違いだった。他チームの方の記事を見てみると、インデックスを追加するだけでクエリのチューニングが不要になるくらい早くなるとの事だったので、これは完全に判断ミスでした。
- ローカルポートの食いつぶしに気が付かなかった
完全に知識と経験不足でした。予選終了後にいろいろ試したところ、ポートの設定を弄らないと20000弱で頭打ちになったので、これを対処できない人は足切りされる感じだった。
まとめ
予選は惨敗だったけど、チーム全員が準備も含めて楽しんでやってたし、色々と良い経験が詰めました!
来年も開催されたら絶対リベンジしたい!
YAPC::Asia 2014に参加してきた #yapcasia
去年初めて参加したYAPC::Asia。2回目の参加です。
ちゃんと感想を書いて、今年のYAPCを終えようと思います。
印象に残ったセッション
Releasing perl
ドキュメントの充実や開発プロセスの簡略化にかなり力を入れていて、 長く続けていくにはこういった地道な努力が必要なんだと思わされました。
お待たせしました。Perl で BDD を簡単に実践する最高にクールなフレームワークができました
Test::More 2.0が開発中止になっていたとは...。Test::Kantan使ってみます!
Perl::Lint - Yet Another Perl Source Code Linter
トークンとはどういうものなのか、どういった処理でチェックを行っているかの説明が理解しやすかったです。プロジェクトごとのポリシーも作れる(作りやすい)とのことなので(複雑でなければ)、業務に活かせそうで楽しみです!
WHERE狙いのキー、ORDER BY狙いのキー
何故クエリが遅くなるのか早くなるのかをPerlのコードを用いて説明されていて、Perl使いにはありがたいです。Indexはソート済みなのでそれを狙ってクエリの高速化を行うなど、具体的なやり方を説明しているのがとても良かった。
理解する前に進んでしまったところが一部あったので、スライドを見直そうと思います!
オープンソースの開発現場 - Perl 5.20 のSubroutine Signaturesが来るまでの奮闘の軌跡
Peter Martini氏がいい男過ぎてもう......。最終的に全部書きなおされたのに、入った事自体がいい事なんだ、なんて俺は言えないだろうな......。
海外のこういった事例が聞けるのは本当に貴重でありがたいセッションでした。
半端なPHPDisでPHPerに陰で笑われないためのPerl Monger向け最新PHP事情(5.6対応)
トークが抜群に上手く、聴衆が一気に引きこまれてました...!ネタ(闇)も交えてPHPの魅力が語られていて、やってみようかなという気にさせてくれるとても素晴らしい内容でした。
ベストトーク賞1位おめでとうございます!
Perlあるある
憧れのハッカーの方々についての、普段聞けないようなことが聞ける貴重なトークでしたw 時間の都合で答えられていなかった質問の回答がとても気になります!
YAPC::Europe 2014 に行ってきました
ビール天国に行きたい!そしてOAuth認証のLTが面白いw
Google翻訳でも何とかなるというお話は、躊躇している人にとってはとても良い情報だと思いました。
Perlの静的解析入門とPerlリファクタリングツールApp::PRTのご紹介
独特なテンポのトークがとても面白かった!
しかも内容はかなり実用的で、これは実践せねばと思いました!
キーノート
「一度の人生楽しまなきゃ損!」
最高でした。
設備など
コーヒー、ジュース、お菓子、レッドブル、かき氷が無料で提供され、懇親会は無料、HUBは貸し切りで1000杯までフリードリンクという至れり尽くせりで、正直このチケット価格で大丈夫かな...と心配になります。
イベントホールでは書籍の販売やイベントトラックがあったり、とても賑やかな感じでお祭りのようでした。
会場のネットワークは快適過ぎて最高でした。
その他
懇親会で出身地が同じ方と偶然出会い、地元トークや、色々共通点がある過去の(ヤバイ)プロジェクト話で大いに盛り上がり、とても楽しい時間を過ごすことが出来ました。こういった出会いもあるYAPCは本当に素晴らしいですね。
まとめ
運営の方々、本当にお疲れ様でした!最高です!
次回は登壇する側に立ってみたいです!
Vimでヤンクした内容を複数行にペーストする
参考にさせていただいた記事
検索・置換に便利なヤンク(レジスタ)【Vimコマンド】にさん追加7/3 - YKMbPP
やりかた
Ctrl+r
を押した後"
を入力すると、入力モードやコマンドラインモードでもヤンクした内容をペースト出来るようなので、以下の手順で複数行ペーストが可能です。
Ctrl+v
で矩形選択モードにする- 範囲選択したあとに
Ctrl+I
で入力モードにする Ctrl+r
を押して"
入力でヤンクした内容をペースト- ESCで抜けて複数行に反映
文字だと分かりづらいのでGIFアニメ
HerokuにHubotをデプロイしてSlackと連携&Travis CIで自動デプロイ
やること
以上を無料枠で
前提
Hubotを作成
必要なアプリケーションをインストール
brew install node redis npm install -g hubot coffee-script
Hubotの雛形を作成 (名前はお好みで)
hubot --create foobar-hubot cd foobar-hubot
Slack用のアダプターを追加
npm install hubot-slack --save
Slack用のアダプターを使うようにProcfileを書き換え
vim Procfile
web: bin/hubot --adapter slack
GitHubにpush
git init git add . git commit -m "create" git remote add origin (リポジトリURL) git push -u origin master
Herokuにデプロイ
HerokuのCLIツールをインストール
brew install heroku
CLIツールの初期設定
heroku login
Herokuアプリの作成
heroku create --stack cedar (アプリ名)
ワーカプロセス数の設定とアドオンの追加
heroku ps:scale web=1 heroku addons:add redistogo:nano
HerokuアプリのURLを設定
heroku config:add HEROKU_URL=*****
SlackのIntegrationsでHubotを追加
Setup Instructionsに表示された設定をHerokuに追加
heroku config:add HUBOT_SLACK_TOKEN=***** heroku config:add HUBOT_SLACK_TEAM=***** heroku config:add HUBOT_SLACK_BOTNAME=(ここは任意)
HerokuにHubotをデプロイ
git push heroku master
SlackにHubotのURLを追加して完了
Travis CIを使って自動デプロイ
Travis CIのProfileでHubotのリポジトリを有効化
gem install travis
リポジトリにHeroku用の設定
travis setup heroku
.travis.ymlの先頭にnode.js用の設定を追記
vim .travis.yml
language: node_js node_js: - '0.10'
git add . git commit -m "add .travis.yml" git push origin master
HubotがデプロイされたらSlackに通知
SlackのIntegrationsでHerokuを追加
通知させるチャンネルを指定
Setup Instructionsに表示されているコマンドを実行する
heroku addons:add deployhooks:http --url=*****
参考にさせていただいた記事
Capture::Tinyを使ってSTDERRに何も出力されていないことをテストする
経緯
- 数千万行あるファイルに対して1行ごとに処理するようなバッチを書く
- utf8フラグ付きの文字列を
encode_utf8
せずに処理してしまう - 大量の
Wide character in ...
という警告がログに出力される - 気付かずに本番リリースされ、それが原因で障害発生
- 悲しみに閉ざされる
対策
あれ以来、テストでSTDERRのチェックを行うようにしている。
あと、テストデータにマルチバイト文字列を忘れずに入れる。
my ($stdout, $stderr, $exit) = capture { MyApp->new->run(@argv) }; is $stderr, '', '標準エラー出力には何も出力されていない';
Path::ClassからPath::Tinyに移行した時に書き換えた処理
バージョン
- Path::Class 0.33
- Path::Tiny 0.056
オブジェクト生成
Path::Classで生成したオブジェクトは、ファイルはPath::Class::File、ディレクトリはPath::Class::Dirとなる。Path::TinyはどちらもPath::Tinyとなる。
# Path::Class $file = file($path); $dir = dir($path); # Path::Tiny $file = path($path); $dir = path($path);
ディレクトリ配下のファイルオブジェクト生成
# Path::Class $file = $dir->file($name); # Path::Tiny $file = $dir->child($name);
ディレクトリ配下のディレクトリオブジェクト生成
# Path::Class $dir = $dir->subdir($name); # Path::Tiny $dir = $dir->child($name);
ディレクトリ削除(配下ファイルごと)
# Path::Class $dir->rmtree; # Path::Tiny $dir->remove_tree;
ファイルオープン
# Path::Class $read = $file->openr; $read = $file->open('<:encoding(cp932)') or die "Can't read $file: $!"; $read = $file->open('<:utf8') or die "Can't read $file: $!"; # Path::Tiny $read = $file->openr; $read = $file->openr(':encoding(cp932)'); $read = $file->openr_utf8;
ファイルの内容を配列に読み込み
# Path::Class @lines = $file->slurp( chomp => 1 ); # Path::Tiny @lines = $file->lines( { chomp => 1 } );
ファイルにまとめて書き込み
# Path::Class $file->spew( iomode => ':utf8', $data ); # Path::Tiny $file->spew( { binmode => ':utf8' }, $data );
親ディレクトリ名でファイルを作成
# Path::Class file($file->parent)->touch; # Path::Tiny $file->parent->touch;
ファイルをコピー
# Path::Class $file->copy_to($path); # Path::Tiny $file->copy($path);
ファイルをリネーム
# Path::Class $file->move_to($file_path); # Path::Tiny $file->move($file_path);
ファイルを指定ディレクトリに移動
Path::Tinyのmoveはrenameと同等の機能なので注意が必要。
# Path::Class $file->move_to($dir_path); # Path::Tiny use File::Copy 'move'; move $file, $dir_path;
ディレクトリ配下のファイルを処理
# Path::Class $dir->recurse( callback => sub { my $file = shift; return unless -f $file; print $file, "\n"; }); # Path::Tiny my $iterator = $dir->iterator( { recurse => 1 } ); while( my $file = $iterator->() ) { next unless $file->is_file; print $file, "\n"; }
ディレクトリ構成を保ったままn日以前のファイルを移動する
移動先のディレクトリ作成
find /from -type f -mtime +31 | xargs -I {} dirname {} | sort | uniq | sed 's!/from!/to!g' | xargs mkdir -pv
ファイルを移動
find /from -type f -mtime +31 | sed 's!\(/from\)\(.*\)!\1\2 /to\2!g' | xargs --max-args=2 mv -v
rsyncとか使ってコピーした後に削除とかやりたかったけど、n日以前のファイルコピー方法が分からなかった。
xargsが引数を複数取ることが出来るのを初めて知った。便利。