@ijin

[Michael H. Oshita]

ISUCON4の予選を通過したんだった

そういえば、3回目の参加となるISUCONの第4回目の予選を通過してました。

結果は185チーム中、19位というなんと微妙な結果で本戦にいける事に。

事前準備

今年も@cads@fruweでお馴染みのメンバーで「Mr. Frank & Co: A New Hope」として登録。

前日までこんな感じの打ち合わせや予習を実地。

  • 選択言語はruby
  • 効率化・自動化の為にansible playbookを用意
    • ssh keys
    • dotfiles
    • rubyの複数バージョン
    • 各種ミドルウェア
  • sidekiq等のバックグラウンドワーカーやキャッシュ周りの仮組み
  • redisの復習
  • 去年やったエントリを読む
  • 当日の流れの確認
  • コミュニケーションツールとして最近仕事で活躍してるSlackを導入

役割としては他の二人がコード実装に集中できるように、私がインフラ周り、コードレビュー、ファシリテーターを務める。

お題発表

不正ログイン防止の為にアクセスの失敗回数に応じてログインロックをかけるシステム。実に今風なオンラインバンキングサービス「いすこん銀行」w 最後はjsonのレポートが出力。

前半戦

まずは今までの経験からいきなりチューニングをせず、じっくりとアプリ挙動を把握し、計測し、コード解析に専念。レギュレーションもしっかり読む。この辺の衝動のコントロールはうまくなって来た感触。

アプリ自体は例年の作りによく似ていて、まあそこそこ高速化はできるだろうけど、その分他のチームも同様だろうと予想。

コードを3人で読みつつ、前日までに用意していたansibleを流したり、git化したり、計測を淡々と実地。

言語をいくつか参考値として計測した初期スコア

language score
ruby 1236
go 1733
perl 1662
  • Web側は各リスエストの比率やレスポンスタイム等を解析
  • DB周りは幸いMySQLだったので全ログ出力してpt-query-digestで解析
    (Postgresだったらどうしたんだろう)
  • OS周りはdstatやhtop等でリソースの利用具合を観測

計測やコードの理解が出来た時点で新アーキテクチャの設計を3人でディスカッションし、戦略立案。

タイムリミットをいくつか設け、いざ開始!(この時点ではまだ1行も変更してない)

基本戦略としてはMySQLのRedisへの置き換えし、アプリの処理自体を減らしていく方向。チームメイトの2人には実装を担当してもらい、その間に私が既存のMySQLバージョンのチューニングを施し、地味にスコアを上げていく。

  • INDEX追加
  • MySQL parameter tuning
  • stylesheetsやjavascriptの直接配信
  • TCP tuning
  • File Descriptor上限緩和
  • unix domain socketの利用

合間合間に声をかけ、実装具合や残り時間をチェックし、テンポをとる。

予め決めていた期限である14:00(ソフトリミット)に到達した時点で進捗を確認し、15:00(ハードリミット)までには間に合いそうだったのでそのまま続行を決定。

14:31にredis版が仕上がる!

実装方法

  • 初期化スクリプトでMySQLからRedisへ変換
  • 失敗ログインはINCRでカウント
  • lockとban時にはSADDでSETに追加
  • SISMEMBERでlock/banの確認
  • ユーザ個別の履歴はHMSET/HMGETでハッシュ化
  • /reportはSMEMBERSで出力

後半戦

Redis版のgit branchに私のコミットをマージし計測。MySQL版よりは多少良いスコア。

mysql version

tag:benchmarker type:score      success:82910   fail:0  score:17910

redis version

tag:benchmarker type:score      success:87810   fail:318  score:18969

CPU消費がアプリに移っていたので、後はいかにrubyに処理をさせないかの勝負となる。

ここからやったのは

  • worker loadの最適値模索
  • パスワードを初期化スクリプト時にオンメモリで持つように改造
  • パスワードのハッシュ計算除外(ハッシュ計算のスキップは結果的にスコアには影響せず)
  • トップページのエラーリダイレクトをクエリパラメータ化し、nginxで静的キャッシュ
  • ログイン部分のlua実装(間に合わず)

最後のlua実装は取り掛かったものの、期限までには時間が足りないと判断し断念。かわりに失格とならないように再起動後の正常動作する事を入念に確認。

結局、ハイスコアは39243で終了

tag:benchmarker    type:score    success:181670    fail:0    score:39243

後で気づいたのが最高スコアではなく、最終提出スコアが最終結果となるので誤差により若干下がってしまった。。

戦いの軌跡

振り返り

  • 戦略と遂行は概ね正しかったかと
  • タイムキープ大事
  • 実装は段階的に作ったので大きなバグがなかった
  • ansible便利(自動化で効率化)
  • Slackが非常に良かった(特にgithub連携)
  • VarnishのTシャツ着てたのに使わなかった
  • access_logを切るのを忘れてた!(offで試した41594だった)
  • 多くのチームが失格となっていたので最終チェックは大事
  • 今回は基本実装が余裕で間に合ったので確実に上達していると実感

復習

予選終了後、最後に断念したlua化を一人で実装してみた。

/login実装でスコアは5万超、/mypageまでやって6万超でした。

tag:benchmarker type:report     count:banned ips        value:1043
tag:benchmarker type:report     count:locked users      value:5290
tag:worker      type:fail       reason:Response code should be 201, got 403     method:POST     uri:/results
tag:benchmarker type:fail       message: Score sending failed   reason:Response code should be 201, got 403     method:POST     uri:/results
tag:benchmarker type:score      success:278260  fail:0  score:60108

その後に、競技中には思いつなかったimageやcssをUser Agent判定で切って(DOM構造は変えずに)みたら15万超え

tag:benchmarker type:report     count:banned ips        value:3115
tag:benchmarker type:report     count:locked users      value:10426
tag:worker      type:fail       reason:Response code should be 201, got 403     method:POST     uri:/results
tag:benchmarker type:fail       message: Score sending failed   reason:Response code should be 201, got 403     method:POST     uri:/results
tag:benchmarker type:score      success:210324  fail:0  score:155272

この辺が限界。

うーむ。山形組の30万超えには程遠いなぁ。恐ろしや。

最後に

運営の皆様、ベンチマークツールの不具合やインスタンスガチャの問題等ありましたが、対応方針は非常に納得のいくものでした。引き続き、本戦を楽しみにしています。ありがとうございました!

ConsulによるMySQLフェールオーバー

先日(6/22/14)、6月なのにどういう分けか早めに開催されたJuly Tech Festa 2014でConsulについて発表してきた。そのユースケースの一つとしてMySQL failoverをちょっとだけ紹介したので、ここに詳しく書いておく。

MHA

MySQLレプリケーションの障害時にフェールオーバーしたい場合、MHAを使うの結構ポピュラー(日本では)だと思います。MHAは最新binlogの適用、Slaveの昇格とレプリケーションの張替えまではやってくれますが、実際のフェールオーバーの部分はユーザに委ねられていて、master_ip_failover_scriptのテンプレートをカスタマイズするか独自実装する必要があり、一般的な実現方法としてはカタログデータベースの更新かVirtual IPの切替等があります。

Virtual IPだと居残りセッションの問題や切替の保証難しかったり、そもそも環境によっては使えなかったりするので、私はあんまり好きじゃありません。また以前、後者的アプローチとしてAWSのVPC内であればrouting tableを変更する事によってこの挙動に似た実現方法を紹介した事がありますが、一番の問題点はAPI backplaneがSPoFになってて、ここが落ちたらそもそも動かない;また、APIのrate limitに達して呼び出しさえ出来ないという結構痛い目に会ったりします。

そこで前者的なアプローチとしてmasterの情報を管理するカタログデータベースの更新部分にConsulを使ってみました。

CONSUL

ConsulとはHTTP APIとDNSで操作ができる分散型クラスタで、VagrantやSerf等を開発しているHashicorpの新プロダクトです。Key featureは以下のとおり。

  • Service Discovery
  • Failure Detection
  • Multi Datacenter
  • Key/Value Store

ConsulのConsistencyについて

ConsulはCAP定理でいうCPという特性をもっており、Consistency(一貫性)に重きをおいてあります。Paxosを由来とするRaftをベースにしたconsensus protocolで実現していて、ピアセット内の各サーバノードでlog entryの書き込みがquorumで決定された後にcommitされたと見なされ、FSMに書き込まれます。よって、書き込みに関してはStrongly Consistentな処理となります。読み込みに関しては、パフォーマンス要求に応じてConsistencyレベルをクエリータイプによって調整可能(まるでCassandraのように!)で、Usually Consistent, Strongly Consistent, Staleから選択可能です。DNSベースのクエリーはデフォルトで単一のリーダーノードが返答するのでStrongly Consistentな処理となっています。(Staleにする事も可能)

ConsulとMHAの連携概要

  • Consulを内部DNSとして使い、clientはDNSベースでmasterに接続
  • MySQL masterはAPIで予めサービス名(alias DNS)を登録しておく
  • mysql_failover_scriptで旧情報の削除と新情報の登録をやる
    • 旧master IPを無効化する部分でConsulのCatalog endpointを使ってderegister (/v1/catalog/deregister)
    • 新master ipを登録する部分でConsulのCatalog endpointを使ってregister (/v1/catalog/register)
    • 成功しない限り進まない(exit code check)
  • 削除、及び登録はconsulのconsistency modelによって一貫性は保証される

DEMO

Notes

DNSは最新のv0.3.0になってからTTLを設定できるようになったので、Amazon RDSっぽい感じのフェールオーバーも可能ですが、v0.2系に比べて格段にパフォーマンスが向上(スライド参照)したので、デフォルトのTTL 0でも問題ない範囲になってる感じです。また、もうちょっと詳しい内容は今度の#hbstudyで話す予定です。

スライド

以下、July Tech Festa 2014で発表した時のスライドです。

#ChefConf 2014に参加してきた

San Franciscoで行われた#ChefConfに参加してきました。 忘れないうちに忘備録的に少しメモっておく。

Day 1

Awesome Postmortems by Dave Zwieback

システム障害に対して素晴らしいPost Mortem(振り返り/報告書)の書き方に関する丸一日のワークショップ

前半

まずはチームに分かれて断片的且つ関連性の不明な情報を渡される。 例えば、

  • Tomは紫色の家の住人より短い
  • Jimは両隣の住人より高い
  • 赤色の家の隣人は子供が5人いる

各メンバーは情報を全部開示できないまま、ある不明確なタスクを時間内に完了させる必要がある。しかし、紙やモノを使って情報の整理をしてはならず、口頭による連絡のみなので当然情報は錯綜しタスクは未完のまま終了。

障害時の情報不足・体制不足のシミュレーション。Nosey Neighborsと言うゲームらしい。

その後、お怒りのCEOからDarth Vader風のオーディオ・メッセージで以下を解答せよとのお達しが。。

  • 根本原因は何か
  • 何をすれば良かったか
  • 誰の責任か

メンバー間で議論して各グループとの比較・プレゼンする

後半

パラダイム・シフトとディスカッション形式

以下、メモ

  • たった一つの根本原因は存在しない。システムはimpermanence(非恒久・無常)である。コレに対して誰かが「なんてZenなんだ!」w
  • そもそもHuman Error(人的エラー)は原因ではなくもっと大きな問題を示すサインであり、症状であり個人やグループのせいにしてはいけない。そうすると簡単で気持ち良いが解決にはならない。80年代の航空会社の事故分析のパラダイム・シフトを引き合いに
  • Accountability vs Responsibility(個人・グループに責任は負わせないけど説明・報告はさせるべき)
  • その為にもBlameless(誰かを責める事のないように)でNonpunitive(非罰則)にするべき。火災の消防活動に対する消防隊員の扱いの例が面白かった
  • Hindsight/Outcome Bias(事後だと情報が多くより物事がより鮮明になるけどそれは偏見である)
  • Counterfactual(事実に反する。タラレバ)な事を書いてはならない。起こった事は事象は変えられない
  • 3 Rs (Regret, Reason, Remedy) 遺憾を示し、事象のリニアなタイムラインを記載し、解決案の列挙
  • 5 Whys 日本では「なぜなぜ分析」というらしい。要因追求のイテレーティブ・プロセス。
  • Sharp End vs Blunt End(顕在的エラー vs 潜在的エラー)
  • Morgue - Postmortem用の便利なツール

主に組織論やカルチャーの話やDevOpsとの関連性の議論等。今度#トラしゅに組み込もうかな。

山◯君はこっちではボブと呼ばれる

Day 2

Keynote

  • ヘビメタ風のBGM
  • Barry Crist CEOによるDelightful Economyの熱いプレゼン。Uberの紹介
  • Chef Metalの紹介/デモ(DockerやMongoDBで)
  • ChefDK (Chef Development Kit)の紹介

Hunting the DevOps Whale in Large Enterprises

  • 大企業でのDevOpsの話
  • かなり抽象的でメタファー引用多数
  • Scrumfall (Scrum + Waterfall)という悪い冗談のような本当にあった怖い話

Spice up your recipes with Chef Sugar

ChefSpec、Test Kitchen、BerkshelfのコアコミッターであるSeth VargoによるChef Sugarの紹介。コードをよりrubyっぽく、より美しくする為のsyntax sugar

The Berkshelf Vision

  • Berkshelfの原作者であるJamie WinsorによるBerkshelf 3.0の紹介
  • ChefDKでのインストールを推奨
  • 推奨されるべき新しいワークフロー管理や手法について
  • その補助ツールであるBerkflowの説明
  • このセッションが一番面白かったのでyoutubeに上がったらまた見るべし!
  • スライド

Implementing Continuous Delivery in Chef

継続デリバリーのお話。発表者がつまらなかったので、お仕事してた。

Chef and Docker

  • Dockerの紹介やロードマップ
  • 実装方法
  • Chefとの組み合わせ方
  • 2014 2QのDockerConで1.0が発表されるかも
  • 監視に関して突っ込んで質問したら、夏以降に出るであろう監視用コンテナに期待とな
  • ついでにTシャツもらった

BoF - Chef on AWS

  • AWS上でのChef利用に関してBoF (Birds of Feather)形式のディスカッション
  • Cloudformation、Autoscaling、Opsworks、Ohai等
  • 特にOpsworksの使い勝手の悪さを熱く議論してきた
  • また、OhaiはIAM roleの情報をキャッシュするのでChef Serverとの組み合わせが悪い
  • Citadelというので回避してる人も

Day 3

Keynote

Adam Jacob CTOによるChef社の歴史、思想や方向性やDevOps Cultureについて

Get Up Again (Over and Over): Learning and Relearning with Chef

変化への対応、リファクタリング、実験的コードの組み立て方等

Foreman and Chef Integration

Red HatによるForemanの発表。あんまり聞いてなかった

DevOps Culture And Practices To Create Flow

  • ThoughtworksのJez Humbleによる発表
  • 自動化や継続デリバリーによるリーンな開発手法
  • トヨタやHPの事例
  • 社内カルチャーの話
  • Jesse’s rule - “Don’t fight stupid. Make awesome”
  • なかなか良いスピーカーであった

その他

  • アメリカのクライアントやリモートでの仕事仲間と初顔合わせ
  • ランチで会った人は同じくCassandra構築に苦労してて盛り上がった
  • Chef Zeroの作者と会って中の動きについてお話した
  • mizzy氏を発見。立ち話を少々

AWS Game Day Japan 2014春を開催してきた

過去に2度参加した事(東京・ラスベガス)があるGame Dayに今回は運営側に周りました。3/15に行われるJAWS Days 2014の前夜祭という位置づけです。

日本では前回、東京のみだったけど今回は東京・大阪・名古屋・仙台と4都市同時開催。

お題

前回と全く一緒。。SQSを使った疎結合でオートスケーリングする画像変換処理システム(サーセン)。まあ、1年前と比べてAWSの機能やできる事も大分変わったので2回目の人もいろいろ工夫のしようがあったかと。

チーム

1チーム3〜4人に別れて計14チームとなりました。

東京

  • Cookie Devil
  • Bluescreens
  • 沖縄
  • Blue Light of Death
  • 時計じかけのオレンジ
  • I am みどり

名古屋

  • Shachihoko
  • ななちゃんだがや
  • ゴーゴーひつまぶし

大阪

  • 大都会
  • AWS学坊や
  • 初心者

仙台

  • Zao
  • 八重の桜

内容

当日の迫力ある詳しい内容は参加者の方がきっとブログに書いてくれるはず!その代わり、運営側で評価用に使っていた攻撃・修復内容のまとめを公開して欲しいというツィートを頂いたので下記に表示しておきます。

(実は今回もイベントオーナーのMiles Wardが来日していたので、Google Spreadsheetに書き込まれていた内容を運営側で逐一翻訳していました)

個人的にはs3の「Requester Payオプション有効化」がお気に入りでしたね。

結果

総合優勝はチーム「AWS学坊や」の優勝となりました。おめでとうございます! (地方賞はすみません、総合結果評議中で聞けてませんでした)

今回、運営側のトラブルで攻撃対象が重複してしまう等いろいろ問題がありましたが、開催する側としても楽しかったです。次回はさらにチャレンジングな内容にしていきたいと思っています。

JAWS DAYS

明日のJAWS DAYS 2014はスタッフとしていろいろ動くので今回は手短に。

I2 instanceでMySQLベンチマーク

新年明けました。おめでとうございます。

すっかり12月のaws/mysql advent calendarに乗り遅れたので、AWSのi2 instanceでのMySQLのベンチマークを勝手におまけとして公表します。 以前取ったhi1.4xlargeとの比較になります。

構築

SSDディスクはAWSが推奨するover-provisioningを有効にする為に10%を非partitionし、それぞれ720Gでパーティション作成。

sudo mdadm --create /dev/md0 --level 0 --raid-devices 8 /dev/xvdb1 /dev/xvdc1  /dev/xvdd1 /dev/xvde1 /dev/xvdf1 /dev/xvdg1 /dev/xvdh1 /dev/xvdi1
sudo mkfs.xfs -f -b size=4096 -i size=512 -l size=64m /dev/md0
sudo mount -t xfs -o noatime,logbufs=8 /dev/md0 /data

OSはkernel versionが3.8以上が望ましいのでいつものLTSではなく、Ubuntu Server 13.10のHVMタイプ (ami-b93264d0)

sysbench

やり方やパラメータは前回の計測方法と同じ。i2.8xlaregは32coreですが、今回はhi1.4xlargeの時と比較する為に敢えて16スレッドで計測しました。

  • sysbenchのoltpモード
  • データサイズは12G(5000万件)
  • readonly
  • uniform(フルスキャン)

コマンド

time sysbench --test=oltp --oltp-table-size=50000000 --db-driver=mysql --mysql-user=root --num-threads=16 --max-requests=0 --max-time=180 --init-rng=on --oltp-read-only=on --oltp-dist-type=uniform 2>&1 run

トランザクション推移

レスポンスタイム推移

速いですね。

tpcc-mysql

こちらも前回)の計測方法と同じ。

  • 500 warehouses (50GBぐらい)
  • 24GB Buffer pool
  • 16スレッド
  • 1時間実行

コマンド

 tpcc_load localhost tpcc root "" 500
 tpcc_start -d tpcc -u root -p "" -w 500 -c 16 -r 300 -l 3600

hi1.4xlargeはSSD1台で計測した事を考えると、同価格帯のi2.4xlarge(SSD4台)の半分(SSD2台)のパフォーマンスが出るのは妥当ですね。

fio

ついでにfioでそれぞれRAID0した場合のベンチマークも取ってみたけど、並河さんと結果が違ってwriteがスケールしてます。OSとmkfs.xfsのオプションしか違わないはずだけど。。

その他

いやー。spot instanceがないので計測だけで結構かかってしまった。

しかし、なんでhi1世代の次はhi2ではなく、i2なんだろう。

Autoscalingによる自動復旧(Immutable Infrastucture)

以前、「非ELBなAutoscalingによる自動復旧」でインスタンスの自動復旧の挙動をテストしました。 障害が発生したサーバをterminateし、新サーバをstartしてリプレースする仕組みはまさに最近話題のImmutable Infrastructureですね。 CDP的には「Server Swappingパターン」が一番近いですが、今後はImmutable分類もあっても良いような気がします。

前回はAuto Scalingがインスタンス障害を検知してリプレースするまでのタイムラグが約20分だと分かりました。 本日、インスタンスの状態をチェックするEC2 Status Checkが1分間隔になった(以前は5分間隔)と発表されたので、 これによってタイムラグが短縮されたかを検証してみます。

設定

手順は前回と一緒なので省略

自動復旧

通信を遮断し、Status Check Failを発動させる

1
2
3
ubuntu@ip-10-123-32-180:~$ date; sudo ifdown eth0
Sat Dec 14 14:19:19 UTC 2013
Write failed: Broken pipe

EC2のStatus Checkを流す

while true; do date; aws ec2 describe-instance-status --instance-ids i-b03788b5 --query 'InstanceStatuses[*].InstanceStatus' --output text ; echo ; sleep 10; done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Sat Dec 14 23:22:05 JST 2013
ok
DETAILS reachability    passed

Sat Dec 14 23:22:16 JST 2013
ok
DETAILS reachability    passed

Sat Dec 14 23:22:27 JST 2013
impaired
DETAILS 2013-12-14T14:22:00.000Z        reachability    failed

Sat Dec 14 23:22:37 JST 2013
impaired
DETAILS 2013-12-14T14:22:00.000Z        reachability    failed

約3分でStatus異常が検知されました。

Auto ScalingのHealthStatusを流す

while true; do date; aws autoscaling describe-auto-scaling-instances --query 'AutoScalingInstances[*].HealthStatus' --output text; echo; sleep 10; done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Sat Dec 14 23:38:06 JST 2013
[
    {
        "State": "InService", 
        "Health": "HEALTHY", 
        "ID": "i-b03788b5"
    }
]

Sat Dec 14 23:38:17 JST 2013
[
    {
        "State": "InService", 
        "Health": "UNHEALTHY", 
        "ID": "i-b03788b5"
    }
]

Sat Dec 14 23:38:28 JST 2013
[
    {
        "State": "InService", 
        "Health": "UNHEALTHY", 
        "ID": "i-b03788b5"
    }
]

Sat Dec 14 23:38:38 JST 2013
[
    {
        "State": "Terminating", 
        "Health": "UNHEALTHY", 
        "ID": "i-b03788b5"
    }
]


Sat Dec 14 23:38:49 JST 2013
[
    {
        "State": "Terminating", 
        "Health": "UNHEALTHY", 
        "ID": "i-b03788b5"
    }
]

Sat Dec 14 23:38:59 JST 2013
[
    {
        "State": "Terminating", 
        "Health": "UNHEALTHY", 
        "ID": "i-b03788b5"
    }
]

Sat Dec 14 23:39:10 JST 2013
[
    {
        "State": "Pending", 
        "Health": "HEALTHY", 
        "ID": "i-4cc7a849"
    }, 
    {
        "State": "Terminating", 
        "Health": "UNHEALTHY", 
        "ID": "i-b03788b5"
    }
]

やっとAuto Scalingの方でも異常検知。

SNS通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Service: AWS Auto Scaling
Time: 2013-12-14T14:39:41.271Z
RequestId: f622c6d2-8c77-4fef-8b38-ece463574712
Event: autoscaling:EC2_INSTANCE_TERMINATE
AccountId: 111155559999
AutoScalingGroupName: test-sg
AutoScalingGroupARN: arn:aws:autoscaling:ap-northeast-1:11115559999:autoScalingGroup:0e771015-f979-4afe-b065-595abafdbf9b:autoScalingGroupName/test-sg
ActivityId: f622c6d2-8c77-4fef-8b38-ece463574712
Description: Terminating EC2 instance: i-b03788b5
Cause: At 2013-12-14T14:38:38Z an instance was taken out of service in response to a system health-check.
StartTime: 2013-12-14T14:38:38.257Z
EndTime: 2013-12-14T14:39:41.271Z
StatusCode: InProgress
StatusMessage:
Progress: 50
EC2InstanceId: i-b03788b5

やはり20分のタイムラグ変わらずですね。。

結論

というわけで、EC2 Status Checkが1分間隔になっても、EC2のみ(ELBを使わい場合)のAuto Scalingによる不調インスタンスの自動復旧時間は変わらずでした。

ちなみにAWS ConsoleでAuto Scalingの設定ができるようになったけど、まだscaling groupにtagが付けられないのがちょっと微妙ですね。。GUIで状態を見る分には楽だけど。

サバフェス!2013に参加してきた

少し前にサバフェス!2013 Autumnに参加してきました。 内容を忘れないうちにやった事を書いておきます。

スコアはトップレベルだったものの、運営側がサーバを起動した所、自動でサービスが立ち上がらなかったらしいので残念な事に参考値のみに。(提出前に2回ぐらい再起動確認したのにおかしいなぁ。。) 優勝スコアは90.830 (GET 199,802 : POST 18,106)で、私のは93.410 (GET 162,923 : POST 29,930)でした。

お題

「最速インフラを構築せよ!!!

WordPressに一切手を加えずに、どこまで高速化できるのか!?

OSチューニング、サーバチューニング、負荷分散…最適解を探せ!」

IDCFクラウド上で仮想マシン5台(M8タイプまで)を使ってスコアを競うというもの。 チューニンガソンと似てますが、サーバが複数台使えるのが良いですね。

構成

表彰式でLTしたけど、LVS (DSR) + php 5.5 + apcu + Varnish + nginx (lua) + memcached

ポイントは

  • GET時は1台では帯域の限界に達したのでLVS (DSR)による4台での並列応答
  • POST時には必ず各Varnishのキャッシュクリア(ban)する
  • POST時にnginx->memcachedへ渡すと高速すぎたのであえてsleepを5msして遅延させる
  • nginx(lua)はコード量が多いと通らないのでぎりぎりまで削減
  • memcachedからmysqlへの非同期処理(5ms間隔)
  • mysqlの更新処理はそんなにいらないので基本チューニングとInnoDBにしただけで、5.1のまま
  • サーバはM8までいらないのでM4で
  • 海外から参戦したけど、GUIが重いのでAPI経由での操作

でしょうか。特に意識したのはmemcachedからmysqlへの許容範囲内での同期と複数台あるVarnishのキャッシュクリアですね。他のチームはnginxでTTLを設定して逃げたようですが、実運用時にはPOST時にキャッシュクリアを確実にする必要があるのでVarnish moduleをコンパイルして他のsiblingへ並列でban処理を投げてました。まあ、今回のベンチマークツールはそこまで厳格じゃなかったけど、最終チェックは人間が動作させるので。

LT資料。スコア推移と簡単な構成の紹介

設定ファイル

以下、設定ファイルです。いらない所は削ったけど動くはず。。

backend varnish vcl (各backendの設定は微妙に違う):

backend nginx.conf:

base nginx.conf:

base syncer.rb:

base supervisord.conf:

1
2
3
4
5
6
7
8
9
10
[program:syncer]
command=/home/mho/.rvm/bin/ruby /home/mho/syncer/sync.rb 5
stdout_logfile_maxbytes=1MB
stderr_logfile_maxbytes=1MB
stdout_logfile=/tmp/%(program_name)s-stdout.log
stderr_logfile=/tmp/%(program_name)s-stderr.log
user=mho
directory=/home/mho/syncer
autostart=true
autorestart=true

base my.cnf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
character-set-server = utf8
max_connections = 1000
 
key_buffer_size = 32M
max_allowed_packet = 16M
thread_stack = 192K
thread_cache_size  = 200
 
#slow_query_log=1
#long_query_time=0
query_cache_type = 0
skip-innodb_doublewrite
innodb_buffer_pool_size = 192M
innodb_log_buffer_size = 4M
innodb_flush_log_at_trx_commit = 0
innodb_support_xa = 0

sysctl.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fs.file-max = 1048576

net.ipv4.ip_local_port_range = 1024 65535

net.core.wmem_max = 16777216
net.core.rmem_max = 16777216

net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216

net.core.somaxconn = 8192

net.core.netdev_max_backlog = 8000

net.ipv4.tcp_max_syn_backlog = 8192

net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_retries2 = 5

net.ipv4.tcp_keepalive_time = 900

net.ipv4.tcp_keepalive_probes = 3

net.ipv4.tcp_keepalive_intvl = 15

net.nf_conntrack_max = 1000000

終わりに

最初はecho選手権になってたのであんまりやる気がなかったけど、後半はちょっと楽しめました。結果は惜しかったけど、今までチューニンガソンISUCONに出場したり、トラブル☆しゅーたーずを主催したり、日々の運用等の経験が生きた感じがします。運営の皆様、ありがとうございました。次があれば楽しみにしています!(レギュレーションの解釈が微妙だったのでそこはブラッシュアップして欲しいですね)

ISUCON3の本戦に参加してきた

先月の予選に通過したので、ISUCON3の本戦に参加してきました。

完敗。

お題発表

画像版twitter。投稿する画像の公開レベルをpublic, private, followers onlyに設定できるシステムが1台のVPS(2コア、4GB RAM)で動いている状態。プレスを打った為、大量アクセスがくる18時までに別途用意された4台のサーバを使ってスケールせよという使命を与えられる。

流れ

画像データが1万点・約3GBあったので、まず失敗しても戻れるようにバックアップ取得を開始。それと平行して他のサーバへのsshキー登録したり、hosts書いたり、もろもろ下準備。

デフォルトのperlのスコアは1206.8

言語はrubyと決めていたので、supervisorで立ち上げてみるが起動失敗。よくよく調べてみるとforemanが入ってなくてGemfileに追加してbundle。

この時のスコアが1180.8

次にデータベースを見てみるものの、レコード数も比較的少なく、総容量が2MBもないのでできる事は限定されていると判断。クエリーをさらっと見た後にentriesのimageカラムに対してインデックスを張ったぐらい。

アクセスログにレスポンスタイムを出力するようにして1回ベンチを走らせログを解析。

ブラウザ上の挙動を確認しつつ、ソースコードを読んで結局画像変換のconvert処理が一番重そうだったのでそこから着手することに。

予選の時も外部プログラムを呼んでいるところが改善ポイントの一つだったので、まずfastimage_resizeを使って置き換えてみるものの、処理速度はそんなに上がらず、スコアもほぼ横ばい。

その間に、ロングポーリングの処理を変更してみるけど、

 "message": "2013-11-09T14:48:17 [36898] [CRITICAL] timeline refrection timeout"

タイムラインの反映がうまくいってない模様。 (ちなみにエラーメッセージのrefrectionはreflectionのスペルミスですね)

次に画像変換処理の部分で毎回リクエストがくる度に実行されるリンクをredisにてキャッシュ。これは効果があり、スコアは6634.2で暫定3位。

その間にVarnishやHaproxy + nfsを軽ーく試してみるものの、スコアは伸びず。

この辺でリンクだけではなく、画像自体をredisに突っ込んで全サーバで処理するアーキテクチャを決定。@acidlemonさんと似た構成ですね。ただ違うのはPOST後のsidekiqを使って処理を裏のワーカーに任せるという事。

sidekiqが動作するところまではでき、全画像の変換を試みるがredisサーバのメモリが溢れてたので、最初にアクセスされる直近30件、アクセス比率が高いサイズsと、新規画像のみに注力。スコアは徐々に上がる。

その後はただひたすらに、もくもく実装とデバッグ。

残り3-40分ぐらいのところで、生ハムチームでブレークスルーが起こり、彼らが一気にトップへ踊り出る。我々も1台構成であれば5位ぐらいにはなれただろうけど、スケールアウトしなければ全く勝負にならないので最後の最後まで果敢に挑戦するもあえなくタイムアップ。

結果、FAIL。

感想

今回はサーバが5台もあったので、スケールアウトしなければならないのは明白で、実装を真っ先に着手するべきでしたね。前半で1台だけチューニングして後でスケールしようと思ったのが戦略上の致命的ミス。時間切れで終わったので実装が間に合っていたらそれなりのスコアが出たはずかと。優勝した生ハムチームが結構ギリギリまでかかったのを考えると、やはり見極めたポイントは重要で、さすがとしか言いようがないです。また、一番時間のかかった画像配信に関しては普段AWSを使っている身としてはs3へ画像を突っ込むのが当然だと考えていたので、なかなか新鮮で違う脳を使う感じで楽しめました。

その他

  • 途中ディスカッションをすれば良かった(予選は上々でも本戦で焦ってしまった)
  • 落ち着いて俯瞰して見るべし。見極め大事
  • 予選とかの先入観が邪魔したのでまっさらの状態で考えるべき
  • ベンチマークツールはFAILしても再実行に2分待たされるのがどうしてももどかしかった
  • ベンチマークツールの他のサーバへの実行切替がバグってて時間をロスった
  • チーム名のRevengeが果たせなかったので、来年はチーム名どうしようかな。。
  • ドヤモリスが満面の笑みで幸せそうだった

ISUCONのレベルも毎回毎回レベルが上がっていき、運営側の苦労が伺えます。本当にお疲れ様でした!また来年にも期待しています!

おまけ

さて。1年間待ち望んだイベントがあっという間に終わってしまって消失感・焦燥感を味わいつつも、気を取り直して次はAWS re:InventのGAMEDAYに日本から唯一(多分)の参加者として参戦します!

ISUCON3の予選を通過した(はず)

去年に引き続き参加したISUCON第3回目の予選を通過しました。

結果は参戦1日目の方で4位。 74チーム中、総合6位になりました。

事前準備

再び@cads@fruweに声をかけ、去年3位という雪辱を果たすべく、「The Revenge of Mr. Frank & Co.」とチーム名を改名。 今年はチーム数が膨れると予想されてたので去年と違って予選がありトップ20位ぐらいまでしか本戦に行けないという仕組み。方針は以下の通り。

  • 言語は我々の得意なruby(goも面白そうだったけど)で勝負
  • 去年Railsの再実装で失敗した為、Sinatra(であれば)でやりきる
  • Sinatra、Varnish、Sidekiq、Redisの復習
  • 計測ツールの導入
  • 早い時期に全体のスケジュールを決定する
  • 戦略転換の見極め

特に去年の敗因が戦略転換の遅れだったので、それを意識するようにしました。

お題発表

Markdownを使ったコメントシステム。しかもユーザログインがあってポストのprivate/public指定ができるというちょっとアドバンストな作り。

前半戦

今回、サーバはAWSのAMIとして提供され自分のアカウントで起動するやり方だったので、CPUガチャに若干期待しつつ10台一気にlaunchする。確認したら全部同じCPUだったので半分落として、後は本番用、開発用、バックアップ用として残す。

まずは、状況把握の為にひとまず皆でルールの理解とソース解析。ざっと目を通した後はブラウザで動作確認し、今回同一マシン内で実行させるベンチマークプログラムを複数言語で実行。制御方法は去年と同じsupervisord

Perl

2013/10/05 10:31:15 done benchmark
Result:   SUCCESS
RawScore: 1020.4
Fails:    0
Score:    1020.4

Ruby

2013/10/05 10:38:11 done benchmark
Result:   SUCCESS
RawScore: 2446.9
Fails:    0
Score:    2446.9

Go

2013/10/05 10:45:32 done benchmark
Result:   SUCCESS
RawScore: 2840.1
Fails:    0
Score:    2840.1

Node

2013/10/05 10:49:07 done benchmark
Result:   SUCCESS
RawScore: 1543.4
Fails:    0
Score:    1543.4

お、rubyそんなに悪くないかも。 デフォルトのperlが遅いのはメモリが温まってないかなと思って再実行したけど飛躍的に良くはならなかったですね。

この辺で予め決めていた開始後1時間という期限になったので作戦会議とスケジュール策定に入る。

まずはVarnishを入れてみようという事なので一人がその作業にとりかかる。もう一人はNewrelicという測定ツールの導入とバックアッププランのRedisバージョンの作りこみ。私はDB周りのチューニングやミドルウェア周り、後は二人のサポート。

DBはMySQLだったので全クエリを吐き出してpt-query-digestで解析して統計的に遅いクエリ順に潰す。 schemaをよく見たらindexがなかったので張ったり、クエリーをちょっと改善してみたり、パラメータを変更(innodb_buffer_pool_sizeとinnodb_flush_log_at_trx_commitのみ)したり黙々作業。

時間はあっという間に過ぎて両方のバージョンを導入してみるもののベンチマークツールからFAILを大量にくらい、スコアが全然つかず。。。

折り返し時点が迫ってきたので、とりあえず今あるチューニングだけのスコアを送信。

2013/10/05 13:43:37 done benchmark
Result:   SUCCESS
RawScore: 3791.5
Fails:    6
Score:    3450.2

後半戦

その後、unicornのワーカー数を調整したり、クエリーを更に見なおしたり、initializer作って事前にキャッシュを温める仕組みを作ったりして6000ぐらいのスコアを出した気がする(この辺は作業ログを残すのを忘れていたので記憶が曖昧)

Redisバージョンは雰囲気的に無理そうだったので早々に捨ててこの辺は去年の教訓が生かされてると思う)ESI対応 + Varnishに注力する事を決断。しかし、こっちもエラーの連発。。

最も悩まされたのは

14:53:20 [FAIL] invalid Cache-Control header
14:53:21 [FAIL] invalid post memo URL http://localhost/

どうやらリバースプロクシ用に付与しているヘッダーチェックで弾かれていた模様。

VCLファイルをよく見たら既存のシステムから流用したモノらしく、余計なルールがたくさん入っていたので、仕切り直しということで一旦白紙から再作成する事に。

なんとか基本部分だけ動かす事ができるようになってスコア送信。

2013/10/05 15:45:18 done benchmark
Result:   SUCCESS
RawScore: 14694.2
Fails:    0
Score:    14694.2

この辺で暫定一位。


しかしその後privateページのキャッシュexpire方法を模索するも、なかなか進まずにタイムリミットが刻々と迫り、他のチームがどんどん上位に食い込んできて若干焦り始める。ひとまず、強制フラッシュするエンドポイントと裏で定期実行するバックグランドタスク(Varnishでいう所のbanlurker相当)を即席で実装したりしてスコアを伸ばすもトップには届かず敢え無く終了。

振り返ってタイムスタンプを見るとギリギリまで粘っていたのが分かりますね。

-rw-r--r-- 1 root root 2322 Oct  5 17:59 /etc/supervisord.conf

結果、送信できた最終スコアは「20599.5」でした(ローカルスコアは22000ぐらいだったけど)。 予選突破(暫定)はできたものの、まだまだ課題山積な感じです。

良かった点

  • 最初に戦略とスケジュールを策定できた
  • 去年学んだ捨てる勇気を持てたこと
  • 去年よりいろいろ試せたので敗北感は改善
  • オンライン参戦なので普段使い慣れてる環境で落ち着いて出来た(本戦はアウェー)
  • 今年の密かな目標であるモリスさんに勝ったこと(予選だけど)

反省点

  • 作業ログをもっとしっかり取るべき
  • バージョン違いはgit mergeせずにbranchをcheckoutするべき
  • lingrサポート見ればよかった
  • MySQL5.6のMemcache APIの存在には気づいてたけど、罠とは知らなかった
  • Newrelic導入に時間かけた割には得られる情報量が薄かった
  • リバースプロクシが遅れたのは全実装して投入を試みたからでincremental apporachの方が良かった
  • 全員のタスクマネジメントとペアプログラミングをもっとすれば良かった

今回も前回同様、非常に楽しめました。 運営の皆様、お疲れ様でした&本戦はさらなる期待をしてます!

おまけ

後日行われた各チームの反省会で判明したのがworkloadの存在。どうやらベンチマークツールの並列度を上げる事ができるらしく、そこそこ速くなっているシステムだったらスコアが1.5倍ぐらいは伸びたかもとの事。最終スコアを考えると、30000点台のトップを取れたかもしれないのが無念。。アプローチ自体は総合トップの@sechiroさんと似ていたので方針は間違ってなかったかと。

AWS Game Day Tokyo 2013で受賞してきた

大統領選挙でオバマ陣営のシステムを堅牢化する為に用いた手法である仮想対戦シミュレーション「AWS Game Day Tokyo」が日本で初めて(世界2番目に)開催されたので参加してきました。

結果、ベスト・ディフェンス賞こと「Most Awesome Fix!」賞を受賞しました。

経緯

以前、JAWS DAYS 2013でMiles Wardが講演した「Behind the Scenes of the Presidential Campaign」でチームを攻撃・防御に分けて対戦させ、そこから学んだ事をフィードバックしてシステムをより堅牢にするという「Game Day」を知り、日本でもやりたいねという話になってました。そこで先日のAWS Summit Tokyoのスピンオフイベントとして、Milesの再来日に合せてADSJ(アマゾンデータサービスジャパン株式会社)さんによって開催される事になりました。チーム戦の大会はチューニンガソンISUCON以来なので、わくわくしながら速攻で応募をしました。

概要

大体、こんな流れです。

  • システムの構築・堅牢化
  • 相手システムの攻撃(この間、自システムも攻撃される)
  • 自システムの修復
  • 評価

それぞれ、2〜3人のチームに別れ、計18チームにより対戦。私のチーム名は時事ネタとして今流行りの「PRISM」としました。 システム概要はnginksさんのブログが非常に分かりやすいです。 要するに画像変換処理バッチクラスターですね。

構築

手順書をざっと見ながら画像処理クラスターを構築。相方はシステムの人間ではなかったので、動作確認を手伝ってもらいつつ実質一人でもくもくと作業。s3作成、sqs作成、アプリのインストール・設定・動作確認、AMI化、Cloudwatch設定、AutoScaling設定等を淡々と。構築しながらシステムを把握して行くけど、結構これだけで時間がとられます。なので、じっくりと防衛策は練れなかったのでひとまず、プロセスの自動復旧をしてくれるmonitをインストール・設定し(upstartでやりかけたけどうまく動かなかった)、主要ファイルのchecksumを取って改善検知してメール通知する仕組みを導入。

攻撃

AWSキー(Poweruser権限)を奪取したという仮定の元、相手システムに攻撃をしかけるターン。 単純に全部消したり、セキュリティグループの権限を変えたりではあまりにもつまらないので、いろいろ考えます。(無論、システムの全消しは誰でもできる最低の攻撃手法)

まず、状況把握する為にいろいろ動作確認。キューに画像を突っ込んで、ちゃんと処理されるとか。あれ、でも動かない。。 どうやらTokyoで作りかけたけど、結局Virginiaリージョンで仕上げたと運営側から伝えられる。いきなりのタイムロス!

気を取り直して、稼働中のインスタンスに入る方法を思いつく。通常はキー設定されているのでsshでは入れないので仮のインスタンスを起動し旧インスタンスのroot volumeのEBSを強制detachし、仮インスタンスにattachして中身をいじってからre-attachする事に。見た目は同じinstance-idなのに中身だけ違う、一見すると分かりづらいです。そこで旧インスタンスを一旦停止させる為にstopさせると、、terminateされちゃいました。。よくよく調べて見ると、Auto Scalingの設定になっていて、min-sizeの制約によって旧インスタンスが消され、代替インスタンスが自動的に起動するようになってました。

どうやら構築が間に合わなかったチーム用に運営側が用意した自動構築をしてくれる虎の子のCloudformationを使った模様。 そこで、相手チームのスキルレベルがそれ程高くないと判断し、Auto Scalingの元AMIを置き換える事に。 新たなlaunch configを作成し、既存のscaling groupと同盟のものを作成。

次にs3への攻撃。bucket名はglobalなnamespaceなので、こいつを削除して同名のを別AWSアカウントで作れば乗っ取りが可能。。 のはずが、削除してから一定時間経過しないと作成不可だったので1字違いのbucketを作成しておく。

最後にs3のbucket一覧を取得して、常に空のディレクトリと同期し続ける攻撃を思いつき、実装を始める。システムはs3に出力するのでそこを継続的に空にする攻撃です。しかし、実装を初めて動作確認の途中で時間切れになりシステムに埋め込めなくて断念。もうちょっと時間が欲しかったです

修復

次は自システムが受けた攻撃を修復するターン。

いろいろ余計なインスタンスが起動していたが、まずやったのがAMI番号の確認。(これが無事であればOSに侵入されていようがAMIをベースに全体の再構築が速やかにできるので) 幸い、控えていたのと一致していたので他のインスタンスを全て停止。一応monitのアラートメールが飛んでいなかったので、インスタンスに対しての操作は限定されているのだろうとは踏んでましたが。

AMIが無事なら次はAuto Scalingの確認。ざっと見た感じだと、lauch configは操作されておらず、scaling groupのmin-sizeが0に変更されている模様。他の変更点は確認が面倒だったので、一旦全部削除してさくっと再作成。後で聞いた話だと、Auto Scalingのrecurring schedule設定で1分起きに0台にする設定をしていたらしいが、消された時点で攻撃は無効化。

次にSQS。消して再作成すればてっとり早いけど、相手チームがキューに投入した画像を最終的に表示させないといけないルールだと誤解していて、その復旧に務める。新しく作ったSQSと比較するとパラメータ(Default Visibility Timeout, Retention Period, Message Size等)が異常な値に変更されていると分かり、通常の値へ戻す。

この時点でアプリとSQSの通信を確認するも疎通できない事を把握。pingが通らない事からSecurity Group, Routing Table, Network ACLが変更されていないかを確認。どうやらSecurity GroupのIn/Outルールが削除されている単純な攻撃だと判明し、なんなく再設定。

キュー内のメッセージが1コ処理されるのを確認し、SQS周りは対応済みかと思ったけど残り2コのメッセージがいくら待てども処理されず、若干悩む。 ログを見たり、メッセージの中身を覗くとと「–max-redirect=99999999 」が目に留まる。どうやら変換する画像をダウンロードする部分で無限ループさせている模様。メッセージを削除し、そのパラメータを除外したものを流してちゃんとキューが処理される事を確認。

最後にs3周りで怪しい設定がないかを調査して、一通りの動作確認をして復旧完了。

振り返り

お互いに対戦したチームと顔合わせをし、攻撃や復旧の手の内を明かします。全チームの行動記録を集約して運営側で審査を行われ各賞が授与される中、我がPRISMはSQS内の無限ループを検知・修復したのが評価されて最も優れた修復を行った「Most Awesome Fix!賞」を頂きました。後で他のチームに聞いた所、monitのような検知・通知の仕組みを導入した所はなさそうだったので、それも評価ポイントだったかも知れません。

賞の内容としては、ワンタイムトークンを生成するハードウェアMFAデバイスとAWSのクーポンコードでした。ありがとうございます。

最後にMilesが壊れても戻せるようにあるべき状態の定義と常に比較して自動的に自己治癒するのが最高のシステムと言ってました。AWSの状態を保存するにはCloudformerでCloudformationテンプレート化すれば便利で楽だけど、chefみたいにIdempotency(冪等性)を継続的に保証する仕組みをそのレイヤーで組むのはなかなか大変ですね。(個々のサーバ単位は可能だとしても)

感想

チューニンガソン」や私が他にお手伝いをしている「トラブル☆しゅーたーず」とも一味違って、非常に楽しめました。

以下、思った事をいくつか。

  • 構築に時間が取られたのでなるべく出来合いのシステムがあった方が防衛策に専念できそう
  • 攻撃可能な時間が思ったより短かったのでもうちょっと長めで
  • Default-VPCとEC2-Classicでは挙動が違うのでアカウントタイプは統一した方が良い
  • ターン性ではなく、攻撃と修復・防御のリアルタイム性を試すとか
  • あるべき正しい状態を把握する為のツールがあると他のタスクに集中できるかも

次に開催される時も参加したいですね。もしくは運営側のお手伝いでも!