すがブロ

sugamasaoのhatenablogだよ

ISUCON5の予選で爆散してきた

昨年に引き続きISUCONに参加しました

今年は id:koemu さんと id:ariarijp さんのチームでした。YAPCのタイミングだったかな?で id:koemu さんからISUCON一緒に出ましょう!と声をかけられたので、ホイホイとチームを結成しました。その後、YAPCの帰りに一緒に飲んでいた縁で知り合った ariarijp さんが一緒にチームを組んでくれることになってチームが結成された。

当日まで

あまり素振りとかできなかったのが悔やまれますが、まずは昨年参加した知見から、事前に作業方針のメモを共有しておいた。

例えば ssh の公開鍵を予めあつめておくとか、最初に手を付ける箇所の分類、チャットツールの準備などなど。

これは結果論だけど、チャットツールにSlackを選択していたのは、当日ISUCONの公式チャットツールidobataに障害が発生していて正常にアクセスできなかったので助かった。

当日

id:koemu 宅におじゃまして3人で顔を突き合わせて作業しました。当日やんごとなき事情で開始時間が1時間遅れたので、その間にトイレ行ったりして気持ちを高めていた。

〜13:00 ころまで

  • koemu さんが秘伝のタレやミドルウェアのバージョンアップなど
  • 自分はアプリのコードをgit化してデプロイできる環境を構築。その後、ベンチマークでDBをドロップしたりしないで毎回特定のID以降をクリーンアップしてる、というあたりを確認していた
  • ariarijp さんがER図やベンチマーク実行時のログからどのような動きをしているかを確認してくれた

この時点のベンチマークでは(ランキングが正しければ)暫定1位を取ってたのは穏やかではなかった。

f:id:seiunsky:20150928235142p:plain

この時の私の様子はこちらです

〜18:30 ころまで

ここらへんからどうもミドルウェアよりはアプリ(SQL)の問題らしいということが明確になってきて、各自問題のありそうな部分の確認、修正に着手していく。

この時、id:koemuの圧倒的コミットによって一瞬15000点くらい叩き出していて、暫定3位くらいにつける瞬間があった。

この時の私の様子はこちらです

ただ、ベンチマーク的には問題なかったけど画面上の出力結果に問題があったので、おそらく意図しない結果だろうと判断して、この結果はお蔵入りになった(この得点に自力で到達できれば予選通過できたのだけど、、、)。

また、並行してプロファイラを仕込んだりしてボトルネックの可視化を図っていった*1

sugamasao.hatenablog.com

N+1問題の解決やデータをmemcachedへ移す作業、追加のindexなどを見ていたけれど最後まで詰められず時間が来てしまった。

〜19:00

不要なログ出力のOFFなどを準備し、再起動してベンチマークが正しく動くかなどのチェックを始め、18:58くらいにギリギリ最終提出用のイメージをfixすることができた。

所感

前回のISUCONはどちらかというとミドルウェアを攻めていけた印象が強かったので、じっくりソフトウェアのチューニングを行うことにはならないだろう……などという先入観があり、プロファイラの導入など、アプリに手を入れるための施策が遅れてしまったのは大きな痛手だった(git化のついでくらいの勢いで導入すべきだった)。

あと、ローカルでアプリを動かせるような環境を作れればもうちょっと作業が効率化できたと思うのだけど*2、mysql2のビルドがうまくいかなくて諦めてしまった。ここはもうちょっと掘り下げるとか、MySQL2 0.3系を試すとか、そういう方向の手を打っていた方が良かったかもしれない。

もう一つ、今後やるならチャットツールにサーバ上のログなどの情報を手軽に通知できる仕組みを準備できると、作業時間のロスがなくて良いのかなと思った(とくに具体案はないけど、やってて手間だった)。

最後に

id:koemu さん宅におじゃまして一日作業させてくれてありがとうございました。あと、id:koemuさん、id:ariarijpさんともインフラもアプリのコードも書けて素晴らしいエンジニアで、マジ自分は空気だったのでもうちょっと頑張っていかないと厳しいとしか言えない。。。

本当に最後に

今年も自分の課題が見えた*3ので、ISUCONは本当に刺激になる良いイベントでした(めっちゃ疲れるけど!)。運営チームのみなさん、ありがとうございました?

*1:ただ、このとき自分は大チョンボをしてしまって、うまく動かないと思って1時間くらい有効活用できなかった。標準出力にログが出ているのに気がついてなくて、WARNINGがでていたのでそれでうまくとれてないのかと勘違いしてしまった、、、

*2:とにかく掟破りの本番修正というか祈りながらデプロイするみたいになってたのはあんまり良くなかった

*3:去年と変わらず筋力が足りない!!!!!!!!!!!!!1

Webエンジニアの教科書をいただきました

@sasata299さんからWebエンジニアの教科書をいただきました!ありがとうございます><

Webエンジニアの教科書

Webエンジニアの教科書

目次

  • CHAPTER-01 Webエンジニアについて
  • CHAPTER-02 Ruby on Railsでの開発
  • CHAPTER-03 PHPでの開発
  • CHAPTER-04 NoSQLデータベース
  • CHAPTER-05 フロントエンドの実装
  • CHAPTER-06 ログについて
  • CHAPTER-07 データの可視化について
  • CHAPTER-08 環境構築の自動化
  • CHAPTER-09 便利な外部サービス

CHAPTERだけ見るとちょっと荒いのでよくわからないかもしれませんが、もう少し詳しい章レベルだと、「 CHAPTER-04 NoSQLデータベース」では「Redis」や「MongoDB」について説明されています。

小学生並みの感想

この本の冒頭の対象読者でも書いてありますが、新卒さんがWebエンジニアになるぞ!!という本ではなく、少し経験を積んだ人がWebエンジニアとして幅を広げる際にとっかかりをつかむための本かなと思いました。

例えば、僕はMongoDBやTypeScriptなどは触ったことがなかったのですが「とりあえず触ってみる」という時はこの本を読みながら手を動かしてみれば良さそうだなと考えています。

もちろん、紹介されているそれぞれの技術に対して詳しく知りたい場合は専用の書籍を購入するのが良いと思うのですが、幅広く紹介されているので懐に忍ばせておくと、ある日突然MongoDBなどを触りたくなった時に便利そうですね(他にも、FluentdやServerspec等も取り上げられています)。

詳細な目次を見て、複数の項目で知らないけど触っておきたいなーというものがあれば購入してそんはないと思います!!!1

Webエンジニアの教科書

Webエンジニアの教科書

合わせて買いたい

昨年はこんな本を共著で書いてたのでこちらもよろしくお願いします

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

MySQLのinnodb_thread_concurrencyとかinnodb_commit_concurrencyを変更する必要があるのか問題

MySQLのパラメータむずかしい

項目の説明はわかりますが、実際のシステムに即した値にするのってむずかしいなぁ、と思いつつパラメータを眺めていたのですが、こういうことらしいです。

最近のは(5.5や5.6なら)デフォルトで大丈夫だよ〜とのこと。

日本語の適当なツイートを拾って回答くれる MySQL Community Manager さんしゅごい〜〜?

bundle gem foo -bの挙動がかわった(exeディレクトリになった)

bundle gemでgemライブラリのひな形が作成できる

その中で、-b オプションを付けると実行ファイル用のbinディレクトリが作成される。しかし、Bundler 1.8系からは bin ディレクトリは binstub用(で良いのかな)のsetupやconsoleスクリプト等(-bを付けなくても生成される)が配置され、gemライブラリの実行ファイル用として exe ディレクトリが用意されるようになる。

1.7.9 と 1.8.3での確認

1.7.9 で bundle gem プロジェクト名 -b -t した結果

➜  /tmp  tree baz
baz
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── baz.gemspec
├── bin
│   └── baz
├── lib
│   ├── baz
│   │   └── version.rb
│   └── baz.rb
└── spec
    ├── baz_spec.rb
    └── spec_helper.rb

4 directories, 10 files

1.8.3 で bundle gem プロジェクト名 -b -t した結果

➜  /tmp  tree bar
bar
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bar.gemspec
├── bin
│   ├── console
│   └── setup
├── exe
│   └── bar
├── lib
│   ├── bar
│   │   └── version.rb
│   └── bar.rb
└── spec
    ├── bar_spec.rb
    └── spec_helper.rb

5 directories, 12 files

経緯など

move gem bins to exe/ and add console and setup · ab3e217 · bundler/bundler · GitHub

あたりでやりとしていている。インパクトある割にしれっと変わってて何なのwという感じだけど、やりとりの先のブログとかも見ると、RSpecとかずいぶん前からそうしてるしみたいな事が書いてある。

rspec/rspec-core · GitHub

なるほどねー

その他

いつのバージョンから入ったか調べてないけど、bundle gemの時に、MITライセンスにするかだとか、テスト用フレームワークはどれを使用するか、等が質問されるようになっている。

これらの回答結果は設定ファイルとして(~/.bundle/configに)保存されるようになっていて、フレームワークの指定等をしなかった場合のデフォルト値として使われるようになってるっぽい。

OSXのHomebrewでインストールするのをitamaeでやるようにした

自分のマシンの環境構築

数年に一度なのでイマイチ自動化とかしてなかったのだけど、Homebrewでインストールするヤツくらい自動化しても良いかと思ったので、 itamae-kitchen/itamae · GitHub でやるようにした。

caskでのインストールとかまで自動でできるようになると結構良いかなーと思っているが、そこまでやってない。。。

このエントリを読んで得られるもの

itamae についてちょっとだけ知識を得られるかもしれません。

  • パッケージのインストールを行う方法
  • シェルスクリプトの実行をする方法
  • itamaelocal モード(実行しているマシン自身を対象とする)の実行方法

リポジトリと実際のソースコードはこんな感じ

dotfiles/app_setup at master · sugamasao/dotfiles · GitHub

主に package リソースを使っていますが、brew tap の部分は直接シェルスクリプトを実行しているため、 execute リソースを使っています。

また、Homebrewは重複してインストールしてもエラーになったりはしませんが、 only_ifnot_if を使ってインストール済みであれば実行をスキップするようにして処理時間を短縮しています。

# brew install
%w(openssl git tig imagemagick mysql zsh tmux mecab mecab-ipadic tree).each do |pkg|
  package pkg do
    action :install
    only_if "brew info #{ pkg } | grep -qi 'Not Installed'"
  end
end

## using tap repository
%w(sanemat/font peco/peco).each do |tap_name|
  execute "using tap #{ tap_name }" do
    command "brew tap #{ tap_name }"
    not_if "brew tap | grep -q '#{ tap_name }'"
  end
end

%w(ricty peco).each do |pkg|
  package pkg do
    action :install
    only_if "brew info #{ pkg } | grep -qi 'Not Installed'"
  end
end

local モードで実行するにはこのようにする。

$ bundle exec itamae local recipe.rb

まーこの程度シェルスクリプトでも良いじゃん?という気もするんだけど、今どきはOSXでもRuby 2.0系がデフォルトで入るようになってるし、臆さずRuby使ってけば良いのではという気持ちになっている。あと重要なのはitamaeを気に入ってるので、使っていきたいという気持ちw

OSXでもstraceしたい?よろしい、ならばdtrussだ

strace便利ですよね。最近もnginxがどのファイル開いてるのか調べるのに使いました。ただ、OSXだとそれに準ずるコマンドってないのかなーと勝手に諦めていたのですが、ありましたね。

dtruss

straceと同じように sudo dtruss -p プロセスIDシステムコールを確認できる。

例えば、nginxで確認してみよう。

$  ps -ef | grep nginx
  501 31223     1   0  6:22PM ??         0:00.00 nginx: master process nginx
  501 31224 31223   0  6:22PM ??         0:00.00 nginx: worker process
  501 31935 31725   0  8:07PM ttys005    0:00.00 grep nginx

ワーカーを見れば良いので、 31224 にアタッチしてみよう。

$ sudo dtruss -p 31224

おもむろにWebページにアクセスしてみる。

SYSCALL(args)            = return
kevent(0x8, 0x7F91DD005C00, 0x1)                 = 1 0
recvfrom(0x3, 0x7F91DD005400, 0x400)             = 469 0
stat64("/usr/local/Cellar/nginx/1.6.2/html/index.html\0", 0x7FFF5DEEF148, 0x400)                 = 0 0
open("/usr/local/Cellar/nginx/1.6.2/html/index.html\0", 0x4, 0x0)                = 10 0
fstat64(0xA, 0x7FFF5DEEEF08, 0x0)                = 0 0
writev(0x3, 0x7FFF5DEEE9D0, 0x1)                 = 179 0
write(0x4, "127.0.0.1 - - [31/Dec/2014:20:08:35 +0900] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36\"\n\0", 0xC1)              = 193 0
close(0xA)               = 0 0
access("/etc/localtime\0", 0x4, 0x0)             = 0 0
open_nocancel("/etc/localtime\0", 0x0, 0x0)              = 9 0
fstat64(0x9, 0x7FFF5DEEBFC0, 0x0)                = 0 0
read_nocancel(0x9, "TZif\0", 0x2A64)             = 126 0
close_nocancel(0x9)              = 0 0
accept(0x6, 0x7FFF5DEEF430, 0x7FFF5DEEF42C)              = 9 0

標準エラーを標準出力にリダイレクトすればgrepもできるので、ここらへんもstraceと同じですね。

$  sudo dtruss -p 31224 2>&1 | grep open
open_nocancel("/etc/localtime\0", 0x0, 0x0)              = 10 0
open("/usr/local/Cellar/nginx/1.6.2/html/index.html\0", 0x4, 0x0)                = 10 0

ちなみに、Homebrewでインストールしたnginxの何が何だか分からない問題

この検証にあたり、適当なプロセスとしてnginx動かそうと思いました。で、Homebrewでインストールしてみたのですが、どのポートで動いてるかとかぜんぜんよくわからなかったので調べてみましたのメモ(lsofの使い方いつも忘れるのだった)。

ちなみに、起動は単に nginx と打つだけで良い。 lsofはプロセス名で検索することができるので、今回はnginxを指定して調べる。この場合、-cオプションを使用する。さらに、-P オプションを使用することでポート番号を生のまま(数値で)表示するオプションを追加している。

$ lsof -c nginx -P | grep LISTEN
nginx   32055 sugamasao    6u    IPv4 0xed732b31079edf33       0t0      TCP *:8080 (LISTEN)
nginx   32056 sugamasao    6u    IPv4 0xed732b31079edf33       0t0      TCP *:8080 (LISTEN)

こうすると、どうやらLISTENしているのは8080ポートらしいということがわかる。

-Pを指定しない場合、こんな感じになってService Nameとやらの値に変換するようになっている(生データをデフォルトにしてほしいなぁ)。

$ lsof -c nginx | grep LISTEN
nginx   32055 sugamasao    6u    IPv4 0xed732b31079edf33       0t0      TCP *:http-alt (LISTEN)
nginx   32056 sugamasao    6u    IPv4 0xed732b31079edf33       0t0      TCP *:http-alt (LISTEN)

っていうか、まともにlsofの結果見るとオープンしてるファイルとかもわかるし結構良いですね、、、。

オマケ

Homebrewでインストールされたnginxが使うconfの場所がパッとわからなくてこんな感じで確認しました(こういう使い方もできるよ、ということで一つ)。

masterプロセス、あるいはプロセス名自体でdtrussでアタッチして、nginx -s reloadで設定ファイルを読み直すことで、どこのファイルを参照しているかを確認できた。

$ sudo dtruss -n nginx 2>&1 | grep conf

こうしてから別ターミナルで nginx -s reloadするとこんな感じのログが表示されていた。

32880/0x641a4:  stat64("/usr/lib/system/libsystem_configuration.dylib\0", 0x7FFF54E3E9F8, 0x2)           = 0 0
32055/0x6168e:  open("/usr/local/etc/nginx/nginx.conf\0", 0x0, 0x0)              = 4 0
32880/0x641a4:  read_nocancel(0x4, "#\n# OpenSSL example configuration file.\n# This is mostly being used for generation of certificate requests.\n#\n\n# This definition stops the following lines choking if HOME isn't\n# defined.\nHOME\t\t\t= .\nRANDFILE\t\t= $ENV::HOME/.rnd\n\n# Extra OBJECT IDENTIFIER in", 0x1000)               = 4096 0
32880/0x641a4:  open("/usr/local/etc/nginx/nginx.conf\0", 0x0, 0x0)              = 4 0

内容を確認してみると、どうやら /usr/local/etc/nginx/nginx.confっぽいなってのがわかった。便利〜〜

忘年会しても忘れてはいけない便利最高grepオプション

grep -o

これです。おじさんになっても恥ずかしがらずにこういうのを書いていくのが重要かなと思うようになりました。

-oを使うと、その引数にマッチした文字列のみ出力できます。 例えば、こんなアクセスログがあったとして……

xxx.xxx.xxx.xxx - - [26/Dec/2014:18:59:12 +0000] "HEAD /login.do HTTP/1.0" 404 0 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"
xxx.xxx.xxx.xxx - - [26/Dec/2014:18:59:15 +0000] "HEAD /login.action HTTP/1.0" 404 0 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"
xxx.xxx.xxx.xxx - - [26/Dec/2014:18:59:16 +0000] "HEAD /index.action HTTP/1.0" 404 0 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"
xxx.xxx.xxx.xxx - - [26/Dec/2014:18:59:17 +0000] "HEAD /struts2.action HTTP/1.0" 404 0 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"
xxx.xxx.xxx.xxx - - [26/Dec/2014:18:59:18 +0000] "HEAD /root.action HTTP/1.0" 404 0 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"
xxx.xxx.xxx.xxx - - [26/Dec/2014:19:50:15 +0000] "HEAD / HTTP/1.1" 302 0 "-" "Hatena Antenna/0.5 (http://a.hatena.ne.jp/help)"
xxx.xxx.xxx.xxx - - [27/Dec/2014:02:18:38 +0000] "HEAD / HTTP/1.1" 302 0 "-" "Hatena Antenna/0.5 (http://a.hatena.ne.jp/help)"
xxx.xxx.xxx.xxx - - [27/Dec/2014:08:44:14 +0000] "HEAD / HTTP/1.1" 302 0 "-" "Hatena Antenna/0.5 (http://a.hatena.ne.jp/help)"

UAがHatena Antennaが来ている時のアクセス時間を知りたいぞい!とか思うとするじゃないですか。こんな感じにしたら、grepだけでも十分なのだった……!!!!!!!

$ grep "Hatena Antenna" /var/log/nginx/access.log | grep -Eo [0-9]{2}:[0-9]{2}:[0-9]{2}
14:08:19
14:08:19
14:15:00
14:15:00
 :

この例では-Eを併用して正規表現を使っているけど、例えばegrep -o ナントカみたいにしても良い。

便利ですね。

grepの戻り値あるいは -q

grepって、文字列が引っかかるかどうかで戻り値が変化するんですね。つまり戻り値を確認するだけで値が存在しているか確認できるというわけです。

$  echo foo | grep foo
foo
$  echo $?
0
$  echo hoge | grep foo
$  echo $?
1

さらに、-qを使うと、ヒットした文字列を出力しないので、より明示的に"このgrepは文字列の存在確認をしているのだ"というのが強調されるのと、自動化するときに標準出力が挟まってきてダルいみたいなことを回避できます。

$  echo foo | grep -q foo
$  echo $?
0

便利ですね。

どうしても書かなくてはいけない宣伝

またかって感じかもしれませんが、よろしくお願い致します。

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

あと、一時期パーフェクトRubyを始めとするパーフェクトシリーズのKindle版がamazonから姿を消していましたが、今日確認したら掲載されていましたので、買う機会を逃していた皆様はぜひお買い求めください*1

パーフェクトRuby

パーフェクトRuby

パーフェクトRuby on Rails

パーフェクトRuby on Rails

購入の際は紙かKindleか念のため確認してくださいね。

*1:インデントが破滅していた問題の対応をしていたっぽいですね