たごもりすメモ

コードとかその他の話とか。

ISUCON4 いってきた&勝ってきた! #isucon

連覇だ! ヒャッホウ!!!


特にkazeburoさんのエントリに最終的な状況についての詳細が書いてありますので、ぜひそちらもどうぞ。sugyanは自分で力不足とか言ってますが、ISUCON本戦という場で、業務でほぼ使ったことがないはずのRedisメインのコード改造をごりごりやってちゃんと動かす人なので、チーム外のみなさんは騙されてはいけません。それできるの超すごいんやで。

主催のLINE株式会社、あれこれ提供いただいていたデータホテル改め株式会社テコラス様、問題作成担当 @mirakui, @rosylilly, @sora_h の3氏、本当にありがとうございました。たのしかった!

だいたいこんなんで

大雑把に時系列の経緯だけ書くとこんな感じ。

  • 開始-12時頃
    • ssh鍵の導入など環境の準備、コード読む、全体の設計変更の議論
  • 12時-14時過ぎ
  • 14時半-18時半過ぎ
    • NW帯域詰まってどうにもならず呪いの言葉を吐き続ける
    • kazeburo親子を見てほっこりする
    • 散歩
  • 18:43-19:00
    • Cache-Control!

実際のところ14時半には複数台構成への変更が完了し1Gbpsをほぼ使い切った状況までいってたようで、その他の、スコアに影響がほとんどないけど普通のWebアプリとしてはやったほうがよさそうな変更をやりながら悶々と考えていたのが17時頃まで。
ほぼ諦めかけて投げやりな気分になったところで33万点を目にし、現実的に動画を配信しないでスコアを上げる方法があるらしい、ということで可能性を片っぱしから列挙しては自分で却下したりしてた。その中に Cache-Control も含まれていたが、この時は設定ミスで気付かず。もはや万策尽きた、というところになりかけたが、最後の最後でなんとか間に合ったのでした。

なおlocal ipを --hosts に指定できること、それによりNW帯域を稼げることは気付いてなかった。Cache-Control のほうが間に合わなかったら5位に入ってなかったんじゃないかと思う。(ので結果発表が怖過ぎた。)

そういえば最後が劇的すぎて忘れてたけど、途中で突然ベンチマークツールが登録に来なかった advertiser id についてレポートが無いといってFAILにされてた時間帯があったんだけど、あれはなんだったんだろ。裏側でベンチ用データがバグってたとかなんじゃないかと思ってたんだけど。だいぶ長いことデバッグログを入れたりしながら唸ってたんだけど、そのうち勝手に直った。

変更した構成

最初に考えた構成で、結局最後までこのまま。帯域が詰まらなくてrps勝負になったときに威力を発揮した構成なんじゃないかと思うんだけど、確認できず。

  • 動画は(例によって)WebDAVでnginxから配信する
    • アップロードリクエストが来たらレスポンスを返す前にnginx全台にpush
    • /asset リクエストが来たらnginxが直接返す
  • isu401 は1CPUなので、基本的にはRedis専門サーバとする
    • ただしappも立てておいて、広告の登録やレポート集計にはこちらを使う
    • レポートのリクエストはベンチ走行中も来ていたようなので、その負荷の隔離先
  • isu402, isu403 で /ads, /ad, /count, /redirect の処理
    • /ads -> /ad のリダイレクトを行わず、直接jsonを返してもベンチは文句を言わなかったのでそうした*1
    • ログはファイルに吐かれていたのをRedisを使うように

他に悶々時間帯にささやかなチューンとして、公平性ボーナスの条項を満たし次第*2、あとはバイト数が最も小さな動画広告だけを返す、などということもしていた。ほぼ結果に影響なかったみたいだけど。

いちおう何がどうなってもいいよう、isu401 のnginx設定やperlのコードは isu402/3 と全く同じ状態で動くようになっている。こういうふうにしておくと、ボトルネックが移動して問題を再考しないといけない場合にも便利。

ブレークスルー案として検討した項目

動画が1ファイル5.3MBから5.4MB、1Gbpsを使い切ったあたりでremoteなスコアが8000前後、アクセスログによるとだいたい700回くらいのレスポンスを返していたようだった。
で、このスコアを33倍以上にするために動画リクエストをどうすればいいかと考えたときの候補。

  • いきなり206を返す(ダメ)
  • レギュレーションによると25%のERRORが許可されているので、動画の25%をエラーにしつつclickを増やす
    • 動画を返さないとそもそもclickに来ない、ダメ
  • 動画ファイルのmd5コリジョンを発見
    • ムリ……というかアップロードされてくるのでいちいちそんなもん見付けてられるか
  • 動画の先頭とかの部分だけ返してチェック通すとか?
  • ETag, Cache-Control (最終的にはこれだった)
  • ベンチ走行中の1分間のあいだは動画リクエストが来たら全部 sleep して60秒待たせる
    • 60秒経過後に溜まりに溜まったリクエストに順にレスポンスを返せば、60秒間で稼いだコネクション数だけのスコアが稼げるのでは?
    • と思ったけど33倍ってことはつまりベンチ実行合計時間が33分以上になるってことで、いやそりゃダメだろと
    • 結局その戦略はsleepしてる間にレポートのリクエストが来て不整合でFAILするらしい

他にもいくつかあった気がするけど忘れた。ひたすら会場内をうろうろ散歩したり座ってぶつぶつ独り言いいながら検討してた。

問題設定について

Cache-Control つけるだろ、というのはまあ冷静になればわかるんだけど、もうちょっとヒントがあっても良かったかなあという気はする。例えばレギュレーションの impression の説明のところに、有効なインプレッションとして認められるレスポンスコードを列挙しておくとか。

終わってみると、Cache-Controlに気付くか気付かないかで消費するにはもったいない問題だったんじゃないのという気がして、そこはちょっと残念。超大量のアクセスをどう捌くかこそがISUCONのいちばん脳汁出るところで、その戦いがもっと多くのチームでできればよかった。redirectをひとつ削減したやつとかもそこで効くはずだったし、Redis::Jet や Chobi が火を噴いただろうに。
とりあえずうちのチームの得点、最後のベンチ走行時にどこがボトルネックになってたかすら全く見られてない*3んだけど、たぶんちゃんと調整すれば workload 8 で100万点いったんじゃないかなと思う。

参考資料

今年もマスタリングnginxにはお世話になりました。

が、もうちょっといい解説があった気がするんだよなー、なんだっけかなー、と30分くらい悩んだところで気付きました。自分でちょっと前に書いたSoftware DesignのNginx特集の記事だ!!!!!!!

これまじ分かりやすくて超便利でした。みんなも買うといいよ……と思ったけど、Amazonだと中古だけだな。PDF版を買うといいですね。いい時代だ。

Software Design 2014年7月号 | Gihyo Digital Publishing

まとめと今後の話

なんにしろ、面白かった。じつにすばらしいイベントでしたね!

で、2回勝ったので、まだあんまりちゃんとチームで合意をとってないけど、もうこのチームで出ることはないんじゃないかなーという気がする。出るなら自分も、もうちょっと他の人とやるISUCONを経験してみたい気がするし。何より勝ち逃g(ry

で、来年も開催できるらしいですね! 2回出場側にまわって、そろそろまた出題側に戻ってもいい気がしてきた。気力が回復した。w
これもまた別途確認ですが、やれれば出題担当しようかなーと思う。ちょうど勝ったことだし。

また ISUCON Makers Casual Talks というイベントを近々やろうかなと思います。いろいろ話したいことがあるんですよ! 出題をやった側にしかわかんない苦労とかがですね! そういうのをビールを飲みながら赤裸々に話したい!

さて

来週はRubyConfでサンディエゴだ!

*1:スコアに関係ないHTTPリクエストの削減

*2:つまり該当slotの広告を1度ずつ返したら

*3:なんと去年に続いて今年も……。。。。

Hokkaido.pm #12 いってきた&しゃべってきた

https://atnd.org/events/57038



YAPC::Asia Tokyo 2014 ベストスピーカー2位ということで地方pmに3回行かせてもらえる権利のうち1回目を行使しました。ありがとうございます。
主催の @aloelight さん、会場を提供してくださったクリプトン・フューチャー・メディア株式会社さま、ありがとうございました。ボーカロイド作ってる会社!!! とかテンション上がっておりました。

で、せっかく行ったので自分も自分にできる話をしました。

ちょっと散漫な内容になりましたが、思うところをあれこれ突っ込んであります。障害対応とはつまり過負荷障害の対応シミュレーションだったんだよ!!!

という冗談は(半分くらい本気ですが)置いておくとしても、運用系モニタリングとサービス改善のためのデータ分析は実際に技術領域としてかぶるところがあるなあとは最近よく思っています。

みなさんにもぜひ、どちらかの目的だけでもいいから始めてみていただきたい。

お手軽なデータ収集からでも。けっこう効果があると思います。

最近はFluentdとかもあるしね!



当日聞いた話、内容自体はもう普通に普段東京で聞くような勉強会の話と変わらず、面白かった。あんまり普段聞かない話を聞いた気がする。

SECCON CTFを主にやっています、みたいな人の話は普段東京の勉強会だと聞く機会があまりなく、コミュニティが細分化されちゃうとそういう弊害があるなあ、というか、もっと他所のコミュニティに顔を出せよという話なんだけどいやいや、みたいなことをぐるぐる考えていた。

あと会場で聞いてみたところ、やっぱりWebサービス業者は東京に集まっているのか、そういう仕事の人はあまり多くないみたい。とはいえ請負一辺倒というわけでもなく、パッケージソフトウェア開発の人も……って、よく考えたらクリプトンの人がいたからそりゃそうだな。今頃気付いた。懇親会でもその話をしてたのに。

あとは聞いたところによるとRubyのコミュニティ(?)はもうちょっと大きいらしい、とか、結局コミュニティ運営は主催者のやる気が、みたいな話も。そのあたりはどこでも変わらないなあ。

今回のHokkaido.pmはATND上で13人という参加者数で、ちょっとさびしい気もするが、そうは言ってもヒカリエのPerl Casualも100人集まらないし、時代なのかどうなのか、いやいや、みたいな話も。うーん。人数多くなるとそれはそれで開催が面倒になる部分もあるけど。

印象的だったのは函館や釧路からの参加者がいたということで、200kmとか400kmとか恐ろしい距離を勉強会のためにやってくるということ。すごい。そのへんの距離感は聞いてるとまったく理解できなくて、会話のなかでしばしば混乱してた。400kmって……。

YAPC::Asiaは日本のみならず世界中から開発者が集まってくるけど、国内のあちこちでも、こうやって近隣から人が集まってくるイベントがあれこれあるといいなあ、色んな人と話せるし、などと無責任に思う。都合が許すならそういうところでふらふら遊びに行きたい。(あと2回権利がある!)

まあそういう感じで、技術にまみれた旅行でした。天国かと思った。楽しかった。







あと2回権利があるので、ぜひ地方pmに呼んでください! 行きます!

はじめてのmaven central 公開

前置き:このエントリはJavaおよびJava周辺の*1開発環境に全く縁の無い人間が、可能な限り依存ソフトウェアを少なく手順をシンプルに保ったままやろうとしたものであり、知識・経験のある人にとっては全く最適な手段でなかろうことをお断りします。

先日のエントリ で書いたとおり woothee 1.0.0 をリリースした。Perl, Ruby, Node.js および PHP などはそれぞれの言語毎のモジュールリポジトリに登録されている。
が、Javaについては自分が Maven Central の勝手がわからず、されてると便利だよなーとは思いつつ放置していた。

が、なんと @making さんからMaven Central登録用の pull requestがきた 。きてしまった。これで最大の問題(xmlを書く)はおおむね解決されてしまったので、覚悟を決めて登録作業をすることにした。
せっかくやったので、アカウント登録からfirst releaseまでの手順をここにまとめてみる。

なお環境は以下の通り。

  • 適当なバージョンのMac OSX (10.8.5)
  • 適当なバージョンのJDK (1.7.0_55)
  • 適当なバージョンのMaven (3.2.1)
  • できる限りCLIで頑張る
  • XMLEmacsで編集する

JDKMavenは適当にインストールする。javacとmvnコマンドが動けば多分大丈夫。Mavenについては自分はtarballを展開してパスを通した。

なお英語のドキュメントはここにあるが、割とJavaに慣れた人向けのドキュメントな気がする。つらかった。

パッケージ名前空間を決める

Maven Centralへのモジュール登録は、人(アカウント)ごとにアップロードできるパッケージの名前空間が決まっている。Java書いてると出てくる package com.mycompany.softwarename; っていうアレ。
確保する必要があるので、何か決めよう。自分は tagomor.is っていうドメインを持ってたのでそれを使った*2。これ、誰がどうやって正当性を確認しているんだろうな……。

なお登録時に共同メンテナみたいなのも登録できるので、1パッケージを一人しかデプロイできないということではない。安心。

アカウント登録

誰がどのパッケージを登録できるのか、みたいな権限はOSSRH JIRAで申請して変更してもらう。ので、まずそのJIRAのアカウントを作る必要がある。このアカウント(ユーザ名/パスワード)は実際のバイナリのアップロード時にも使う。

https://issues.sonatype.org/secure/Signup!default.jspa

このアカウント作成はすぐ終わるはず。終わったらパッケージアップロード権限的なのをもらうためのチケットを作成する。

https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134

SummaryとDescriptionは、なんか適当に書けばいいんじゃないでしょうか。自分の場合はこれだけど、プロジェクト名くらいしか書いてない。

groupId には前で決めたパッケージの名前空間を書く。例えば is.tagomor と書いておけば is.tagomor.** なパッケージは自由にデプロイできることになる。

Project URLとSCM uriはそのOSSプロジェクトのWebページとリポジトリURL。GitHubの場合は普通にViewのURLと .git のHTTPS URLを書いとけばいい。

Username(s) は共同開発者がいる場合はその人の JIRA username を書く。とりあえず自分だけでいいなら空欄。

Already Synced to Central は初めて申請するなら No でいい。これは多分既に申請通ってたりする分の修正とかの場合に選ぶものだと思う。

これを埋めたら Create する。すると誰かがなんか作業をやってくれる。ドキュメントには2日は待てみたいに書いてあるけど、自分の場合は8時間で完了した。

完了すると登録したメールアドレスに "Resolution: Fixed" と書かれたメールが来る。
なおメールの末尾には "please comment on this ticket when you promoted your first release, thanks" とも書かれてた。こうしておかないといけない事情が忍ばれる。

pom.xml の記述

これはやってもらいました! すまん!!!
https://github.com/woothee/woothee-java/pull/1

これも基本的には公式ドキュメントにある記述を pom.xml に入れていけばいいと思うんだけど、ひとつだけ問題があって、このままだとビルド時にGPG Keyを要求するので Travis-CI が mvn install したときにコケる。

ググったらStackOverflowの記事がヒットした。これをそのままパクり、普通に mvn install や mvn test したときにはGPG署名用プラグインを呼ばないようにpom.xmlを変更。これで Travis-CI がこけなくなる。また、この時点で mvn install やら mvn test するときに、まだGPGのことを考えなくて済む。

で、この状態でとにかくテストを通せるようにするとか、とか。

GPGのセットアップ

Maven Centralにバイナリを上げるにはGnuPGで署名する必要があるらしい。ので、インストールする。以下のURLから "Download GPG Suite" をクリックしてdmgをダウンロード、展開してインストールした。

https://gpgtools.org/

インストールすると勝手に起動したので Key -> Generate して鍵ペアを作る。Full NameとEmail addressを入れて Pass Phraseを入れるだけで基本的には終わり。
作ったら公開鍵の方は "Send public key to keyserver" しておく。

またインストールが完了したら、作業中の端末で "gpg2" コマンドが動くことを確認しておく。動かなかったらshellを起動し直すなどする。

アカウント・パスフレーズの設定

OSSRHやGPG鍵を使えるよう、Mavenの設定をしてやる必要がある。設定は settings.xml というXMLに書くが、これはユーザ毎にあるものらしい。自分の場合は ~/.m2/settings.xml に置けば読みこまれた。

OSSRHとGPGの設定が両方必要。まとめるとこんな感じ。

<settings>
  <servers>
    <server>
      <id>ossrh</id>
      <username>YOUR_JIRA_USERNAME</username>
      <password>YOUR_JIRA_PASSWORD</password>
    </server>
  </servers>
  <profiles>
    <profile>
      <id>ossrh</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <gpg.executable>gpg2</gpg.executable>
        <gpg.passphrase>GPG_KEY_PASSPHRASE</gpg.passphrase>
      </properties>
    </profile>
  </profiles>
</settings>

YOUR_JIRA_USERNAME, YOUR_JIRA_PASSWORD とか GPG_KEY_PASSPHRASE のところは自分のものを埋めること。
これを書いておけば maven plugin が勝手に読んでくれる。

デプロイ

以上で手順は完了。「pom.xmlの記述」で書いたとおり、指定したときにしかGPG署名をしないような記述にしたので、デプロイ時にはそのスイッチを有効にしてやる必要がある。

$ mvn clean deploy -DperformRelease=true

うまくいったら最終的にはこんな表示が出て終了するはず。

[INFO]  * Upload of locally staged artifacts finished.
[INFO]  * Closing staging repository with ID "istagomor-1002".

Waiting for operation to complete...........

[INFO] Remote staged 1 repositories, finished with success.
[INFO] Remote staging repositories are being released...

Waiting for operation to complete..............

[INFO] Remote staging repositories released.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:33 min
[INFO] Finished at: 2014-10-28T16:07:10+09:00
[INFO] Final Memory: 30M/363M
[INFO] ------------------------------------------------------------------------

この時点ですぐに以下のURLを見たところ、ちゃんとパッケージ名をたどったところにアップロードされていた。
https://oss.sonatype.org/content/groups/public/

うまくアップロードできたことが確認できたら、JIRAのチケットのほうにコメントを書いておく*3。自分の場合はこんな感じで適当に書いた。

I've just promoted my first release, woothee-java-1.0.0.jar:
https://oss.sonatype.org/content/groups/public/is/tagomor/woothee/woothee-java/1.0.0/

完了!

これで(特に問題がなければ)定期バッチによりmaven centralに同期されるらしい。作業の進捗を示すものだと思うけど、以下のようなメールが順番に来てた。たぶんみっつめが来たら全部の処理が完了。

  • "Nexus: Staging Repository Dropped"
  • "Nexus: Staging Completed"
  • "Nexus: Promotion Completed"

見事以下のURLから見えるようになってた。
http://repo1.maven.org/maven2/is/tagomor/woothee/woothee-java/1.0.0/

おつかれさまでした!

*1:特にIDEやGradleなどなど

*2:申請時の例はソフトウェア名を含めろという感じになってたので is.tagomor.woothee で申請したんだけど、処理する人が is.tagomor でいいよって変更した。いいのか。

*3:誰かの処理がこれで行われるのかな?

多言語対応User-Agentパーサライブラリ Woothee 1.0.0リリース: OS versionの出力をサポート

地道にバージョンアップを繰り返しているUser-Agentパーサライブラリの woothee ですが、このたび 1.0.0 をリリースしましたのでお知らせいたします。

http://woothee.github.io/

今回は前からやろうやろうと思ってたOSバージョンのparseをサポートしました。特にスマートフォン(iOS/Android)のバージョンが気になる人が多いようで、手元でもあったらいいなというケースが多くなってきたため、えいやと入れました。

ただし今回はあまり時間がなかったので、とりあえず自分で使いそうな以下の言語のみリリースしています。

以下の言語についてはまだ対応していません。誰かPull Requestを送るといいと思います。

現状こんな感じですね。

便利だと思うので、どうぞみなさんお使いください。

10/28追記

PHPも 1.0.0 対応されました。 @okonomi さんによるものです。
https://github.com/woothee/woothee-php/pull/13

Rubyでdynamic scopeを(メソッド定義だけ)実現する dyna_mo を書いた

ついカッとなって書いた。たのしかった。

https://github.com/tagomoris/dyna_mo

どういうことかというと、つまりこういうことだ!

require 'dyna_mo'
 
module MyModule
  class MyClass
    attr_accessor :num
    def initialize; @num = 0; end
    def name; "name"; end
    def sum(numbers); @num + numbers.reduce(:+); end
  end
end

こういう割と普通なクラスに対して

dynamo_define('MyModule::MyClass', :mytest_case_default) do
  def_method(:initialize) do
    @num = 1
  end
  
  def_method(:name) do  # #name を差し替える
    "dummyname"
  end
  
  def_instance_method(:name, :mytest_case1) do # #name 差し替えの別パターン
    "dummyname1"
  end
  
  def_method(:sum) do |numbers|
    @num + numbers.reduce(:+) + 1
  end
  
  def_class_method(:create) do |init_num=0| # 元の定義になかった MyClass.create の追加
    obj = self.new
    obj.num = init_num
    obj
  end
end

特定のとき*1にこのメソッドをこういうふうに上書きしてね!*2 ということを指定して

class SynopsisTest < Test::Unit::TestCase
  def test_synopsis
    assert { MyModule::MyClass.new.name == "name" } # 元の状態

    obj = MyModule::MyClass.new
    
    assert { obj.num == 0 }  # インスタンス変数 @num は 0
    assert { obj.name == "name" }
    
    dynamo_context(:mytest_case_default) do # ここから定義をスイッチ
      assert { obj.num == 0 } # #initialize is not overridden # これは @num の参照だけで変更なし
      
      assert { MyModule::MyClass.new.name == "dummyname" } # ウヒョー!
      assert { obj.name == "dummyname" } # 生成済みのオブジェクトのメソッドだってもちろん変わる!
      
      assert { MyModule::MyClass.new.num == 1 } # initializeだって上書きできる!
      
      assert { MyModule::MyClass.new.sum([1,2,3]) == (1+(1+2+3)+1) } # 普通に引数だって与えられる!
      
      assert { MyModule::MyClass.create(100).num == 100 } # ちょっとこのテスト専用に便利メソッド!
    end
  end

こういう感じで使う! dynamo_context(:mytest_case_default) に与えているブロックの中でだけ MyClass#name や MyClass#sum の動きが違ったり MyClass.create が使えたりする!!!!!!!!!!!!!!!!!!!
コンテキスト外で作った obj のインスタンス変数 @num は変化していないがそれを使うメソッドだけが書き変わっている! ウヒー!

もちろん別のコンテキスト名を指定すれば別の動きかたをする!

class SynopsisTest < Test::Unit::TestCase
  def test_synopsis
    dynamo_context(:mytest_case1) do # もちろん違うコンテキストでは違う動きになる!
      assert { obj.name == "dummyname1" }
    end
    
    dynamo_define(MyModule::MyClass, :onetime_context) do
      def_method(:name) do
        "onetime"
      end
    end
    
    dynamo_context(:onetime_context) do # 違う! 動きに!!!
      assert { obj.name == "onetime" }
    end
  end
end

もちろんだがダイナミックスコープなので、直接記述されていなくても、与えられたブロックからの呼び出し階層のどこかに存在するメソッドであれば容赦なく書き変わっている。例えば次のコードは MyClass を継承して MyClass2#name を呼んでいるが、親クラスの宣言は :onetime_context 内では書き変わっているので自動的に MyClass2#name の結果も変わる! カッコイイ!!!!!!

module MyModule; class MyClass2 < MyClass; end; end
 
class Synopsis2Test < Test::Unit::TestCase
  def test_onece_more
    dynamo_define(MyModule::MyClass, :onetime_context) do
      def_method(:name) do
        "onetime"
      end
    end
    
    dynamo_context(:onetime_context) do
      assert { MyModule::MyClass2.new.name == "onetime" } # 継承元の書き換えがここにも影響!
    end
  end
end

このような感じで自由の地平が開けます。ぜひどうぞ。

FAQ

なんで作ったの?

RubyKaigiの質問コーナーでdynamic scope欲しい!って言ったらMatzに「dynamic scopeを使わないためにRubyを作っている」と答えられたのでムシャクシャしてやった。

なんで作ったの?

power_assert とかちょっと真面目に使ってみたかった。が、まだちょっとバグいね*3

なんで作ったの?

RubyHirobaでうまい方法ないかなーと思っていたら @_ko1 さんにスレッドローカル変数とModule#prependを使うという方法を授かったのでついやってしまった。責任の半分は @_ko1 さんにあると思う。

本番で使っていい?

絶対にやめましょう。ありとあらゆる不幸が振りかかります。

本番で使っちゃおうかな?

万が一にでも自分が数年後とかに転職した先で見付けた本番コードで使われてたりしたら、たとえ退職者であろうとも書いた人のリアル住所を割って襲撃に行くかもしれません。やめておいた方が賢明です。

ぶっちゃけこれ必要なのって設計が悪いんじゃない?

はい。

例えばマルチスレッドをゴリゴリ使いまくっているのに超大事なところにシングルトンオブジェクトが居座っているFluentdというソフトウェアなんかがあって、そいつがマジでもうどうにもならなかったりするので作りました。しかしFluentd v0.12 ではそのシングルトンオブジェクトが排除されたため不要になりました。おおお……。。。

まあ、しかし、外部gemの動作に依存したコードのテストを書かなければならない時ってあるじゃないですか。ナントカAPIとかナントカSDKみたいなgemを叩きたいんだけどそいつがバックエンドをモックに置き換えられなくなってるとか! あるでしょ!

たのしかった?

はい!!!!!!!!!!!!!! 特異クラスに対してprependとかしてて脳から変な汁が出そうになった!!!

*1:この場合は :mytest_case_default や :mytest_case1 を指定したときだけ

*2:あるいは追加してね!

*3:変なことばっかりやってるテストコードなので再現条件が難しい、が、何がどうfailしたのかがたまに出なかったりした。そのうちちゃんと調べてフィードバックしたい。

「ZooKeeperによる分散システム管理」を読もう、という話

Zookeeperは現代の分散システムに不可欠なミドルウェアで、メタデータの管理、更新通知、リーダー選出といった問題を解決する。自分でZookeeper APIを叩く人は少ないかもしれないが、今やHadoop Namenode HA*1もYARN ResourceManager HAもHBaseもZookeeperを要求する。とにかく使う必要がある、という人は今や多いのではないだろうか。

こういったソフトウェアが何故必要なのか、どういった役割を持つのかについて、明快な回答を返せる人はあまり多くないのではないだろうか。Zookeeperは必要性を喧伝され、あちこちで使われ、しかしいまだに、何台で動かす必要があるのか、どのように運用されなければならないのか、ということをまとめて学べる資料などは多くない。

ということで、この本を読もう。

「2.2.1 Zookeeperのクォラム」。個人的には、自分にとってはこの一節のみでもこの本には価値が十分にあると断言できる。なぜZookeeperは3台以上の奇数ノードで構成するのが良いのか、3ノード構成においてクォラムを2とするのはなぜか。3ノード構成で1ノードに障害が起きても問題が起きない、としてよいのはなぜか。

読もう!

*1:ZKFC、Zookeeper Failover Controller

LINE選抜で isucon4 予選に参加してPerlのコードをごりごり書いた

みなさんtagomorisはPerlなんて書いてねーだろと思うかもしれませんが、意外にちょっとは書くんですよという話……じゃなくて、それはどうでもよくて、今年も ISUCON の季節ですね、という話。

詳しくはこちらをどうぞ。 LINE選抜で isucon4 予選に参加してきました。暫定スコア「51192」を出すためにやったこと - blog.nomadscafe.jp

では済まないので、自分の視点からの話も。

準備

上述エントリのとおり、準備自体はほぼ去年のISUCON3本戦のものを持ち込む形。結果的にほとんど問題なかったと思う。

設計

10時になってからの開始後、サーバ側のセットアップと初期ベンチ実行および結果確認は@kazeburo、コードのバージョン管理まわり等は@sugyanにおまかせしてひと足はやめに詳細が公開されたレギュレーションとアプリケーションコードを読む側に回った。
その前にアプリの動作確認はもちろんやったんだけど、ログインしかできることがなくて面喰らって運営チャットで確認するなど。

今回も1時間以上はほぼ何も手を入れないで内容の確認。
さて、ということでどうするかの相談の第一のお題は、インメモリDBアプリにしたほうが最終的に高得点になるのが明らかだけどどうしましょうかね、という……。選抜チーム枠をもってることだし、本戦の練習のつもりで本戦で使えるアーキテクチャだけでやりますか、ということにした。まあそれでも予選通過できるくらいの点数は出せるだろう、と。*1

テーブルはふたつしかなくて users と login_log のみ。usersは更新が無いしそんな大した量でもないので全部メモリに読んじゃえばいいんじゃね、ということで、そのうち手が空いたらやることに*2

問題は login_log で、以下の制約をクリアする必要があった。

  • 割と面倒なSQLで状態を毎回計算している
  • しかも状態を計算するのに /login と /report で異なるSQLを用いていて、整合性がとれてるかどうかが難しい
  • 初期状態である程度のログが存在するので、データストアを変えるならこれを再現する必要がある
  • /report を出力するためにはデータストアを変える際の設計に注意が必要

さあどうしようかと相談をして、3人であれこれ検討した結果、以下のように。

  1. 一度 ban/lock された状態から復活することは無いようだ
  2. memcached で ip/user ごとにカウンタを持っておいて、そいつをincrすればいいんじゃないかな
    • そのカウントが閾値までいったら ban/lock すればいい
    • チェックは get だけで済む
  3. ban/lock されたらincrと同時にその ip/user をファイルに追記しとこう
    • そしたら /report はそれを全行読み出して json にして返すだけでOK!
  4. ただし初期状態での ip/user ごとのカウンタはmemcached上に用意してやる必要がある
    • 初期化用のスクリプトinit.plを用意してinit.shから起動してやることに
    • 初期データをMySQLにロードしたあと login_log を全行読み出し、1行ずつリプレイすることでオンメモリで状態を全部作る
    • 最後にログ中にあった ip/user の全状態を memcached に add すればOK

これでいけるんじゃないかな、と方針が決まったので、Webアプリ側の改修を@sugyan、初期状態の構築を自分が担当することにしてゴリゴリコードを書きはじめた。

経過

sugyan,tagomorisがコードを書いている間にMySQLにインデックス追加したりnginxの調整をやったりでスコアが一時的にトップに。完全にkazeburoパワーオンリーです。
コードの実装もそれなりにいいペースで進んでたのでそこで気分よくお昼ごはんにした。

Memcachedデータストア版の実装がほぼ終わり、MySQL版のコードにマージする形で両方のデータストアを更新しつつ、参照をちょっとずつMemcachedに寄せていったら、ちらほらとバグが。だいたいはすぐ解決したんだけど /report のチェックでこける問題が長いあいだ解決できなかった。commit log見るとこれに2時間近くかけたらしい。

で、最終的には動くものが揃ったんだけど、これで50000付近のスコアを出した頃には遥かに高いスコアを出しているチームが複数あり、うひー、という状況だったのでした。*3

インメモリDBアプリ

まあこんなもんですかね、というところで落ち着きかけたんだけど、あと30分くらいの時間を手持ち無沙汰に過ごすのもなんだったので、とりあえずやってみるかーとインメモリDBアプリ、シングルプロセス構成のコードに改造してみることにした。

login_log をインメモリにするだけだったのでそう難しいこともなくガッと改造したんだけど、ベンチを走らせてみると通常の構成*4と較べてほとんどスコアが変わらず。高いか低いかは微妙だけど、意味ないなーと思って諦めた。
まあやったのは普通に Kossy でのインメモリDB化なので、生PSGIアプリでゴリゴリ書いたらまた変わったのかも。そんな時間はなかったし、あんまりそこまでやる気もなかった。

ISUCON予選という競技

今回思ったのは、インメモリDB化を試すインセンティブが予選だとありすぎて、これはもう別の競技だなあ、ということ。
ベンチマークツールが同一サーバ上で動いているとどうしてもCPUパワーをベンチかける側と受ける側で食いあうので、最終的にはアプリをCPU1コアで動かし、残りのCPUコア全部をベンチツールに捧げるのが正しい、みたいになってしまう。*5

それはそれで正しいのかもしれないけど、複数サーバを効率的に組合せてハイスコアを叩き出す、という決勝の競技と較べると違いすぎてて、ちょっとなあ、という気持ちが抑えられない。
これはたぶん去年のISUCON3予選問題や今年夏のISUCON夏期講習環境が出回りすぎて、決勝の問題じゃなくて予選の問題の練習をするという方に慣れすぎちゃったんだろうな、という気がする。決勝の問題を環境から準備しようと思うとサーバが5台必要になってしまうので、必然的にそうならざるをえない、のかもしれない。うーん……。

まあ、そういうこと言ってる脇で、fujiwara組がこっちのチームと同程度の得点を叩き出した上でベンチツールの挙動を見越した点数アップを更にやっているので、負け犬の遠吠えかもしれぬ。はい。
あとたぶん、選抜チーム枠を持っていて安穏とした気分で予選に出てるからそういう余計なことを考えてしまうのかもしれなくて、他の参加者からすれば何をトボケたことをと言われることかもしれない。何しろ、いまや決勝出場人数の6倍もの人数が予選を勝ち上がれないんだもんなあ。その人達にとって、ISUCONとは予選のことなのだ。いやはや。

まとめ

何にせよ今回も楽しかったです!
運営の方々はいろいろ苦労があったかと/あるかと思いますが、決勝も非常に楽しみにしてます。予選通過の方々は決勝でお会いしましょう! 我々のチームは出られるのです! HAHAHAHAHAHAHA!*6

*1:結論から言うと、1日目終了時点での結果を見て、ウゲー、甘かったかなと思っていた。

*2:んで手が空いたときにさくっとやった

*3:後から話を見るに、はるか上というスコアのチームはどうもworkloadのバグだったっぽくて、インメモリDBアプリのチームはそんなには上じゃなかったっぽい?

*4:Starletのworker 8とか

*5:あるいはベンチマークツール自体の挙動を推測してそれに対応した修正をやるとか、ベンチマークツール自体のチューンをやるとか……他の参加者のblogエントリ参照のこと。

*6:みんなも主催会社の社員になれbゲフンゲフン