HTML Media Captureの記述の仕方

webでHTML Media Captureについてググると、上の方にでてくるコンテンツはわりと、

<input type="file" accept="image/*;capture=camera" />

とか、

<input type="file" accept="image/*" capture="camera" />

とか書いてあったりするのだけど、http://www.w3.org/TR/html-media-capture/ を見たら、

partial interface HTMLInputElement {
                attribute boolean capture;
};

のようになっていて、capture属性はbooleanになっているなー、ということで、

 <input type="file" name="image" accept="image/*" capture>

のように書くらしい。

なんでだろーと思って調べてみたら、

http://www.w3.org/TR/2012/WD-html-media-capture-20120712/

のときの仕様だと、上の方の書き方で、最新のものだと、captureがbooleanになるらしい。

20120712以降で改訂ということは、それ以前のブラウザだと、上の書き方じゃないと動かなかったりすることあるのかな。。。

mod_proxy_balancerで振り先が//にならないようにする。

<Proxy balancer://app>
  BalancerMember http://192.168.0.1/
</Proxy> 

<VirtualHost *:80>
   ProxyPass / balancer://app/
</VirtualHost>

のように、BalancerMemberの定義の末尾に"/"をつけると、振り先で、//に対して振るようになってしまうっぽいので、つけないように気をつける。

<Proxy balancer://app>
  BalancerMember http://192.168.0.1
</Proxy> 

<VirtualHost *:80>
   ProxyPass / balancer://app/
</VirtualHost>

dalliはfork safeか?

ふと、dalliがunicornなどのmulti processで動作するアプリケーションで動かしたときにコネクションを張りなおす処理をしないといけないか気になったので、調べてみた。


gist8284358

これを実行してみると、

true
1
I, [2014-01-07T00:27:41.396287 #9290]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.396622 #9291]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.396905 #9292]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.397327 #9293]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.397726 #9294]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.398108 #9295]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.398495 #9296]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.399108 #9297]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.399440 #9298]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.399961 #9299]  INFO -- : localhost:11211 failed (count: 0)
[9290, 0, 1]   
[9292, 0, 1]   
[9291, 0, 1]   
[9290, 1, 1]   
[9293, 0, 1] 


こんな感じになり、forkした直後のログで失敗した、というエラーメッセージが出るが、その後、再接続し、通信できているようだ。

dalliのソースコード(acb1ff3afd4d)をpidでgrepすると、

# lib/dalli/server.rb
    def verify_state
      failure!(RuntimeError.new('Already writing to socket')) if @inprogress
      failure!(RuntimeError.new('Cannot share client between multiple processes')) if @pid && @pid != Process.pid
    end


という処理が出てきて、newしたときと、pidが変わっているかをチェックしていることがわかる。

このverify_stateはクライアントがリクエストしようとしたときには必ず通過するようになっているので、これでpidの変更を検出しているようだ。

failureの中では、

    def failure!(exception)
      message = "#{hostname}:#{port} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
      Dalli.logger.info { message }

      @fail_count += 1
      if @fail_count >= options[:socket_max_failures]
        down!
      else
        close
        sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
        raise Dalli::NetworkError, "Socket operation failed, retrying..."
      end
    end

のようになっていて、失敗回数を保持するカウンタを1あげつつ、例外を吐いている。

ここで発生させた例外は、

# lib/dalli/client.rb
    # Chokepoint method for instrumentation
    def perform(*all_args, &blk)
      return blk.call if blk
      op, key, *args = *all_args

      key = key.to_s
      key = validate_key(key)
      begin
        server = ring.server_for_key(key)
        ret = server.request(op, key, *args)
        ret
      rescue NetworkError => e
        Dalli.logger.debug { e.inspect }
        Dalli.logger.debug { "retrying request with new server" }
        retry
      end
    end

serverを呼びだしている元の、performメソッドで、rescueされて、retryするようだ。

pidファイルの管理をする

pgrep -F pidfileオプションを使うと、pidfileを開いて、その中にあるプロセスIDが存在するか調べ、存在している場合には、0で終了してくれるようだ。

なので、起動スクリプトとかで、pidfileの存在チェックとかをするときには、

   if test -e $PIDFILE && pgrep -F $PIDFILE > /dev/null; then

       restart

   fi

とか書いてやれば、マシンがハングアップとかして、正常に終了しなくて、pidfileが残っているがファイル内にあるプロセスIDはもういない場合にrestartをしても悲しいことにならずにすむ。

なるほどUNIXプロセス

http://tatsu-zine.com/books/naruhounix

帰省中の新幹線でナルホディウス!しながら読んだ。

 

中身としては思っていたよりはライトな内容で、わりと知っていることだった。

 

Resqueのworkerが毎回worker実行するときにforkするのはなんでだろーと思っていたので、そこの解説はよかった。

Resqueのところで、fork時にsrandしている説明があるが、これは解説通りごくごく一部のバージョンのバグなどで動かないというだけなので、以前調査したように、rubyではsrandする必要はない。

 

waitpid(2)が、子プロセスが死んでしまってから親プロセスが実行しても、Kernelがちゃんと動作を保証してくれる、というのは知らなかったので良い発見だった。

 

Kindleで読む気だったのだけど、Kindleがちょうど応答不能になってしまっていた関係で、泣く泣くPCで、iBookで読んだのだけど、Kindleよりは目が疲れるけど、サンプルすぐ試したりできるし、PCで本読むのはふつうにありだなと思いました。Cracking The Coding Interviewは重くて持ち運びにくいので、電子版も欲しくなってしまった

 

はてなブログへ移行しました

はてなブログがでてからだいぶ経過して、ぼちぼち、はてなダイアリーもいつ終了してもおかしくないかなという気もしたので、いい加減重い腰をあげて移行しました。

 

今年は、去年よりもうちょいコンスタントに書けるようにがんばろうと思います。なるべく休日につき1記事ぐらいはあげていきたいところ。。。

isucon #3 予選に参加してきた

1日目で参加してきた。

今回は他の二人にアプリケーション側はお任せして、自分はミドルウェアまわりをやる、ということにしていた。

最初は、PHPを動くように設定を変更して、ベンチマークを実行していたのだけど、どうもfailしてしまう。原因を見ると、ログイン回りなので、セッションがおかしそうだ、というところまではわかったのだけど、ちゃんとした原因まではわかっていなかった。

セッションをファイルストレージに書くように変更してもfailするので、途中rubyにしようか、phpを5.5でコンパイルしなおして頑張ってみる、とか12:30ぐらいまで右往左往してた。

その後もfailし続けたので、セッションをAPCuに入れるようにしようとしてみたりとかしたが、解決できなかった。

途中で、ベンチマークスクリプトのworkloadをあげると、なんとかスコアが最後までfailしないでおける、ということがわかったので、それからようやくアプリのチューニングをしはじめた。

今までの経験上、isuconの場合はこれ知ってる、こうしたら早くなるはず...とかって手を動かしてしまうと検討違いのところに時間を使ってしまうのをようやく学びつつあるので、今回はなるべく現在のボトルネックになっている処理以外のことは考えないようにした。

そこでapacheにログと実行時間を出すように変更して、それを集計して、pathごとに実行回数、平均実行時間、合計実行時間を出すようなツールを書いた。

合計時間ベースで見ると、/および、/recent/xxxが遅いねー、ということで、pt-query-digestをかけて、クエリも確認して、INDEXを貼ったり書き換えをしてもらった。

INDEXを貼ったのだけど、/home/isucon/schema.sqlを変更しても反映されないなー、とちょっとはまってしまったが、まぁinitに書けばいいやということでscriptを書いた。

alter table memos add index (is_private, created_at)
alter table memos add index (user, created_at)
SELECT * FROM memos WHERE is_private=0 ORDER BY created_at DESC, id DESC LIMIT 100 OFFSET #{page * 100}

このクエリは、

SELECT id FROM memos WHERE is_private=0 ORDER BY created_at DESC, id DESC LIMIT 100 OFFSET #{page * 100}
SELECT * FROM memos WHERE id IN (?,...)

という感じにした。OFFSETは気になる感じではあったけど、とりあえずそのままになった。また、usersへのクエリが非常に実行回数が多かったので、INでとるように変更した。

ベンチマークをかけて、topで見ていると、mysqlがCPUを一番たくさん使っていたのでmysqlをチューニングしようと思ってmy.cnfを探したがどこにあるかわからなかった。あと管理者権限で入る方法がわからなくってどうしようと色々しらべていたのだけど、あとから聞いた話だと、ちゃんとREADMEに書いてあったようだ。(勘で試してなんとか入れた

my.cnfはとりあえず/etc/my.cnfに書けばまぁだいたいの環境で読もうとするはず、ということで/etc/my.cnfに書いた。

ただデータセットはメモリに十分の乗りきるので、あまりmysqlのチューニングをしても効果がないだろうなということで、INSERTまわりに効きそうな変更以外は特にしなかった。

具体的には、

thread_cache_size = 200
innodb_buffer_pool_size = 1G
innodb_flush_log_at_trx_commit = 2
innodb_locks_unsafe_for_binlog = on
innodb_doublewrite=off

innodb_locks_unsafe_for_binlogはあまり効かなかったっぽいのでいらなかったかなと思う。例によって、早さのみを考慮した設定なので、本番などに適用するのはおすすめしない。

query_cacheはたぶん無駄だろうなーと思いつつ、一度有効にしてみたが、むしろ遅くなったので、やめた。

あとは他の人がクエリをAPCuにcacheするように書きかえたりしていて、気がつくと、だいぶmysqlのCPU使用率が下がってきたので、workloadの並列数を変えたりして、いちばんスコアの良いところを探っていったりした。

すると、markdownのプロセスの実行がCPUをくっているのがちらほらtopで見えだしたので、次はこれを潰さないと次のボトルネックにたどりつけないだろうなー、ということで、bin/markdownを覗いたら、コードがカオスすぎて絶望した。

実行自体が遅いというのもあると思いつつ、おそらくfork & execしまくっているのが問題なのかなーと思ったので、これをdaemon化して、TCPでappと通信するサーバーを書けば、なんとかできるかなーと思い、調査していた間に、適当なPHPのMarkdown Parserで試してもらったら動いてしまったので、どうかなーと思いつつ、他のperl以外のチームがこのコードをいじらないといけないようだとperlの人が有利になりそうなので多少は互換性が落ちても問題ないのかなーと思って、そのままいくことにした。

markdownを解決したら、スコアが10000を越えだしたので、あとは細かいところをあれこれやったり、ミドルウェアの設定とかを見直したら20000ぐらいになった。

終了したあとで、どの程度AMIをいじったりしても良いものか不安だったので、30分前からは再起動の検証とか作成したAMIをもとに起動の検証とかをしていたら終了した。

今回は(も?)けっこう別のところでハマったりして、チューニング自体にはうまく時間を割けなかった気はするのだけど、3回目の参加ということで、今までより計測と洞察を元に確実なものを改善する、ということは着実にできたかなとは思う。とはいえ、本戦で勝つにはもうちょっと抜本的な変更にもtryできないとだめだろうな、ということで今までのAMIの復習をがんばろうと思います。(まだ出れるかわからないけど。