読者です 読者をやめる 読者になる 読者になる

2016年のエンジニアリング振り返り

日記

はてなに入社して3年経った。

3年というのは節目と言われる。働き方や考え方の軸が多少変わってきたように思う。 技術観点では、仕事が少しつまらなくなっていた時期があった。 技術的におもしろいことより、つまらないことのほうが優先度が高くなってしまうというよくあるやつだと思う。 もしくは、おもしろいところまで到達できずに、仕事としては十分な成果になってしまうということもあった。

去年の振り返りに、来年はコードを書くと書いていて、多少はできたものの実感としてはあまりできていない。これからのオペレーションエンジニア/SREは、ソフトウェアエンジニアリングによる問題解決ができないと時代に取り残されてしまうという危機感がある。

技術的挑戦を続けていくためには、自分だけでなく、周囲の環境も変えていかないといけないと思い、マネジメントし始めたり、リーダーシップをとったり、直接的な採用活動をするような局面が増えてきた。 それはそれで自分でやると言ってやっていることだし、悪くはないのだけど、これ自分でやっていればアウトプットもできるのになと思いながら、任せないといけないことも多かった。 慣れないことをやっていると、大したことやってなくても疲れるもので、アウトプット活動が止まってしまった時期もあった。

一時的にそういった期間があることは仕方ない、いつも最高の環境があるわけじゃないので、意識的に環境に作用できるようになれれば、それは自分の強みになると言い聞かせてきた。 最終的には、社内外の期待をかけてくださる方々にいくつかのきっかけをいただいて、来年は技術的に挑戦できる年になるという気持ちで年を越せそうだ。 サーバモニタリング向け時系列データベースの探究 / The study of time-series database for server monitoring // Speaker Deck に書いた設計を自分の代表的プロダクトにすべく、プライベートリポジトリでちまちまとコードに落とし込んでいたりする。 このあたりは、今年大変仲良くしていただいたid:matsumoto_rさんと、あとはMackerelチームのプロデューサーである id:sugiyama88 さんのおかげだ。

振り返ってみると、今年は次の挑戦のための土台づくりに専念した年かもしれない。

成長

内在的な技術的成長は多少はあったものの、アウトプットにつながっていないものは成長とみなさないことにしているので、今年は技術的成長があまりなかったといえる。

あえていえば、実際の運用で導入したKeepalivedのシンタックスチェッカーや今書いているソフトウェアの要素であるGraphiteの式のパーザーなど、これまで書けなかったタイプのコードを書けるようになった。トークナイザーとYACCを書いてるだけはあるけど。

あとは、はてなのこれまでのスタックにはないようなアーキテクチャを構築する機会がいくつかあり、アーキテクチャで解決するという手持ちの札が増えてきたように感じる。

技術以外の成長では、多少マネジメントをしたり、プロジェクト管理みたいなことを覚え始めた。SRE本の第18章"Software Engineering in SRE"にも書かれているが、インフラの領域でも、ソフトウェアエンジニアリングを本格的にやるなら、サービス開発のプロジェクト知見も必要なので、役に立つと思う。周囲の方々の助けもいただいて、多少の手応えもつかめた。

そして、今年一番の収穫は、ストーリーをつくるという意識かもしれない。 ストーリーというとプレゼンのようなものを連想する。もちろんプレゼンも含むのだけど、もっとこう自分の技術の取り組みのストーリーみたいなものを考えるようになった。 普通のエンジニアである僕がなぜか来年に登壇することになったIPSJ-ONEの演題を考えたり、研究会での登壇があったり、あとまだ公開されていない何かのためにまつもとりーさんの論文や口頭発表、ブログを読み返したことが影響している。 博士課程では、複数回のジャーナルを通して、最後にそれらをまとめるということをやるようなので、その手法にも影響を受けている 博士課程の予備審査にいってきました - 人間とウェブの未来, NAKAHARA-LAB.NET 東京大学 中原淳研究室 - 大人の学びを科学する: 博士論文とは「構造を書くこと」である!?。 自分のストーリーがあれば、次に何を勉強するかも決まってくる。 僕のように瞬発力とか手の速さで勝てない凡人は、ひたすら考えて、自分のやってきたこと、これからやることを繋いでいくしかない。

アウトプットまとめ

いつものようにブログやOSS活動を振り返る。

ブログ

いつものブログに加えて、今年から技術メモとしてGitHubにちまちまとメモを残していたりする。

順位 エントリ
1位 Linuxサーバにログインしたらいつもやっているオペレーション - ゆううきブログ
2位 ウェブアプリケーション開発に新言語を採用したときにインフラで考えたこと - ゆううきブログ
3位 Googleが数千台もある10年前のLinuxディストリをライブアップグレードした話 - ゆううきブログ
4位 自作Linuxコンテナの時代 - ゆううきブログ
5位 インフラエンジニアがGo言語でオペレーションツールを書くことについて - Hatena Developer Blog
6位 2015年の心に残った技術エントリ - ゆううきブログ
7位 ISUCON予選突破を支えたオペレーション技術 - ゆううきブログ
8位 2016年ウェブオペレーションエンジニアの新卒研修 - Hatena Developer Blog
9位 情報処理学会でウェブオペレーション技術について招待講演した話 - ゆううきブログ
10位 2016年はてなWebオペレーションエンジニアのアウトプット - Hatena Developer Blog
11位 ウェブオペレーションエンジニアになるまでの思い出 - ゆううきブログ
12位 リモートワークにおけるSlack Call活用と終業15分前の雑談 - Hatena Developer Blog
13位 nginxのステータスコード444 · yuuki/yuuki
14位 nginxのステータスコード499 · yuuki/yuuki
15位 Keepalivedのシンタックスチェッカ「gokc」を作った - ゆううきブログ
16位 リバースプロキシのコンフィグテスト

合計 8000 users+くらい。

発表

合計8件。

OSS

f:id:y_uuki:20161231215324p:plain

Graphiteへのパフォーマンス改善PR。

go-sql-driver/mysqlのMySQL 4.0対応をして、MySQLプロトコルの知見を深めたりした。 https://github.com/yuuki/go-sql-driver-mysql

他には、ISUCON6予選の出題側としてGo実装とインフラまわりの手伝いをやったりした。

まつもとりーさんの振り返り にも書いていただいているように、今年はペパボさんのエンジニアの方々と交流させていただくことが多く、大変刺激になりました。 はてなの技術力が非常に高いと称していただいていますが、僕としてはどちらかというと我々はまだまだだなあと思っていて、ペパボさんの取り組みは、前へ進む勢いを感じさせられるというか、一本筋が通ってみえるというかそんな気がしています。 我々はまだ場当たり的に問題を解決していってて、このままではいけないと思い、はてなシステムについて考えてたりしている。

あとがき

他のエンジニアの方々の振り返りを眺めていて、たぶん自分より若い人の台頭がすごいなあと感心している。

悔しいので、負けないようにがんばろう。

今年もお世話になりました。来年もよろしくお願いします。

ウェブオペレーションエンジニアになるまでの思い出

日記

書籍「ウェブオペレーション」の中で、「ウェブオペレーションは技芸であり科学ではない」*1という言葉がある。 実際、その通りだと思う。 しかし、技芸というのはどうやって学べばよいのか。 教科書のようなトップダウンな知識体系を構築しようと試みようとしても、どうしても特定の組織に依存したり、特定の技術スタックに依存してしまう。

現時点では、体系立てて学ぶというより、やはりボトムアップに学ぶしかないと考えている。 「ウェブオペレーション」の内容も、基本はストーリー仕立てのエッセイ集になっているのは、そういうことだろう。

Hatena Engineer Seminar #7では、もともとウェブオペレーションの学び方の話をしようと思っていたが、前述のような事情で、自分(id:y_uuki)の場合の学んできたことを例として挙げることにした。 ウェブオペレーションエンジニアの前提となるスキルセットの作り方の一例として紹介する。LT資料なので勢い重視なのは許してほしい。

大学1年からLinuxを触り始めたときからはじまり、C言語を学び、ネットワークプログラミングを学び、アルバイトで巨大なCのプロジェクトをgdbを駆使してトレースしたり、はてなインターンにいったり、研究でSIMDとかGPUとかネットワークスタックとか触ってたり、はてなアルバイトでMackerelの前身を作ったり、子どものころを振り返ってみてシステムっぽいものが好きだったり、といった内容の話をした。

ウェブオペレーションエンジニアになった動機の一つに、教科書に書いてあるような古典的な知識が現場で活用されていることがおもしろいと感じていたことがある。 例えば、書籍「大規模サービス技術入門」*2では、MySQL(MyISAM)のテーブルファイルをあらかじめcatで読ませて、OSのページキャッシュにのせることで暖機運転がわりにする手法に感心したのを今でもよく覚えている。 このような考え方は、自分の技術スキルの方向性にも影響しており、2015年Webサーバアーキテクチャ序論 - ゆううきブログ自作Linuxコンテナの時代 - ゆううきブログ などに表れている。

わざわざ語るような大層な経験はなく、ごく平凡な来歴だなと思う一方で、アウトプットだけは人並み以上にやってきたという自負がある。ちまちましたことでも続けていれば、それなりの何かにはなるものだ。 もし今回の話に共感するところがあり、ウェブオペレーションやSREという技術領域に興味をもってもらえたなら、うれしく思う。

ちなみに僕の場合は、自然科学実験みたいな物理作業をとにかくやりたくなくて情報科学科に入ったので、「モノ」としてのサーバやネットワーク機器にあまり興味がないけど、なんとかやっていけている。

あわせてよみたい

同僚にはこういう変な人もいる。

*1:John Allspaw、Jesse Robbins編、角 征典訳,ウェブオペレーションーーサイト運用管理の実践テクニック,オライリージャパン

*2:伊藤 直也、田中 慎司,[Web開発者のための]大規模サービス技術入門 ―データ構造、メモリ、OS、DB、サーバ/インフラ (WEB+DB PRESS plusシリーズ), 技術評論社

情報処理学会でウェブオペレーション技術について招待講演した話

Monitoring TSDB 日記 Architecture

情報処理学会インターネットと運用技術研究会が主催されているIOTS2016という研究会で、「サーバモニタリング向け時系列データベースの探究」というタイトルで招待講演をしてきました。

講演のきっかけ

インターネットと運用技術研究会(以下IOT研究会)というのは僕にとっては id:matsumoto_r さんが所属されている研究会です。 matsumotoryさんが、ちょうど2年前のアドベントカレンダーで書いた僕の記事に日本語だとIPSJのIOTは分野的にもインターネットの運用技術が含まれるので興味深い論文が沢山あると思う とコメントしていただいたのが最初に研究会の存在を知るきっかけだったと思います。 そのときはそんなものもあるのかと思ってちょっとプログラムを眺めた程度でした。 しかし、まさかその2年後にこうして招待していただくことになるとはもちろん思っていませんでした。 id:MIZZYさんがserverspecの論文をだされた研究会でもあります。

きっかけは、今年の6月ごろです。 やはりmatsumotoryさんに講演とは別件のとあるびっくり無茶振りを受けました。 そして、少し前にIOT研究会へいくつかの資料を提出したところ、次のIOTS2016で講演してくれという話をいただきました。 IOTSがどんなものかわかってなかったのですが、査読付きで1年で1番大きな研究会であり、matsumotoryさんがそこで招待講演できるの羨ましいとおっしゃっていたので、じゃあやります、と答えました。

講演内容

講演するにあたって、当たり前のことですが、まず伝えたいことを考えました。 IOTS2016の開催案内をみてみると、「運用でカバーする」、「運用担当者の人柱の上に成立する前時代的で野蛮な構造物」、「不快な卑しい仕事をやる必要がなくなるのは、人間にとってひじょうな福祉かもしれないが、あるいはそうでないかもしれない」といったなかなか興味深いセンテンスが散りばめられていることに気づきました。 研究者と技術者という、コンテキストが異なるものの、同じ運用技術に携わっているという前提があります。 そこで、ウェブサービスの運用の世界で実際に起きていることと生の課題を伝えようと思いました。

これを伝えるための話作りには、いくつかの案がありました。 ひとつは、そのまま「ウェブサービスの運用の世界」といったタイトルで、ウェブオペレーションを概観する話をするというもの、もうひとつは、時系列DBやコンテナ型仮想化など特定の技術の話をするというものです。

最終的には後者にしました。 ちょうど時系列DBのアーキテクチャ刷新に取り組んでいるため、成長するサービスとスケーラビリティといういかにもウェブオペレーション技術っぽい話ができます。 自分がまだ大学にいた頃から取り組んでいたことであり、時系列DBの話をすることは自分がこれまでがんばってきた技術の話をすることにもなります。 さらに、研究会なのだから、論文にできそうなテーマならなおよいはずと思って、最新の取り組みを紹介したかったというのもあります。

だいたい話すことは決まっていたものの、ストーリー化することには苦労していて、2日くらい前にインフラチームの同僚何人かに見せたら反応が芳しくなかったので、同僚の意見を取り入れて、大幅にスライドを書きかえました。 無意識に研究発表的なスライドっぽくつくってしまっていた気がします。 トップダウン視点ではなく、自分視点に置き換えていって、アーキテクチャの説明はすべて図にしました。 会社としてというよりは個人として招待されたと思っているので、結果として自分視点になってよかったと思います。

matsumotoryさんには、見事に上記の意図をすべて汲み取っていただきました。

講演の導入には、まず異なるコンテキストのすり合わせをしようと思って、開催案内を引用させていただきました。 不快な卑しい仕事というのは、書籍Site Reliability Engineeringに書かれている"toil"に相当し、Googleでさえtoilからは逃れられていないが、toilではないエンジニアリングに価値を置いていると伝えました。 toilを消すことは、最近のエンジニアのコンテキストにおいても重要であり、そのような研究活動には価値があるということを暗にお伝えしたつもりです。

発表スライド

発表スライドを以下に貼っておきます。 あとで読む資料としてはまとまりがない点についてはご容赦ください。

若いと言われるが実際若くて平成生まれとか、IOTSの開催案内を会社の全体朝会で紹介したら社員のみんなが大喜びだったとか、アルバイト時代になんか癒やしとか言われてだまされてRRDtoolのCPANモジュールつくってたとか、入社するころにはRRDtoolは若者にはふさわしくないことがわかってきたとか、いつものように調子よくしゃべってたら笑いもとれたので、話をしていて楽しかったです。 以下のスライドがハイライトです。

講演を終えて

僕のことをご存知の方は、3年前に大学院を中退したことを覚えておられるかもしれません。 単に特定の環境とあわなかっただけというだけかもしれません。しかし、自身の体験だけでなく、社会人になってからも、大学の研究室で苦しんでいる学生たちの声を何度か聞くこともありました。 このこともあって、研究活動そのものはともかくとして、大学や研究室といった環境に強いネガティブな感情を今だにもっています。

今回の件は、大学から離れ、現場のエンジニアとしてがんばってきた成果をアカデミックな場で話すよい機会でした。 幸いなことに、思った以上に「話が通じる」という感覚を得られ、うれしく思いました。 もちろん、何を言ってるのかさっぱりだと思われた方も少なくはないでしょうが。 他の発表でもAmazon RedshiftとかKibana、Graphana、Elasticsearchなど見慣れた単語がとびかっていたので、安心感があります。

一方で、大学関係のシステムの運用を対象とした話がやはり多く、それらについてそれほど興味があるわけではありませんでしたが、おもしろく聴けた発表がいくつかありました。 特に柏崎先生の発表はとにかくいきおいのあるスライドというかこれは本当に学会発表か?と思うほどでした。 @hirolovesbeer さんの異常検知の話は質問したところ、イベントネットワーク以外にもサーバのログの異常検知にも使える可能性があるとのこと。

これ使えるなーと思ったものが論文優秀賞をとられていて、エンジニアの視点でよいものがちゃんと評価されるんだなと感じました。

ウェブオペレーションやSREに関する学術研究発表があればもっとおもしろく感じるだろうなと思います。 というのは、最近のウェブエンジニア界隈を眺めていて、それは本当に有用なのか、新しいおもちゃを使わされているだけになっているんじゃないかと疑問に思うことが増えてきて、真に有用な技術ってなんだろうなと考えたりすることがあるからです。

そういった考え方、アカデミックなアプローチで技術をつくるという方向性は自分の課題意識とマッチしているのではと思うことはあります。 博士過程へのお誘いもいただいたりしたのですが、前述のネガティブなイメージを払拭するのはすぐには難しいですね。 そもそも生半可な覚悟では社会人で博士号取得なんてできるわけないので、今後、「おもしろそう」「すごいことをやっていそう」「成長できそう」といった強いポジティブなイメージに転換できるかどうかが鍵になると思っています。

帰り際に、あのゆううきブログの中の人ですよね?と話かけていただいて、大学の中の人にまでリーチしているのかとちょっとびっくりしました。 大学を去ったその後に、大学の中の人に影響を及ぼしているというのは不思議な気分ですね。

この記事は、はてなエンジニアアドベントカレンダー2016の4日目の記事です。昨日は id:wtatsuru によるセキュリティ会の取り組みでした。 明日の担当は、buildersconで発表してきたばかりの id:shiba_yu36 です。

ISUCON予選突破を支えたオペレーション技術

ISUCON Performance

ISUCONに参加する会社の同僚を応援するために、ISUCONの予選突破する上で必要なオペレーション技術を紹介します。 自分がISUCONに初出場したときに知りたかったことを意識して書いてみました。 一応、過去2回予選突破した経験があるので、それなりには参考になると思います。 といっても、中身は至って標準的な内容です。 特に、チームにオペレーションエンジニアがいない場合、役に立つと思います。

今年のISUCON6は開催間近で、まだ予選登録受付中です。

※ 文中の設定ファイルなどはバージョンやその他の環境が異なると動かなかったりするので必ず検証してから使用してください。

ISUCONでやること (Goal)

ISUCONでやることは、与えられたウェブアプリケーションをとにかく高速化することだけです。 高速化と一口に言っても、複数のゴールがあります。ウェブアプリケーションの場合は以下のようなものでしょう。

  • レスポンスタイムが小さい
  • スループット (req/s) が大きい
  • CPUやメモリなどリソース消費量が小さい

ISUCONでは、基本的にはレスポンスタイムを小さくすることを目指します。 これは実際のウェブアプリケーションにおいても、ユーザ体験に最も直結するレスポンスタイムを改善することが重要なので理にかなっています。

とはいっても、リトルの法則により、安定した系において、レスポンスタイムが小さくなれば、スループットは向上するため、レスポンスタイムとスループットは相関します。

リソース消費量の改善は、レスポンスタイムに寄与するというよりは、サーバ管理にまつわる人的または金銭的なコストを下げることに寄与します。 例えばメモリが余っているのに、メモリ使用量を削減しても、レスポンスタイムには影響しません。ただし、そういった無駄を省くことで、アプリケーションの処理効率がよくなり、結果としてレスポンスタイムが良くなることはあります。

ISUCONは、具体的には、以下のような様子で構成されています。

  • サーバを含む環境構築
  • OS・ミドルウェアの選択とチューニング
  • アプリケーションロジックとデータ構造の改善

ここでは、前者2つをオペレーションの領域、後者をプログラミングの領域とします。 必ずしも、オペレーション要員が必要ということはなく、あくまで領域なので、分担することも多いと思います。 自分の場合、過去2回とも、チームの構成上、オペレーションまわりはほぼ全部1人でやっていました。

ISUCONの考え方 (Principles)

自分が考えるISUCONの原則は、「オペレーション(System Engineering)で点を守り、 プログラミング(Software Engineering)で点をとる」です。

OS・ミドルウェアのチューニングが劇的な加点要素になることはあまりありません。 そのレイヤのチューニングはリソース消費量を小さくすることに寄与することが多いためです。

ただし、チューニングしていないために、劇的に性能が劣化することはあります。 例えば、InnoDBのバッファプールサイズを小さくしていると、データがメモリに乗り切らず、大量のディスクI/Oが発生し、スコアが大きく下がるはずです。

もちろん、アプリケーションロジックが薄いとそれだけOSやミドルウェアが仕事をする割合が大きくなるため、 OSやミドルウェアのチューニングによりスコアが伸びることはあります。

そうはいっても、基本的にスコアを伸ばす手段は、アプリケーションロジックとデータ構造の改善です。 これは実際のウェブアプリケーション開発の現場でも同じことが言えます。 雑な体感によると、パフォーマンスの支配率は、アプリケーションが8割、OS・ミドルウェアが2割程度(要出典)だと思っています。

ISUCONにおけるオペレーション

環境構築

予選の場合は、クラウド環境セットアップが必要です。ISUCON3、ISUCON4ではAWS、ISUCON5ではGCE、ISUCON6はAzureです。 アカウント作成やインスタンス作成がメインです。 当日のレギュレーションにも一応手順は記載されるはずですが、事前に触っておくと本番で混乱せずに済むと思います。

サーバログイン環境

例年であれば、/home/isucon 以下に必要なアプリケーション一式が入っています。

isuconユーザがもしなければ (useradd -d /home/isucon -m isucon) などで作成します。 さらに、/home/isucon/.ssh 以下に公開鍵認証するための公開鍵を設置します。 /home/isucon/.ssh/authorized_keysファイルを作成し、.sshディレクトリのパーミッションは700、authorized_keysファイルのパーミッションは600であることを確認します。(この辺のパーミッションがおかしいとログインできない) メンバー分のユーザを作成するのは面倒なので、共通ユーザ1つだけで問題ないと思います。

デプロイ自動化

Capistranoのような大げさなものは使わなくて良いので、以下のような雑スクリプトでデプロイを自動化しましょう。

やっていることはSlackへ開始デプロイ通知、git pull、MySQL、Redis、memcached、app、nginxの再起動、Slackへデプロイ完了通知ぐらいです。 MySQLやnginxもいれているのは、設定ファイルを更新したはいいが、再起動をし忘れるということがよくあるので、まとめて再起動してしまいます。 接続先のプロセスがしぬと、うまく再接続されない場合もなくはないので、再起動するのは原則バックエンドからが一応よいです。

リソース利用率把握とログの確認

ここでいうリソースとはCPU、メモリ、ディスクI/O、ネットワーク帯域などのハードウェアリソースを指します。 要はtopの見方を知っておきましょうということです。以前ブログに書いたので、参照してください。

冒頭でISUCONではレスポンスタイムを小さくするのがよいと述べました。とはいえ、リソース消費量を把握しておくことはボトルネック特定のヒントになるので重要です。 例えば、予選問題の初期状態ではMySQLのCPU負荷が支配的であることが多いかつディスクI/Oが高ければ、まずMySQLのバッファプールを増やしたり、クエリ改善が必要そうという程度のことはすぐわかります。 ISUCON4で話題になったCache-Controlの件も、ネットワーク帯域が限界であることにもっと早く気づいていれば、何かしら手は打てたかもと思います。(当時は思い込みでそこでボトルネックになってるとは思っていなかった。)

監視

去年のISUCONでは、自分たちのチームではMackerelの外形監視を使ってみました。 競技中に、アプリケーションのsyntax errorか何かで500でてるのに、ベンチマーカーを走らせてしまい、時間を無駄にしてしまうことがあります。 インターネット経由でポート80番が疎通できてさえいれば、アプリケーションが落ちていると、Mackerelの外形監視がすぐに通知してくれます。

MySQLデータサイズ確認

MySQLのデータサイズとして、テーブルごとのサイズや行数などを把握しておくと、クエリ改善の参考にできる。 行数が小さければ、多少クエリが悪くてもスコアへの影響は小さいため、後回しにできる。

mysql> use database;
mysql> SELECT table_name, engine, table_rows, avg_row_length, floor((data_length+index_length)/1024/1024) as allMB, floor((data_length)/1024/1024) as dMB, floor((index_length)/1024/1024) as iMB FROM information_schema.tables WHERE table_schema=database() ORDER BY (data_length+index_length) DESC;

アクセスログの解析

proxyでアクセスログを解析することにより、URLごとのリクエスト数やレスポンスタイムを集計できます。

解析には、tkuchikiさんのalpが便利です。 自分の場合は、適当な自作スクリプトを使っていました。https://gist.github.com/yuuki/129983ab4b02e3a646ad

isucon@isucon01:~$ sudo parse_axslog isucon5.access_log.tsv taken_sec
req:GET / HTTP/1.1 taken_sum:474.08 req_count:714 avg_taken:0.66
req:GET /footprints HTTP/1.1 taken_sum:58.378 req_count:198 avg_taken:0.29
req:GET /friends HTTP/1.1 taken_sum:27.047 req_count:238 avg_taken:0.11
req:POST /diary/entry HTTP/1.1 taken_sum:6.51 req_count:195 avg_taken:0.03
…

nginxでLTSVによるアクセスログをだすには、以下のような設定を去年は使いました。

log_format tsv_isucon5  "time:$time_local"
 "\thost:$remote_addr"
 "\tvhost:$host"
 "\tforwardedfor:$http_x_forwarded_for"
 "\treq:$request"
 "\tstatus:$status"
 "\tsize:$body_bytes_sent"
 "\treferer:$http_referer"
 "\tua:$http_user_agent"
 "\ttaken_sec:$request_time"
 "\tcache:$upstream_http_x_cache"
 "\truntime:$upstream_http_x_runtime"
 "\tupstream:$upstream_addr"
 "\tupstream_status:$upstream_status"
 "\trequest_length:$request_length"
 "\tbytes_sent:$bytes_sent"
 ;
access_log /var/log/nginx/isucon5.access_log.tsv tsv_isucon5;

MySQLスロークエリログの解析

MySQLのスロークエリログは、実行時間が閾値以上のクエリのログを吐いてくれるというものです。 最初は、long_query_time = 0 にして、全クエリのログをとります。もちろん、ログ採取のためのオーバヘッドはありますが、最終的にオフにすればよいです。

slow_query_log                = 1
slow_query_log_file           = /var/lib/mysql/mysqld-slow.log
long_query_time               = 0
log-queries-not-using-indexes = 1

pt-query-digestで集計するのが便利です。 https://gist.github.com/yuuki/aef3b7c91f23d1f02aaa266ebe858383

計測環境の整備

ログの解析については、前回のベンチマークによるログを対象に含めたくないため、ベンチマーク前に過去のログを退避させるようにします。

設定ファイルを変更したまま、プロセスの再起動を忘れることがあるので、ベンチマーク前に各プロセスを念の為に再起動させます。

チューニング

MySQL、nginx、redis、memcachedについては、特殊なお題でないかぎり、kazeburoさんのエントリの通りでよいと思います。 比較的スコアに効きやすいのは、静的ファイルのproxy配信、UNIXドメインソケット化あたりかなと思っています。

ただし、sysctl.confの設定内容反映については、Systemd バージョン 207と21xで注意が必要です。 具体的には、/etc/sysctl.conf が読まれず、/etc/sysctl.d/*.conf または /usr/lib/sysctl.d/*.conf が読まれるようです。 sysctl - ArchWiki

アプリケーションの把握

オペレーションばかりやってると意外と、何のアプリケーションか知らずに作業しているということがあります。 特に序盤は定例作業が多いので、一息つくまでにそれなりに時間をとられます。 その間に、アプリケーション担当が把握していて、把握していることが前提になるので、会話についていけないこともあります。

そこで、以下のようなことを最初に全員でやって認識を合わせるといった工夫をしたほうがよいです。

  • ウェブサービスとしてブラウザからひと通りの導線を踏んでみる
  • テーブルスキーマをみる
  • MySQLのコンソールに入ってデータの中身をみてみる
  • コードを読む

競技終了前のチェック

競技終了前になにか見落としがないかチェックします。

  • ディスクサイズに余裕があるか。運営サイドでベンチマークを回す時にログ出力でディスクがうまることがないとも限らない。ログを吐きまくってると意外とディスクに余裕がなくなっていたりするため、最後に確認する。
  • ページの表示は正常か。CSSがなぜかあたってないとかないか。
  • ログの出力を切る。アクセスログやスロークエリログ。
  • (厳密には競技終了30分〜60分の間ぐらい) OSごと再起動してもベンチマークが通るかどうか

参考文献

kazeburoさんの資料が非常に参考になります。何度も読みました。

昨年の予選のコードと設定ファイルは公開しています。https://github.com/yuuki/isucon5-qualifier 昨年の様子です。ISUCON 5予選で5位通過した話 - ゆううきブログ

発表資料

この記事は、京都.なんか#2 で発表した内容を元にしています。 準備・運営ありがとうございました! > id:hakobe932 / id:hitode909

どの発表もおもしろかったけど、個人的にはkizkohさんのRust&netmapの話がよくて、netmapは昔論文読んだりしたものの、全然使っていなかったので、実験的とはいえ実際にコード書いて動かしているのがとてもよいなと思いました。 というかこんなところで、netmapとかdpdkの話がでてくるのかとびっくりしました。

あとがき

今年もはてなから何チームか出場するとのことだったので、知見共有のために、これまでのISUCON出場経験をまとめてみました。

ISUCON当日に調べながらやっていては間に合わないので、今のうちに練習しておくことをおすすめします。 さらに、全体を把握するために、過去の問題を一人で解いてみることもおすすめします。

おもしろいのは、多少ISUCON特有のノウハウは混じっているものも、実際の現場でやっているようなことがほとんどだということです。 昨今では、ウェブアプリケーションが複雑化した結果、高速化の余地があったとしても、大きすぎる問題を相手にすることが多いため、ISUCONは手頃な環境で経験を積むにはもってこいの題材です。

さらに本戦出場して惨敗すると、人権をロストしたり、ヒカリエの塔からハロウィンでめちゃくちゃになった街に突き落とされるような体験が待っています。

Googleが数千台もある10年前のLinuxディストリをライブアップグレードした話

Linux 論文

Googleが、太古のディストリビューションであるRed Hat 7.1から、10年新しいDebianベースのディストリビューションへ、ライブアップグレードした話を紹介する。 そのあと、自分の身の回りの環境と比較し、参考にすべきポイントを考察する。

原文は USENIX LISA の投稿論文だ。しかし、中身は論文体というよりは、事例の紹介といった適切かもしれない。

MERLIN, M. Live Upgrading Thousands of Servers from an Ancient Red Hat Distribution to 10 Year Newer Debian Based One. In Proceedings of the 27th conference on Large Installation System Administration (LISA) (2013), pp. 105–114

ペーパーのPDFが Live Upgrading Thousands of Servers from an Ancient Red Hat Distribution to 10 Year Newer Debian Based One | USENIX にて公開されている。 LinuxCon Japan 2013で、同テーマの発表があり、発表資料は http://marc.merlins.org/linux/talks/ProdNG-LC2013-JP/ProdNG.pdf にて公開されている。

この文章は、原文の邦訳では決してなく、自分が理解しやすいように組み立てなおした文章であるため、誤りないし誤解を生む可能性がある。正しい記述を知りたいのであれば、原文を参照してほしい。

概要

Googleでは太古のディストリであるRed Hat 7.1を独自パッチを重ねて10年間運用してきた。

Googleのサーバでは、アプリケーションはルートパーティションの外側のchroot jail環境で動作するため、ルートパーティションの変更がアプリケーションに影響しない。 これまで、Red Hat 7.1のゴールデンイメージのファイルをルートパーティションへ同期することにより、パッケージを更新してきた。

この方法は長い間驚くほどうまくいっていた。しかし、永久にアップグレードを延期するわけにはいかない。実際、モダンなシステム上でパッケージをビルドできないなどの問題があった。

そこで、DebianベースのProdNGという新しいディストリビューションへ移行することにした。 ProdNGは以下のような特徴をもつ。

  • セルフホスティング
  • ソースから完全にリビルドする
  • すべてのパッケージは不要な依存を取り除かれる (xml2, seLinux library, libacl2など)
  • upstart、dbus、plymouthなど複雑なものを取り入れない
  • 小さければそれだけ同期や再インストール、fsckが速くなる
  • 新しいパッケージが常に優れているわけではない。ときには古いもののほうがよいこともある
  • 隔離されている。新しいパッケージをビルドするたびに、ProdNGのchroot jail環境を作り、ビルドツールをインストールする

ProdNGへの移行の手順は以下のようなものだ。これらはOSの再起動なしに実施されている。

  1. ProdNG上でビルドしたDebianパッケージをRPMへ変換する
  2. 現行のRed HatディストリからX Serverやfonts、localesやman pageのような不要なパッケージの削除
  3. 現行ディストリのlibc 2.2.2から、ProdNGのlibc 2.3.6へのアップグレード
  4. chroot jail環境でスクラッチからビルドされた約150パッケージのアップグレード ProdNGと現行Red Hatイメージの両方に配置
  5. 現行Red HatイメージにしかないRPMをdebパッケージに変換 alien(1)と独自のchangelogコンバータの組み合わせ
  6. ここまでで、ProdNGが現行Red Hatイメージと同様に動作するようになったため、リグレッションテストをパスして、手動レビューしたのちにProdNGをデプロイする

これらの手順は、一言でいうと、本番環境で2つのディストリを並行して運用することなく、常に均一な状態を保って、新しいディストリに移行している。各ステップで数千台のホストを均一な状態に保つことは、非常に手間がかかり、全工程を数年かけてやり遂げた。

トピック

前節でペーパーの概要をまとめた。ここでは、概要から省いた、いくつかの興味深いトピックを紹介する。 これらのトピックに加えて、実際の移行時のトラブルや泥臭いlibcアップグレード方法など、興味深いトピックはいくつかある。

前提環境

Googleの本番Linuxはカーネル・デバイスドライバ、ユーザスペース、アプリケーションの3レイヤで管理されている。

カーネル・デバイスドライバはディストリビューションからは切り離されていて、頻繁に更新される。 このレイヤは別のチームがメンテナンスしており、今回の話には関係がない。

各アプリケーションはchroot jail環境で動作する。このjailはアプリケーションの動作に必要なすべてのプログラム、共有ライブラリ、データファイルを含む。

残りのユーザスペース部分は、initスクリプト、/usr/binバイナリなどだ。 OSのパッケージシステムはこの部分にのみ使われる。ペーパーでフォーカスしているのはこの部分になる。 1~2人でメンテナンスされているらしい。

(※ペーパーのタイトルに数千台のサーバとあるが、Googleのサーバはもっと多いはず。どのセクションのサーバの話なのかは書かれていなかった。)

従来のパッケージアップデート

最初期には、sshループでインストール・アップデートコマンドを走らせていた。 sshループは実行に時間がかかり、失敗するホストもあり、スケールしない。 一般的に、プッシュベースの手法は失敗するように運命付けられている。

次に、cronでapt-get/yumを実行した。だいたい期待した更新はできた。 しかし、数千台のサーバでapt-get/dpkg/rpm/yumを走らせていると、ランダムで失敗することがある。 更新中に再起動やクラッシュすると、パッケージデータベースが壊れた。 パッケージデータベースが壊れなくても、設定ファイルがコンフリクトしたり、パッケージアップデートによりホストを予期しない状態になったりなどの多くの問題があった。

ファイルレベルのファイルシステム同期ならば、どの状態からでもリカバーできて、パッケージマネージャーに依存しなくなる。 各サーバを均一に保ち、サーバ固有のパッケージと設定ファイルは同期するエリアの外に置く必要がある。 各サーバはサーバ固有ファイルのリスト(ネットワーク設定やresolve.conf、syslogなど)を持ち、リスト内のファイルは同期からは除外される。 ホスト全体にマスタイメージからrsyncするのはサーバ側(※おそらくマスタイメージを保有する中央サーバ)がスケールしない。 さらに、特定のファイルが変更されたときに、デーモンが再起動するためのトリガーが必要だ。

そのため、独自のrsyncライクなソフトウェアを書いた。基本的に、マスタイメージから全ホストへファイルレベル同期を行う。適切にシェルトリガーを設定できる。I/Oはスロットリングされるため、各ホストの負荷に影響はない。 (※ rsyncライクなソフトウェアとは、おそらく、多数のサーバへ同期をかけられるように、クライアント側を効率化したようなものではないか。各ホストでサーバソフトウェアが起動していて、マスタイメージを保有するホスト上のクライアントプロセスが各サーバへ接続し、ファイル同期を行う。これをrsyncでやると、1つのサーバに対して1つのクライアントプロセスを毎回起動することになり効率が悪い。)

ただし、この手法は各ホストが均一な状態である必要がある。前述のようにルートパーティション外のchroot jailにアプリケーションをデプロイしているからこそできるといえる。

このファイルレベルの同期はProdNGディストリの更新にも用いらている(はず)。ProdNGの場合は、マスターイメージがパッチを重ねたものではなく、毎回クリーンな環境でスクラッチでビルドされたイメージとなる。

どのディストリビューションがよいか

Debianは標準のRed Hatに比べて、多くのパッケージをもつ。(昔のRed Hat 9が1500パッケージで、当時のDebianが15000パッケージ。今日のFedore Core 18が、13500パッケージに対してDebian Testingが40000パッケージ。) Ubuntuは、upstartやplymouthのようなオプションでもなく、信頼もできないいくつかの複雑さを強制される。

ディストリのInitシステムについても、以下のような議論がなされている。

  • Sysv: /etc/rcX.d/Sxxdaemonのようなシーケンシャルなブートがわかりやすい。しかし、遅い。
  • Upstart: シンタックスが完全に異なる(シェルよりはよい)。ブート順の保証がない。何かがおかしかったらデッドロックすることがある。場合によっては、upstartは再起動を要求する状態に陥ることがある。デバッグが難しい。
  • Systemd: 大きな混乱をもたらす。Linuxシステムのブートの大きな再設計。Linuxの低レベルな多くのコア部分を置き換える。自動で依存関係を計算してくれるという理想に反して、手動な依存関係の解決が要求される。Upstart同様にブート順を指定できない。
  • Insserv: 再起動前に、insservは指定された依存を解析して、initスクリプトをS10やS20のようにリネームする。S10以下のすべてのスクリプトは同時に起動し、S20はS10xxのすべてが起動してから起動する。依存関係の可視化とレビューが簡単。ProdNGではこれが採用された。

はてなとの比較と考察

はてなの本番環境のOSはほぼすべてLinuxで、ディストリはCentOS5とDebianが混在している。 数年前からDebianに移行し始めたため、最近のサービスはすべてDebian上で動作している。

とはいえ、まだまだCentOS5のホストが数多く残っており、来年3月のEOLに向けて、今まさに撤退中というステータスだ。

レガシーOSを数多く抱えているという状況は、ペーパーに書かれていたGoogleの状況に近い。 ホスト数のオーダーも同じだ。

大きく異なるのは、レイヤごとに分離していないことだ。(というより一般的には、分離していない環境が多いと思う) はてなの環境では、カーネル、ユーザスペース、アプリケーションが密結合している。 OSをアップグレードしようと思うと、これらすべての変更を意識しないといけない。 特にApache + mod_perl と古いCPANモジュールを新しいOSで動作させるのが課題になっている。

レガシーOSの撤退と一口にいっても、環境により課題は異なり、Googleの事例は特殊にみえる。 どちらかというと、アップグレードの手法よりも、レイヤ別にOSを管理する手法を参考にしたい。 毎回EOLが迫るたびに、すべてをアップグレードするのは現実的とはいえない。

以前に、chrootベースのアプリケーションデプロイを提案 した。 アプリケーションを隔離するという方向性は間違ってないと思う。

マスターイメージのファイル同期による管理は、ChefやAnsibleのようなサーバ構成管理ツールで管理する手法とは大きく異なる。 サーバ構成管理ツールにより、プログラマブルに柔軟な構成ができる一方で、複雑化しやすく、10年もつ仕組みとは言い難い。

そこで、ルートパーティションのみ、ファイル同期で更新して、アプリケーションはchroot jailのようなコンテナで動かす。この手法が、自分の中で長期の運用に耐えやすい手法なのではないかと思えてきた。

あとがき

Googleでさえ、太古のディストリビューションのメンテナンスに苦しんでいる。そして、解決するためにかなり泥臭い方法を選択しているというのは、サーバの構成管理の本質的な難しさが現れているのかもしれない。

手法自体は泥臭い一方で、できるかどうかわからなかったライブアップグレードする技術力があるのはさすがで、既存のディストリにそのまま依存すべきではなくProdNGという新しいディストリをデザインするところもさすがといえる。

やりたいことを実現できる技術力と、長期運用するためにディストリはどうあるべきなのかというビジョンと、レガシーな環境を技術で解決するぞというやっていく気持ちを感じられた。

はてなでは、技術で問題を解決していく気持ちをもったエンジニアを募集しています。

さらに2016年のインターンも募集しています。

自作Linuxコンテナの時代

Linux Docker Container

最近、Docker以外のコンテナ型仮想化技術の流れとして、自作コンテナエンジンの時代が来るのではないかと感じている。

自作コンテナエンジンとは、コンテナ型仮想化技術を構成する個々の要素技術を組み合わせ、自分の用途にあわせて最適化したコンテナエンジンのことだ。 他のOSのコンテナ仮想化技術について疎いため、以下ではLinuxに限定して話を進める。

続きを読む

ウェブアプリケーション開発に新言語を採用したときにインフラで考えたこと

この文章は、サーバサイドのウェブアプリケーション開発において、社内実績の少ない新しい言語を採用したときにインフラ面で考慮したことを社内向けにまとめたものです。

はてなでは、長らくPerlでウェブアプリケーション開発を続けてきた一方、ここ数年で社内でScalaまたはGoの採用事例も増えてきました。 今後開発が始まるプロダクトにおいても、Perl、Scala、Goもしくは他の言語を採用するかどうかを開発開始時に選ぶことになるでしょう。

新言語を採用するときに、考慮すべきことの一つとして、「インフラ」への影響があります。 新言語に関する雑談をしていると、ウェブアプリケーションエンジニアに「インフラ」への影響について聞かれます。 もしくは、ウェブオペレーションエンジニアから考慮するポイントを伝えることもあります。 ScalaやGo以外に、Node.jsやサーバサイドSwiftはどうかというのも雑談レベルで話をしたりもしました。

そのような流れがあったため、ScalaまたはGoで書かれたプロダクトを2年ぐらい運用した結果をまとめておきます。

ウェブアプリケーション開発の言語として何を選ぶのか、といった技術選択そのものについては、はてなでの10年戦える新技術採用戦略の話 - Hatena Developer Blog を参考にしてください。

ウェブサーバアーキテクチャ

1つ目は、ウェブサーバアーキテクチャです。新言語を採用したときに、アプリケーションが動作するウェブサーバのアーキテクチャが変わる可能性があります。

ウェブサーバアーキテクチャ自体については、下記のエントリに書きました。詳しい内容はこのエントリを参照してください。

まず、採用しようとしている言語のウェブサーバが、上記エントリでいうところの、どのモデルなのかを確認します。

  • マルチプロセス(prefork)型
  • マルチスレッド型
  • イベント駆動型
  • ハイブリッド型
  • 軽量プロセス型

マルチプロセス(prefork)型

prefork型の場合、リクエスト処理コンテキストの単位がプロセスなので、コンテキストあたりのメモリ空間が独立しているという特徴があります。 MaxReqsPerChildのようなN回リクエストを処理しなおしたらforkし直すというようなこともできます。 したがって、運用観点でいうと、メモリリークや地雷リクエストへの対処がしやすいと言えます。

よほど1台あたりのパフォーマンスを求められない限りは比較的運用しやすいモデルといえます。

Perlの世界ではprefork型がデファクトになります。Rubyの場合はUnicornなどが相当するでしょう。 JVM上で動作する言語では普通やらない仕組みだと思います。

マルチスレッド型(スレッドプール)

マルチスレッド型は、リクエスト処理コンテキストの単位がスレッドなので、各コンテキストがメモリ空間を共有しているという特徴があります。 したがって、スレッド間でリソース競合を起こす可能性が高いため、プログラマが意識して競合を避ける必要があります。

意識といってもビジネスロジックのコードが問題になるというよりは、言語処理系の実装やライブラリの実装側に問題があることが多いと思います。 以前、Scalaのobjectのメソッド内のローカル変数の lazy valを使っている箇所のリソース競合問題により、著しくアプリケーションサーバのスループットが落ちるという現象がありました。

Javaの世界でよくみるモデルだと思います。他には、RubyのPumaなど。(追記: Pumaについては現在はマルチプロセスとマルチスレッドのハイブリッドモデルであるとコメントをいただきました。)

ウェブアプリケーション開発に新言語を採用したときにインフラで考えたこと - ゆううきブログ

Rubyのマルチスレッド型として紹介されるとMRIのGILが気になってしまいそうですが、Pumaは2以降のclustered modeだとマルチプロセス・マルチスレッドのハイブリッドなのでMRIでもコアを使い切れます。

2016/03/02 20:53
b.hatena.ne.jp

イベント駆動型

イベント駆動型は、イベントループにより1つのスレッドで複数のコンテキストを並行に処理します。 1スレッドで動作するので、マルチスレッド型以上にリソース競合に気をつける必要があります。

特にI/O処理をする場合、基本はノンブロッキングI/Oを使うことになります。 「ミドルウェアクライアント」の項にもでてきますが、意図せずブロッキングI/Oを使ってしまうと他のI/O処理をすべてブロックしてしまうので注意が必要です。

Node.js、RubyのThinなどで有名なモデルです。

ハイブリッド型

ハイブリッド型は上記の3種類のモデルの混合モデルを指します。 例えば、Play2のデフォルトはマルチスレッド+イベント駆動型になります。アプリケーションサーバにはあまり使われませんが、Nginxはマルチプロセス+イベント駆動型になります。

マルチプロセス/マルチスレッド+イベント駆動型の場合、たとえブロッキングI/Oを使っていても、ワーカープロセス/スレッド数分の並列度を維持できるので、あまり気にせずブロッキングI/Oを使うという選択肢もあると思います。

軽量プロセス型

Goのgoroutineベースのウェブサーバなどがこのモデルに相当します。 インフラ運用視点では、基本的にマルチスレッド型に近いと思います。

前述の3つのモデルはOSの仕組みを直接利用するものでしたが、軽量プロセスは、言語処理系上に実装されるため、言語ごとに特性を持っているといえます。 Goの場合、複数のgoroutineがOSの一つのスレッド上に多重化されます。

Goのネットワークサーバについては下記の資料が参考になります。 イベントループなしでのハイパフォーマンス – C10K問題へのGoの回答 | プログラミング | POSTD

JVMや軽量プロセス型の言語のように、言語処理系側で多くの制御をいれるような実装の場合、ロードアベレージやCPU利用率のようなOSのメトリックだけみてもボトルネックがわかりづらいです。

ミドルウェアクライアント

2つ目はMySQLやRedisなどのミドルウェアに接続するためのクライアントの実装に何が必要なのかについてです。

特にデータベースの接続管理まわりの話は以下のエントリにまとめています。

フェイルオーバ後の再接続

特に常時接続で問題になりやすいのが、ミドルウェア側がフェイルオーバした場合に、クライアントがフェイルオーバ先に再接続してくれるかどうかです。

再接続がうまくいかなければ、アプリケーションサーバを再起動するなどの対応が必要です。 これではいくらミドルウェア側で自動的に待機系に切り替わる仕組みをいれていたとしても意味がありません。

クライアントが再接続するためには、例えばRDBMSの場合、トランザクション開始時にpingして失敗したら再接続するような実装が必要です。 ただし、トランザクション中に再接続してしまうと、トランザクションがリセットされて、ACIDを満たせないことがあるからです。(再接続時にトランザクションがリセットされるかどうかはデータベースの実装によるところが大きいと思います。)

AWSのマネージドサービスであるRDS/ElasticCacheを利用する場合、DBのエンドポイントはDNSで参照することになります。 DNS参照の場合、再接続時に、DNSを引き直すような実装になっている必要があります。

このあたりはtkuchikiさんがわかりやすくまとめてくれています。

ブロッキングI/OとノンブロッキングI/O

ウェブアプリケーションサーバがイベント駆動型の場合、ミドルウェアクライアントのI/OがブロッキングI/OかノンブロッキングI/Oかはかなり重要です。

例えば、よくあるはまりどころに libmysqlclient はブロッキングI/Oしか対応していないため、libmysqlclientを用いたミドルウェアクライアントは仮にインタフェースが非同期であっても実際はブロックしてしまいます。

ScalaからJavaのライブラリを呼ぶ時などは注意が必要かもしれません。

ジョブキュー

ResqueやTheSchwartzのような汎用のジョブキューシステムが新しい言語では使えない可能性があることに注意が必要です。 実際、TheSchwartzやGearmanは基本的にPerlの実装です。ResqueやSidekiqは基本Rubyの実装ですが、他言語の実装もあります。しかし、挙動が一致している保証はないため、ある程度検証する必要はあると思います。

新言語を採用したときに、今までと同じジョブキューが使えない可能性があることは意外と盲点でした。 障害時にジョブキューの挙動を把握しておくことはかなり重要です。学習コストを抑えるために、できれば社内で言語ごとに異なる種類のジョブキューを運用するのは避けたいところです。

言語に依存しないジョブキューについて考えていた時期もありました。

実際には、特定言語に依存した汎用のジョブキューを使わずに、Redisを用いてアプリケーションに密結合する形で非同期処理をしていたりします。

DBスキーママイグレーション

DBスキーママイグレーションは、言語というより、ウェブアプリケーションフレームワークごとに管理の仕組みがあります。 Railsのmigrationが有名です。Play2ではevolutionという仕組みがあります。

Perlのプロダクトでは、スキーママイグレーションのための仕組みを用いずに、手動で丁寧にALTERするか、mysqldiffのような素朴なCLIツールを使っていました。

そこからスキーママイグレーションの仕組みに移ると、気軽にスキーマを変更しやすい反面、仕組みが複雑になりやすいというデメリットがあります。 データ量が増えているのに気づかず気軽にマイグレーションが発生した結果、ALTERによるテーブルロック時間が長くなり、無視できないダウンタイムが発生することもあるかもしれません。 (アプリケーションエンジニアが気をつけていてくれるので、自分はまだこの問題を実際に経験してはいませんが、今後を考えると不安にはなります。)

デプロイ

成果物の配置

インタプリタ型言語とコンパイラ型言語では、デプロイ先サーバへの成果物の配置方法が異なってきます。

PerlやRubyのようなインタプリタ型言語はビルドが不要なため、モジュールのインストールは必要ですが基本的にサーバ上でgit pullしてデプロイできます。 もしくは、管理サーバからデプロイ先にrsyncしてデプロイすることもできます。要はソースファイルを設置できればよいということになります。

ScalaやGoなどのコンパイラ型言語でも、ソースファイルをgit pullもしくはryncして各サーバでコンパイルすることも考えられなくはないです。しかし、本番サーバでCPU負荷のかかるコンパイルをやりたくはありません。コンパイルするための環境を用意するのも面倒です。

そこで、コンパイラ型の言語では、CIサーバかなにかで一旦ビルドしておき、ビルドした成果物をファイルサーバなりS3なりに置いて、デプロイ時に対象サーバに設置するというやり方でデプロイしています。

このとき、CIサーバと本番サーバとで、プロセッサのアーキテクチャ、OS、共有ライブラリなどを揃えておく必要があります。 ただし、JVM言語だとそのあたりの差分をある程度JVMが吸収してくれるため、そこまで気にしなくてよいかもしれません。さらに、Go言語はクロスコンパイルできてなおかつ、ポータビリティも高いので、JVM言語同様にそこまで気にしなくてもよいと思います。

ホットデプロイ(Graceful Restart)

言語ごとにホットデプロイ用のツール/ライブラリがある印象です。

PerlのPlackアプリケーションの場合、Server::Starterが有名です。 RubyのUnicornでServer::Starterに対応させたりもしているようです。「Server::Starterに対応するとはどういうことか」の補足 - sonots:blog Goの場合、Go言語でGraceful Restartをする - Shogo's Blog に紹介されている通り、いくつかの実装があります。ここに紹介されている以外にも、いくつか見た記憶があります。

JVM言語のホットデプロイの実装を見つけられなかったので、Scalaアプリケーションでは結局1台ずつローリングデプロイする手法をとっています。(追記:クラスローダによるホットデプロイの実装があるのは知っていますが、本番環境で使うものではないと聞いています。本番環境で使ってる事例があれば知りたいです。) 具体的には、SIGTERMシグナルを受け取ると、ロードバランサ用に生やしたヘルスチェックエンドポイントで503を返し、リクエストを振り分けられないようにします。その間、処理中のリクエストはそのまま処理しながら、N秒待ってからプロセスを停止します。 これら一連のフローを1or2台ずつ順番にやっています。 このような仕組みをPlay2のプラグインとして実装しています。

Server::Starter式に比べてデプロイに時間がかかるため、新旧のアプリケーションの共存時間が長くなってしまいます。 できればローリングデプロイをやめたいところです。

Server::StarterをJVM言語で使えるという話もあります Server::Starter から簡単に Java プロセスを起動できるようになった - tokuhirom blog。JVMのプロセスを2つたてたときのメモリ消費が気になるものの、そろそろこれを試すかという気持ちもあります。

もしBlue Green Deploymentができるなら、言語非依存な形でホットデプロイできることになります。

CI

インタプリタ型言語かコンパイラ型言語かによって、CIサーバのCPUリソースの消費特性が変わることがあります。

インタプリタ型の場合、CIではテストのみを実行するのが普通です。 しかし、コンパイラ型の言語だと、当然ですが、CIでコンパイルもします。 前述のデプロイの項目でも書いたとおり、成果物をCIサーバでビルドすることもあります。

特にScalaの場合、コンパイルが遅いことが有名です。 遅い上に自分の知る限りScalaの標準コンパイラはマルチコアスケールしないため、CIサーバにマイクロアーキテクチャが新しく、クロック周波数の高いCPUが求められます。(ということを実際のJenkins環境のCPUの使い方から観測していました。しかし、id:tarao さんによると、プロジェクトを複数のサブプロジェクトに分割していると並列ビルドされるということがあるようです。実際にサブプロジェクトに分割している環境でマルチコアスケールしていることを観測されたようです。)

一方で、インタプリタ型、コンパイラ型のどちらであっても、テスト実行にはCPUリソースを要します。 しかし、テストコードを工夫して、テスト実行を並列化できるため、テスト実行のためのCPUをコア数重視で選ぶこともできます。

モニタリング

ウェブアプリケーションサーバに必要なメトリックは、時間あたりに処理したリクエストの数や処理中のワーカー数などが一般的です。

当たり前ですが、言語処理系やウェブサーバごとに取得できるメトリックが異なります。 メトリックの取得方法も特に統一されたなにかがあるわけではありません。 メトリックがデフォルトで提供されていないことも普通です。 PerlだとPlack::Middleware::ServerStatus::Liteのような3rd partyのツールでメトリック取得用のエンドポイントを生やします。 Play2では、このようなプラグインが見当たらなかったので、stats取得用エンドポイントを生やしてもらいました。

ウェブサーバとしてのメトリック以外に、言語のランタイムに関するメトリックがほしいこともあります。 JVMの場合は、jstatもしくはjolokiaのようなツール/ライブラリで取得できます。 Goには標準的なものがなく、golang-stats-api-handlerのようなライブラリを使います。

あとがき

この文章で言いたいのは、開発言語の選択は単にプログラミング領域だけの問題ではないということです。

最初は開発言語の違いがどれくらいインフラに影響を及ぼすかはぼんやりとしかイメージできていませんでした。 実際にやってみて、結構あれこれと既存のものが使えないことに気づいていきました。

事前にわかっていればこの言語は選択しなかった、ということがないように、言語選択の議論に「インフラ」を絡める参考になればと思います。

「インフラ」と題打ってはいますが、実際には、ウェブアプリケーションエンジニアとウェブオペレーションエンジニアの境界の話です。 インフラの人がやってくれる、もしくはアプリの人がやってくれるというのではだめで、自分事として意識するのが大事だと思います。

※ 今回の話は 第3回関西ITインフラ系勉強会 - connpass で発表した内容を加筆修正したものです。