Quantcast
Channel: サイバーエージェント 公式エンジニアブログ
Viewing all 161 articles
Browse latest View live

WWDC '14 レポート - 前編

$
0
0

こんにちは。
Ameba事業本部でiOSアプリを開発している @tasanobu です。

みなさん、WWDCのキーノートはご覧になりましたでしょうか?
Swift の発表は衝撃的でしたね!
今年のキーノートでは OS X, iOS 8、SDKの新機能などが盛りだくさんの発表でしたが、
Swiftが話題の多くを持っていったんじゃないでしょうか。

さて、私は6/2から開幕したWWDCに参加するため、サンフランシスコに来ております。
幸運にも入手困難なチケットを手に入れて参加してますので、WWDCの雰囲気や現地で感じたことなどをお届けしたいと思います。

なお、ご存じの方もいらっしゃると思いますが、WWDCではキーノート以外は守秘義務が課せられており、セッション内容を一切公開できません 。
また、既にキーノートの内容は様々なメディアで報道されます。
そのような事情もあり、私の WWDC 2014 のレポート は次のような2部構成でお届けさせて頂きます。
  • 前編: キーノート開幕前の様子
  • 後編: WWDCに参加して感じたことなどのまとめ

今回は前編として、開幕前日とキーノートが始まるまでの様子をお伝えします。

開幕前日の様子

"開幕当日だと受付が大行列になっていて、Keynoteに間に合わないリスクがある" という情報を現地に行く前に入手したので、事前受付をするために会場の MOSCONE WEST に行ってきました。

前日なので当たり前と言えば当たり前ですが、会場は既にWWDCモード!
一気にテンションが上がりました。



こちらは会場内の受付前の様子です。デカデカと 8 の文字が掲げられております。
こういうのを間近で見せられると、キーノートへのワクワク感が高まりますね!



受付作業は、事前にAppleから送られてくるPassbookのチケットとパスポートを受付の人に見せるだけで完了です。
サクッと入場チケットをGETできました!



キーノートまでの長い道のり

初日のメインイベントはキーノートです。
出来るだけいい位置で見たいということもあり、開場待ちの行列に並びました。

予想はしていましたが、午前3時の時点で既にこの行列です。。。

Untitled

午前7時頃、会場への入場が始まりました。
防寒はしっかりしていたのですが、明け方になるにつれてドンドン冷え込んできてつらかったので、ホッとしました。
(注:日本はもはや夏モードなんで想像がつかないかもしれないですが、サンフランシスコは結構寒いです。)

Untitled

会場に入ったらすぐキーノートのホールに通される訳ではなく、廊下で2時間近く待たされます。
それにしても、Geekだらけのすごい光景ですよね。

Untitled

キーノート開始の1時間前になると、ようやくキーノート会場に入ることができました。
長時間の頑張りによって、なかなかよい席を確保できました。



Swiftに代表される衝撃的なキーノートをプレゼンターの表情が分かる位置で見れて、夜中の三時から並んだ甲斐がありました。





長文・乱文 失礼しました。
ここまで読んで頂いた方にキーノートまでの様子を少しでもお届け出来ていたら幸いです。

6/3からのいよいよセッションが始まります。
SwiftやiOS SDKに追加された新機能など、どんな話しが聞けるかとても楽しみです!

WWDC '14 レポート - 後編

$
0
0

こんにちは。
Ameba事業本部でiOS開発をしている @tasanobu です。

昨日の6/6をもってWWDCは全日程を終了しました。
一週間なんてあっという間ですね。
これから帰国の途に就くにあたり、すごく名残惜しい気持ちです。

さて、今回は  WWDC '14 レポート - 前編 に続き、後編としてWWDC参加期間中に感じたことをお伝えしたいと思います。
WWDCに興味がある方の参考になれば幸いです。

幅広く最新情報を得ることができる

WWDCのセッションビデオはご覧になりましたでしょうか?
私の場合、WWDCに参加していない昨年までは、タイトルをざっとチェックして気になったセッションに絞って見ることが多かったです。
日本にいると、日々の業務に追われたり、私生活に時間を割かなければなりません。
そうなると、どうしてもチェックするセッションは仕事に関係がありそうなものに限られてしまい、特定のフレームワークをピンポイントにチェックするだけになってしまいがちです。

サンフランシスコに来てWWDCに参加した場合、いい意味で日常から隔離されます。
WWDCのようなテックカンファレンスへの参加経験があると分かると思いますが、ある時間帯では聞きたいセッションが被っていたり、逆にある時はタイトルを見ただけでは重要さが分からないセッションしかないようなことがあります。
そういった中で、五日間も一日中セッションに参加する生活が続くと、もともと興味があったセッションだけではなく、"他よりはこっちの方がいいかな"っといったレベルで聴講したセッションからも重要な情報を得ることがあります。
実際、なんとなく選んだ Web Kit や Store Kit のセッションから多くのことを得ました。
現地に参加しない場合だと、なかなかこういった形で幅広く情報を仕入れることは出来ないのではないかと思います。

WWDC期間中、LABと呼ばれるAppleのエンジニアに質疑できるスペースが開設されています。
日々のアプリ開発で直面している課題やセッションを聞いて疑問に思った点などを、担当者レベルのエンジニアに直接質問できます。
私はセッションを聞いて気になった点をいくつか質問して、その場で回答をもらいました。
このようにすぐに疑問点をクリアにできるというのは、WWDCに参加する意義の大きな一つかと思います。

参加者コミュニティに飛び込むことの大切さ

今回の出張は非常に充実していたのですが、サンフランシスコへの渡航前・WWDC期間中に知り合った方々のおかげです。
WWDCへの参加は今回が初めてだったため、キーノートの行列に並ぶか否かから始まり、セッション前後の過ごし方などに不安がありました。しかし、日本人の参加者の方々と一緒に行動させてもらったことにより当初の不安は吹き飛びました。
日本人参加者の方と仲良くなれなければ、食事やセッション間の情報交換、現地企業巡りなど、こんなに有意義な時間は過ごせなかったと思います。

at Github Headquarters


at Apple Headquarters


(* Appleの日本人エンジニアの方に敷地内を案内して頂いたのですが、撮影禁止だったため敷地外で記念撮影しました。)

英語力の大切さ

毎回、海外に行くと痛感させられます。
WWDCに参加している海外の方はすごいフレンドリーで、いろんな場面で気軽に話しかけてくれます。
せっかく海外のエンジニアの方と話す機会なのに、相手の話している内容も理解できず、自分の意図もうまく伝えられず、自分の英語力のなさにもどかしい思いをしました。
この悔しさを胸に、帰国後は改めて英語力の向上に励みたいです!
(この気持ちを維持するのが大事ですね。)


iOS 8 のリリースに向けて




iOS8は 4,000以上も新APIが追加された過去最大のリリース というのは伊達ではないですね。
iOSアプリ開発をする立場だと、4,000以上も新APIが追加されているので、ワクワクする反面、既にリリースしているアプリではiOS 7の時を超える様々な修正が必要になりそうです。

このビッグリリースに先立ち、Apple様から "Write the code, Change the World" と言われてます。

ワールドカップを楽しみつつ、秋のiOS 8リリースに向けてしっかり準備をしていきたいと思います!

Amebaの開発環境について

$
0
0

コンニチハ

たぶんサーバサイドエンジニアの@pnskです。


約1年前に設立した、「Ameba Dev. Center」という
「Amebaの開発環境周りに関わる事であれば、何でもやる」というスタンスで
いつか世の中に出しても恥ずかしくない開発環境とその文化をAmebaに根付けられたらなぁと野望を持った組織に所属しています。

さてはて。
今回は、エンジニアブログ執筆の機会をいただいたので、Ameba開発環境についてこしゃべりをしようと思います。

誰得情報ですが・・

Amebaの開発環境ですが、この1年でちょっぴり
変わりました。
(誰も気づいていないかもしれないですけど・・・…。

topicでいうと、GitHubEnterprise、JIRA、HipChatが導入されました。

現在は
・ドキュメント管理はConfluence
・課題管理はJIRA、アジャイル開発補助ツールとしてJIRA Agile
・チャットツールはHipChat
・ソースコード管理はsubversionまたはGitHub Enterprise
がAmeba公式ツールとして利用されています。


まだまだ途中で、課題は山ほどあり、
進めば進むほど課題が新しく出てきて、それに立ち止まり、それに追われ、また進む日々です。

この1年間を振り返って、
・それまでどんな環境だったかと
・それに対してどう対応していって
・これからどこを目指しているか
について書いていこうと思います。

今もまた新しく検証や導入を検討している物がありますが、基本的には
どれも同じような壁にぶち当たり、また同じように解決?していっているので、
特定のツールについて今回は述べす、まとめてみました。

Amebaの開発環境の課題と解決?方法について

課題:同じようなものが沢山ある

Amebaには沢山の同じような物がありました。

ソースコード管理では、複数のgitlab,複数のsubversion、複数のgithubプライベートリポジトリ・・・それから公式ではCVSとsubversion

課題管理では、Redmineが主流で多くあり、Track, Backlog,Brabioなどなど。

チャットツールでは、skypeが主流で、IRC、chatworkとか。

どれが最適か?どのツールが一番効率が良いか?を検討する以前の問題で、

次から次へと新しいものが増えていき、また昔に作られた物は残り続け、
気づいたら沢山の類似ツールがありました。

関わるプロジェクト毎に、様々なツールが利用されていました。

少人数や、サポートできるメンバーがプロジェクト内にいるうちは良いですが、柔軟な組織構成になっているので、メンバーの移り変わりも激しく
管理が難しくなり、新しくアサインされたメンバーの学習コストが課題になりました。

また、統合に向けて、類似物を消している最中に、また増えていくという現象がよくありました。

なぜ・・・
…。

対応1:どれを使うか決めて「統合」

当たり前でシンプルな対応ですが、ある程度の調査の上
「これにする」と決めて、「統合」!!

可能な限りmore betterを決定して、もうやっちゃえ!
仕切り直しは足並みそろえてからでいい。
と企んでいたのは今だから言えるお話。。

新規のプロジェクトは良いですが、既存のプロジェクトについて
「移行」がどうしても課題になるので、

なるべく事前に吸い上げて、移行は可能な限りサポートするようにしました。
ソースコード管理の統合は700リポジトリ以上、移行しましたマリオためいき
個人的には、CVSを撲滅した事が何より大きいひょろり


対応2:組織設立
「開発環境」を管理している組織の知名度の低さにありました。

こういうツール欲しいなって思ったときにすでにあるのかも分からない。
分からないときに、どこに相談したらいいのか分からない。
だから、必要になったら自分たちで立てていく。

これが大きな原因だったんじゃないかと考えました。

GHE導入時は、「経営本部部門 QMグループ」というチームにいて、
経営とかやってないのに、経営やってる人みたいなイメージがついていたんじゃないか?
勝手にそう考えて「Ameba Dev. Center」という比較的分かり易いイメージの組織を設立しました。
※これは他社のGHE管理者の方とお会いしたときにアドバイスいただいたのを参考にしました
ジーッ

あとは知名度があがるまでは、
増えたことをいち早くキャッチアップして抹消しました。

未だに増える事がありますが、以前よりずっと減ってきたのではないかと考えています。

対応3:社内広報活動

以前は重視していなかったのですが、
最近、重要なのかなって心を改めています。

毎週のtopicメール
ふぅ
ジーッうちの公式ツールはこれだよ・・・感。。

時には勉強会も。。

GHEの大々的な勉強会したり
(登壇者は他の人にむちゃぶりしたのはナイショ)、

エンジニア&プランナーさん向けに、別日程で開発ツール全般の勉強会をしました。トータルで約500名の方が参加してくれました。
(これはちゃんと自分で登壇シマシタヨ。。しぶしぶ・・・。終わったその日に熱が出たのはナイショ。。慣れない事をした。。

新卒向けに、勉強会したり。。。

ポスターとかも作ったり。。


そして、こうしてエンジニアブログ書いたり。。



課題:現場との乖離

実際何に困っているのか?をしっかりキャッチアップするにはどうしたらいいのかはとても課題になりました。

Amebaにはたくさんの部門があり、それぞれに、それぞれの特性があるので
共通化すべきこと
共通化すべきでないこと
を知る必要がありました

対応1:各部門のコアメンバーとの定例

基本的には毎週やっています。
どんな些細な事でも、仮に私たちが直接対応できない事でもキャッチアップするようにしました。

検証や導入していくツールが増えるにつれて定例に出席するのが苦しくなり、今はチームの他の方に出席してもらってて私はJIRAのチケットしかみてないですが。。
ジーッ

対応2:Ameba技術代表メンバーと月一定例

「Ameba全体の開発環境を考える会」を月一で開催するようにしました。

「こんなことやろうとしてるよ」がオープンになるように、
Confluenceに議事録を公開状態で残しておくようにしてます。

他社では当たり前のことなのかも知れないですけど、
こういった全体のことを決めようとする議事録がMTG参加者やエライヒトにのみにしか共有されないことが多々あります。

内容によりけりなので、どちらが正解などはないですが、
Amebaの開発環境においては、みんなが関わるので、
どんなことをしようとしているのかが見ようと思えば見れる場所にあればいいのかなぁ・・と。

誰かが議事録を見て「これやろうとしてるみたいですけど、こっちの方がいいかも」などのコメントwelcomeです。

対応3:ひっそり聞く

ひそひそ・・・
マリオ



そんなところでしょうか。。

他は、特に大きなことは何もしていません。

導入を検討するときに共通して確認するのは
・クライアントの対応 (winとかmacとか、、iphoneとか)
・移行サポート手段
・アカウント管理
・api (連携や管理に便利)
・UI
・運用のし易さ(verが上がったときに簡単に対応できるとかも含めて)
コスト(オカネ、ヒト)ワカメ

スタンスとして気をつけているのは
・私たち自身がその手段にこだわりを持たないこと

・実現するためには手段を選ばない事
・more betterを意識する事
・大きなメリット、大きなデメリットを意識する事
くらいです。

ジーッできているかは別・・・。

こういった横軸組織に、どういったスタンスがmore betterなのか、いつも試行錯誤しています。正直ワカラナイwhy?


結局のところ
この1年を振り返って、ちょっぴりでも開発環境が改善できたんじゃないか?
くらいまで持っていけたのは、
職種問わず、現場の開発者のみんなの手助けが一番大きかったかなぁ。。

勉強会の実施を手伝ってくれたり、検証を手伝ってくれたり、
「何か手伝える事はないか?」と聞いてくれる現場のみんなに一番感謝してます。

その言葉だけで私たちは100人力です
筋肉あげ



これからどこを目指しているか

Ameba Dev. Centerという組織の確立

課題は色々とありますが
Ameba Dev. Centerという組織の弱さを多々感じています。
いまは、Ameba Dev. Centerという組織をしっかり確立していくことが課題かなぁと考えてます。

いつも存続の危機と戦っている日々です・・・
ううっ・・・


どんなに組織体制が変わっても、
「Amebaサービスを支える人たちの環境を支える組織が必ずある」
ことが大事なんじゃないかと考えています・・よ。


ともあれ、
Amebaに関わる開発者が困ったときに、
スピード感もってしっかり後押しできるように、

もっと頑張らなきゃ
筋肉ためいき

デザイナーさんの環境改善

この1年はエンジニア周りの環境ばかりやっていたので今期は絶対にやります
(やるやる詐欺になってはいけないのでここで宣言)





私たちが足かせになりませんように・・。


今は現場の人たちに助けてもらう事の方が多いですが

少しずつ、Amebaのユーザサービスを支えてくれている皆さんの足かせを外して、素敵なサービスがユーザの皆さんにいち早く届く手助けができれば嬉しい限りですかおおんぷ


2-0の魔力なんて無かった

$
0
0

皆様こんにちは、アメーバのデータ分析に携わっている和田(@wdkz) です。あと少しでサッカーワールドカップ2014が開幕しそうな今日このごろ(*執筆時点)、サッカー好きな自分としては楽しみで仕方ありません。ちなみに好きな(だった)選手はアルベルティーニ、グアルディオラ、ピルロです。今回のワールドカップも2006年の大会同様に大活躍するピルロを見たいですねー。また、大久保のサプライズ選考で話題となった日本代表にも期待しています。大久保といえば自身の名を冠した「オオクボ」というフェイントがあることは皆さんご存知でしょうか?スペインマジョルカ時代にも試合で使っていたので、ヨーロッパでは「クライフターン」に匹敵するくらいの知名度があるはずです(きっと)。
  というわけで、本記事ではサッカーワールドカップのデータをRを使って分析してみたいと思います。

今回分析する項目
1.  ゴールの生じやすい時間帯があるか
2.  先制点を取ったチームは勝利しやすいか
3.  0-2から1点返して1-2になると、負けているけど追いかけているチームほうが試合に有利になると(松木安太郎さんが)いうあの件は本当か


分析データの取得先
データの取得は以下のサイトをスクレイピングして取得しました。データの充実したサイトなので眺めているだけでも面白い良質なサイトだと思います。
http://soccer-db.net 

取得したデータは、サッカーワールドカップの1982年~2010年の8大会ぶんの全試合のデータです。なぜ1982年からなのかというと、上記のサイトに1982年以降のデータしか無かったからです。
私はR(のXMLライブラリのreadHTMLTable関数)でスクレイピングしましたがRubyやPython等のLLを用いてやるのが一般的だと思います。お鮭の好きな人はselenium何かも使うみたいです。


スクレイピングして作成したデータセットはcsv形式にしてここ置いてありますので適時ダウンロードしてお使い下さい。

分析
0.データの読み込み
Rを起動して上記のcsvファイルを読み込んでみます。
wc_data <- read.csv("wc_data.csv", stringsAsFactors=FALSE)
読み込んだwc_dataの先頭10行を見てみましょう。
head(wc_data, 10)
#library(gridExtra)
#grid.table(head(wc_data, 10)) #今回は便宜上、こちらのコマンドで以下の表を作成した

データセットの先頭表示

読み込んだデータのカラムの説明をします。
game_id 試合を一意に決めるユニークなidです
comp 大会やリーグの名前を表すidですが、今回はワールドカップのデータのみを対象としたので"wc"だけになっています
year 試合の行われた年です
team_category ゴールを決めたのがHome, Awayのチームのどちらかを示すidでそれぞれHかAになっています。ワールドカップは開催国以外はAwayなので今回の分析にHかAかに特別は意味はありません
score その時点でのチームの合計得点です
time 得点時間を表しますが、ロスタイムの得点は45か90にしています

例えばgame_id=2の試合に着目しますと、この試合は1982年のワールドカップのもので3-3であったことがわかります。(timeが91以上の行があるのでこの試合は決勝トーナメントの試合でしょう。)
ではこのデータセットを用いて順番に分析していきましょう。 

1. ゴールの生じやすい時間帯があるか
time_range_df <- as.data.frame.table(table(get_timerange(wc_data$time)))
#                                          ↑get_timerange関数の実装は後述
colnames(time_range_df) <- c("Time range", "Goal counts")
print(time_range_df)
#grid.table(time_range_df, show.rownames=F)  #便宜上こっちで出力

時間帯別ゴール数の表

上の表をグラフに図示してみましょう。

library(ggplot2)
qplot(get_timerange(wc_data$time),
      xlab="時間帯",
      ylab="ゴール数",
      main="時間帯別ゴール数")

時間帯別ゴール数グラフ
時間帯別に見ると、前半(1-45)より後半(46-90)のほうがゴール数が多いことがわかります。後半30分以降(76-90)の時間帯は最も得点されやすいのですね!例え試合に負けていたとしても、最後まで諦めずに試合を見続けた方がいいということがわかりましたね。

2. 先制点を取ったチームは勝利(引き分け含む)しやすいか
firstgoal_towin <- daply(.data=wc_data,  
                         .variables="game_id",  
                         .fun=check_1stgoal_towin)
#                             ↑check_1stgoal_towin関数の実装は後述
table(firstgoal_towin)
#grid.table(as.data.frame.table(table(firstgoal_towin))) #便宜上こっちで出力

先制点とゴールの表
上の表をグラフに図示してみましょう。

qplot(firstgoal_towin,
      xlab="先制ゴールした?",
      ylab="勝利試合数",
      main="先制ゴールは勝利しやすいか")

先制点と勝利のグラフ
グラフを見ると、先制点を取ったチームは圧倒的に引き分け以上に持ち込める割合が高い(82%)ことがわかります。サッカーでは先制点を取ることがいかに大事なことかが明確になりました。

3. 0-2から1点返して1-2になると有利か
return1point_towin <- daply(.data=wc_data,  
                            .variables="game_id",  
                            .fun=check_21win) 

#                                ↑check_21win関数の実装は後述 

table(return1point_towin) 

#grid.table(as.data.frame.table(table(return1point_towin))) 

#便宜上こっちで出力 

1-2からの表

上の表をグラフに図示してみましょう。
qplot(daply(.data=wc_data,
            .variables="game_id",
            .fun=check_21win),
         
      xlab="0-2から1点返して、その後引き分け以上に持ち込めたか",
      ylab="試合数",
         
      main="0-2からの1-2は有利であるか?")

1-2からのグラフ

まず、0-2状態になってから1点返した試合事態が464試合中47試合しかなく少ない(4%)ことがわかりました。その47試合の中で負けていたチームが逆転勝利したもしくは同点で終わった試合が7試合しかありませんでした(15%)。
この割合が多いのか少ないのか比較対象がないとイマイチわからないと思うので、次は0-1状態になったチームがその後逆転勝利したもしくは同点で終わった試合の数を調べてみました。
before1point_towin <- daply(.data=wc_data,
                            .variables="game_id",
                            .fun=check_10win) 
#                                ↑check_10winの関数定義は後述 

table(before1point_towin)

#grid.table(as.data.frame.table(table(before1point_towin)))  #便宜上こっちで出力

0-1からの表

上の表をグラフに図示してみましょう。
qplot(.data=wc_data, .variables="game_id",  .fun=check_10min),
 
     xlab="0-1から引き分け以上に持ち込めたか",

      ylab="試合数",
      main="【比較】0-1からの")

0-1からのグラフ

 0-0以外の試合が全部で424試合あり、その中で先に失点してもその後逆転勝利したもしくは同点で終わった試合が120試合ありました(28.3%)。
1-2から同点もしくは逆転になる(松木さん仮説)より、0-1から同点もしくは逆転になるほうが高い割合であることがわかりました。
 
最後に、1-2からの同点・逆転劇が0-1からの同点・逆転劇よりも有意に異なっているか(有意に高い比率で生じるかどうか)をフィッシャーの正確確率検定を用いて(片側)検定しました。
mat <- as.matrix(
                 merge(as.data.frame.table(table(daply(.data=wc_data, .variables="game_id", .fun=check_21win))),
                        as.data.frame.table(table(daply(.data=wc_data, .variables="game_id", .fun=check_10win))),
                       by.x="Var1", by.y="Var1"        
                       )[,-1]
                ) 
colnames(mat) <- c("1-2", "0-1"); rownames(mat) <- c("lose", "win"); mat <- mat[2:1,] 
print(mat) 
#grid.table(mat) #便宜上こっちで出力

2x2分割表

fisher.test(mat, alternative="greater") 
# 
#Fisher's Exact Test for Count Data  
#  
#data:  mat  
#p-value = 0.9876  
#alternative hypothesis: true odds ratio is greater than 1  
#95 percent confidence interval:  
# 0.1920788       Inf  
#sample estimates:  
#odds ratio  
#   0.44398
「1-2からの同点・逆転劇」が「0-1からの同点・逆転劇」と同じ比率から生じるという帰無仮説に対して、p値が0.9876という有意差の無い結果となりました。つまり、98.76%の確率で「1-2からの同点・逆転劇」が「0-1からの同点・逆転劇」より起こりにくいということです。

【番外】関数
#ゴールした時間をレンジ幅ごとに集約する関数
get_timerange <- function(x=wc_data$time, time_range=15){
   x <- x[-which(is.na(x))]
   tgroup <- seq(from=1, to=121, by=time_range)
   if(tgroup[length(tgroup)]!=121){
     tgroup <- c(tgroup, 121)
   }
   res_vec <- rep(NA, length=length(x))
   for(i in 1:length(x)){
     res_vec[i] <- tgroup[max(which((tgroup - x[i])<=0))]
   }
   conv_tbl <- data.frame(key=tgroup, value=paste0(tgroup, "~", tgroup[-1]-1), stringsAsFactors=F)
   conv_tbl$value <- factor(conv_tbl$value, levels=paste0(tgroup, "~", tgroup[-1]-1)[-length(tgroup)])
   res_vec2 <- apply(as.data.frame(res_vec), 1, function(y){conv_tbl[which(conv_tbl$key==y),"value"]})
   return(res_vec2)
}

#0-1チームが、逆転勝利・引き分け結果になったか調べる関数
check_10win <- function(x){
   if(nrow(x)==1){
     if(is.na(x$score)){
       return(NA)
     }else{
       #1-0
       return(FALSE)
     }
   }else{
     precursor <- x[1, "team_category"]
     g_res <- unlist(dlply(.data=x, .variables="team_category", .fun=function(y){max(y$score)}))
     if(length(g_res)==1){
       #一方は0 score
       return(FALSE)
     }else{
       if(length(which(max(g_res)==g_res))==2){
         #同点
         return(TRUE)
       }else{
         return(names(which(max(g_res)==g_res))!=precursor)
       }
     }
    if(g_res[1]==g_res[2]){
       return(TRUE)
     }else if(precursor != names(which(max(g_res)== g_res))){
       return(TRUE)
     }else{
       return(FALSE)
     }
   }
}

#0-2からの1-2チームが、逆転勝利・引き分け結果になったか調べる関数
check_21win <- function(x){
  if(max(x$score)<2 || nrow(x) < 3){
    #2とってない    
    return(NA)
  }else{
    if(x[2,"score"]==2 && x[3, "score"]==1){
      #2-0からの2-1
      loser <- as.character(x[3,"team_category"])
      g_res <- unlist(dlply(.data=x, .variables="team_category", .fun=function(y){max(y$score)}))
      if(g_res[1]==g_res[2]){
        return(TRUE)
      }else if(loser==names(which(max(g_res)==g_res))){
        return(TRUE)
      }else{
        return(FALSE)
      }
    }else{
     return(NA)
    }
  }
}


まとめ
①先制点大事
②0-2とされたら1点返して1-2となっても、試合展開が有利になることなんて無い
③とはいえ、試合終了間際に最も点が入る確率が高いので最後まで応援する価値あり

以上、技術の話は皆無でしたがここら辺でおしまいにします。

コードのバグはコードで見つけよう!

$
0
0

こんにちは。
アメーバピグでNode.jsを使って開発をしている中村と申します。
平日はエンジニア、土日は主夫として働いています。

さて、早速ですが、この記事ではESLintを使って、JavaScriptのソースコードのバグを発見する手順をご紹介したいと思います。

ESLintとは

ESLintはNicholas C. Zakas氏が中心となって開発しているJavaScriptのLintツールです。

JavaScriptのLintツールといえば、最近ではJSHintが定番だと思います。
ESLintはJSHint同等の機能を持つ他、解析ルールが完全にプラガブルになっており、独自ルールを自由に追加できるという特徴があります。

例えば、JSHintでいうところの、strict(strict modeで実行されるかをチェック)というオプションは下記のURLのように個別のルールとして実装されています。
https://github.com/eslint/eslint/blob/master/lib/rules/strict.js

ESLintを使ってみる

では、次に基本的な使い方を説明したいと思います。

インストールはnpmで一発です。
$ npm i -g eslint

ESLintでは.eslintrcというファイルでルールのON/OFFを設定します。
.eslintrcは下記のようなJSON形式で記述します。
{
    // コメントも可能
    "env": {
        "node": true, // Node.jsで実行された場合のグローバル変数追加&ルール適用
        "mocha": true, // mochaで実行された場合のグローバル変数追加
    },

    "rules": {
        "strict": 1 // 0: off,  1: warn(exit codeに影響なし),  2: error
    }
}

.eslintrcをプロジェクトのルートディレクトリに配置し、下記のように実行します。
$ cd project_root # プロジェクトのルートディレクトリに移動
$ eslint test.js

test.js
 1:0 warning Missing "use strict" statement strict

✖ 1 problem
設定したルールに違反している場合、警告が表示されます。

ESLintに独自ルールを追加する

では、基本的な使い方を学んだところで、次にESLintに独自ルールを定義する流れを見ていきたいと思います。

ESLintの仕組み

まず、ESLintの仕組みを説明します。
ESLint内部で行われている処理は大まかにはこの図のような流れになります。

eslint

1. JSファイルからソースコードを文字列として読み込む
2. ソースコードをEsprimaに渡し、AST(Abstract Syntax Tree)に変換
3. ASTを個別ルールでチェック
※メモ: EsprimaはJSをASTにパースするライブラリ、estraverseはASTを楽に処理するためのライブラリです。

要は、ESLintがASTを個別ルールのプログラムに渡してくれるので、ASTをチェックする3の部分だけ実装すればよいということになります。

ここで、ASTという言葉が出てきましたが、コードをJSONで表現したものというくらいで理解していただければと思います。

例えば、
var hoge = 1;
というソースコードはEsprimaでパースすると
{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "hoge"
                    },
                    "init": {
                        "type": "Literal",
                        "value": 1,
                        "raw": "1"
                    }
                }
            ],
            "kind": "var"
        }
    ]
}
のようなJSONに変換されます。

なお、Esprimaのデモでブラウザ上でコードからASTへのパースを試せるので、触ってみると理解が深まると思います。

※メモ:Esprimaがパースした結果のASTはMozillaのSpiderMonkey ParseAPIの仕様に基づきます。より理解を深めたい方はドキュメントを読んでみてください。

発見したいバグ

続いて、今回発見したいバグについて説明します。

Node.jsでcallbackの第一引数のエラーの有無でエラーハンドリングするのはベーシックな方法ですが、その際、下記のようなバグのあるコードを書いてしまう可能性があります。
function write(filename, data, callback) {
    fs.writeFile(filename, data, function(err) {
        if (err) {
            callback(err);
            // returnを忘れた。。
            // これではcallbackが二回実行されてしまう。。
        }
        callback();
    });
}

コイツを見つけてみたいと思います。

実装手順

eslint-testerというESLintのテスト向けユーティリティが提供されているのでこれを利用しながら開発するのがよいかと思います。テストフレームワークとしてはmochaを使う想定です。
1. ルールファイル(lib/rules/no-callback-return.js)を書く。

2. .eslintrcを書く。(ファイル名から.jsを取り除いた名前がルールのIDになります。)
{
    "env": {
        "mocha": true,
        "node": true
    },
    "rules": {
        "no-callback-return": 2, 
    }
}

3.テストファイル(tests/lib/rules/no-callback-return.js)を作成。validの配列に正しいコード、invalidの配列に不正なコードを足していく。(なお、ソースコードを文字列では書くのは辛いので、heredocという関数を定義して、ソースコードを書きやすくしてみました。)
var eslintTester = require('eslint-tester');

function heredoc(func) {
    return func
        .toString()
        .split('\n')
        .slice(1, -1)
        .join('\n');
}

eslintTester.addRuleTest('lib/rules/no-callback-return', {
    valid: [
        {
            code: heredoc(function() {/*
                function test(err, callback) {
                    if (err) {
                        callback(err);
                        return;
                    }
                    callback();
                }
            */}),
        },
    ],
    invalid: [
        {
            code: heredoc(function() {/*
                function test(err, callback) {
                    if (err) {
                        callback(err);
                    }
                    callback();
                }
            */}),
            errors: 1,
        },
    ]
});

4. テスト実行→コード修正・・・を繰り返す。
$ mocha tests/lib/rules/no-callback-return.js

※メモ:ルールの書き方で困ったときにはESLint 標準のルールの実装が参考になるので、目を通してみるとよいと思います。

コード

上記のバグのあるソースコードを発見するルールはこんな感じになりました。
var estraverse = require('estraverse');

module.exports = function(context) {
    "use strict";

    var ifStack = [];
    var callbackRe = /callback|^(?:next|cb|done)/i;

    function checkCallback(node) {
        // callbackっぽいか
        if (!callbackRe.test(node.callee.name)) {
            return;
        }

        // ifブロック内ではない
        var ifNode = ifStack.length && ifStack[ifStack.length - 1];
        if (!ifNode) {
            return;
        }

        // elseあり
        if (ifNode.alternate) {
            return;
        }

        // if (callback) { callback(); } のパターン
        var isCallbackTest = ifNode.test.type === 'Identifier' &&
            callbackRe.test(ifNode.test.name);

        if (isCallbackTest) {
            return;
        }

        var returnExists = false;

        estraverse.traverse(ifNode, {
            enter: function(node) {
                if (node.type === 'ReturnStatement') {
                    returnExists = true;
                    this.break();
                    return;
                }

                if (node.type === 'FunctionStatement' || node.type === 'FunctionDeclaration') {
                    this.skip();
                    return;
                }
            },
        });

        if (returnExists) {
            return;
        }

        context.report(node, 'no callback return!');
    }

    function startIf(node) {
        ifStack.push(node);
    }

    function endIf() {
        ifStack.pop();
    }

    return {
        "CallExpression": checkCallback,
        "IfStatement": startIf,
        "IfStatement:exit": endIf,
    };
};

実行

それでは実行してみます。
$ eslint --reset --config ./.eslintrc --rulesdir lib/rules/  test.js

test.js
  4:12  error  no callback return!  no-callback-return

✖ 1 problem
バグが見つかりました!

なお、各オプションの意味は下記の通りです。
--reset 全てのオプションをOFFにする
--config 指定した設定ファイルを使用する
--rulesdir 独自ルールを定義したファイルのあるディレクトリを指定する

上記のサンプルコード一式はhttps://github.com/yukidarake/eslint-exampleに上げました。興味のある方はご覧ください。

まとめ

以上、ざっとですが、ESLintで独自ルールを追加する手順を見ていきました。

ESLintは以前は解析速度がJSHintと比較してかなり遅いなど、実戦投入するにはちょっとためらう面もありました。しかしながら、現在では、速度の問題も解消され、実用フェーズに入ってきた感があります。JSHintを使っているプロジェクトでも、足りないチェック機能をESLintで追加していく形で導入するのはありかなと思います。

それでは最後まで読んでいただき、ありがとうございました!
この記事が少しでも皆様のお役に立てば幸いです。

SDNな日々

$
0
0

こんにちは、Amebaでネットワーク エンジニアをしている篠原です。
今日は現在Amebaで取り組んでいるネットワークのSDN(Software 
Defined Network)について少し解説出来ればと思っております。
かなりインフラよりで抽象的な話になりますが宜しくお願いします。

まずは今私が取り組んでおりますSDNに関してはまだ正式にメーカ
からリリースはされていないのですがC社(わかってしまうかと思いま
すが)様の新SDN製品を採用してリリースする予定です。

こちらは今年2月からアメリカでトレーニングを受け、ようやく現在実機
でTest段階といったところまでこぎつけた感じです。


ところで SDNって何が出来るのって? 良く言われます。

一言でいうと  私は なんでも出来るネットワーク かなと思っております。

SDNというと自動化とか抽象化とか 様々な表現で表されますが中身の
コンセプトの部分は単純で今までハードウェア個別で行ってきた機能、
例えばSwiting,Routing,Load Balance, Firewall 等を一つのネットワーク
機器(実際には複数の同一タイプの機器)で実装してしまおうという感じです。

ちょっと簡単ですが図を書いてみました。あくまで一例ですが

図1 今までのネットワーク

いままでのネットワーク

図2 SDN

SDN



SDNってシンプルですよね。

SDNではSpine(幹)とLeaf(枝)の2つのタイプのネットワーク機器で組み合わ
さったFabric構成(網目の様なネットワーク)で構築する事が一般的です。

ではなぜSpineとLeafと呼ばれる2つのネットワーク機器で様々なネットワ
ーク機能が提供出来るかというとそれを実現させているのがSDN Controller
という制御機器になります。

実際にSpineとLeafはこれまでのネットワークで利用してきたL3 Switchや
L2 Switchと見た目は何も変わらないただのSwitchです。

実際SDN Controllerがなければ一般的なSwitchとして動作する機器が大半です。

SDN ControllerはServer上に実装されたSoftwareでこちらがSpineとLeafに対して
ネットワーク系の指令 実際にはどの送信元IP(MAC)のパケットをどの宛先IP(MAC)
 に転送させるにはどのSwitchのどのPortから出力させればいいかという様な制御を
行っています。一般的なルーティングですね。

今までのネットワーク機器ではそのような各転送パケットを制御する部分(Control Plane)、
と実際にDataを転送する部分(Data Plane)の2つの役割を各それぞれの機器が実装して
おりました。

それが一括集中制御される事により各機器のリソース消費の低減や各機器個別の設定
が必要なくなるといった大きなメリットを享受できます。

また今まで何階層にもなっていたネットワークが2層のネットワークになる事によりパケット
を転送する際のレイテンシ(転送遅延)の低減も期待出来ます。

機器の全体的な数も減らす事が出来る為故障率の低減にも寄与出来ます。

実際にはもっと複雑な機能も提供可能です。

何千、何万のHost,VMを収容するData Centerネットワークではネットワークの機器だけ
でも数百台規模となり運用が非常に大変です。

例えば新しいTenant上にVMを1つ作成する場合ネットワーク作業としては

0. Cloud Controller上でVMの作成
1. L3 SwitchにVRF(論理パーティション)の作成
2. L3 SwitchにVlanの作成
3. L3 SwitchのVlanにIpを設定
4. L2 SwtichにVlanの設定
5. Load BalancerのPoolにVMを追加
6. FirewallにLoad balancerのVS Ipの開放

といった様に6つの作業が有ります。(あくまでも1例です)

そこでSDN Controllerを使い各機器を一括で制御した場合どうなるかというと

0. Cloud Controller上でVMの作成
1. SDN Controllerから各Spine,Leaf,LB,FWに対してVRF(論理パーティション)の作成、
Vlan作成、VlanへのIP採番、Load BalancerのPoolにVS追加、FirewallにVS IP追加

SDN ControllerとCloud Controllerとを連携させる事でここまで作業がシンプルになります。

それはSDN Controller上ではAPIを標準で実装しているものが大半の為 
OpenStackの様なCloud Controllerからネットワークの制御が容易に行える為です。
各メーカからのプラグインも多数用意されております。

私はこれがSDNの最大のメリットと思っております。

実際には我々ネットワーク エンジニアの仕事ってどうなるんだろうとか思ったりしますが。。

まあそれはさておき話をCloud Controller連携に戻すと

例えばOpenStack連携ですと上述の様にOpenStack上で作成したTenant(論理的に分割
されたNetowkr,VM郡)をSDN Controller側のAPIに対してネットワークの変更をすぐに
実施させる事が可能になります。
そうすればVM作成と同時にそのVMに対して接続可能なネットワークが提供される訳です。

これっていかに早くサービスを提供するかが命のアプリケーションエンジニアからする
とインフラを素早く提供出来る事はとっても大きなメリットだと思います。

ビジネスインパクトとしても大きな事でタイミングをいかに早く出来るかに
よって流行る、流行らないも左右されますのですごく大切な要素かと思っ
ております。

では逆にSDNのデメリットというと

1. まだ過渡期のネットワーク技術の為安定性に乏しい

2. 大規模なネットワークの実績が少ない

3. まだまだ足りない機能が多い

4. 標準化が進んでいない

などなど結構私が今感じているだけでも色々有ります。
どれも新しい技術、製品で有れば必ずと言っていい程あり得る話なので
この辺は覚悟を持って取り組まないといけないかと思っております。

但し後ろを向いても仕方が無いのでむしろメリットを最大限に活かす事の方が
チャレンジしがいも有りエンジニア冥利に尽きると思って取り組む様にしております。

現在もSDNのTestを色々と行っておりますが例えばIPV6等の特殊な機能に関しては
現在のSDNではまだまだ機能実装そのものがされて無かったり、一部機能が実装さ
れてなかったりと色々障壁は確かに有ります。

但しそのあたりもレガシーネットワークと組み合わせて使用したりして補完すればクリア
出来る課題です。後でSDNに組み込むのは大変ですが。。


ここまでは少しSDNの抽象的な話を書いてきましたがここからは少し具体的に何がどう
今までのレガシーネットワークと異なるかを説明したいと思います。


図3 一般的なHost間(いわゆる East,Westトラフィックの流れ)

Host AからHost Bに対してパケットを送信した場合
(Host AとBは別Subnet)

いままでのRouting

通常 Host Aが接続しているTOR Switchから上流のL2 Switch
を経由してL3 SwitchでRoutingを行い再度 L2 Switchを経由してHost B
が接続されている TOR Switchへと転送されます。

一見自然な動作に見えますが Routingという機能をL3 Switchで提供している限り必ず
そこを経由する必要があります。
小規模なネットワークですとそれ程Trafficの量も気になりませんが大規模になるとこの
小さなTrafficも無視出来ません。
ネットワークの世界では DHCPやArp、その他制御Packetが常時ネットワークを流れて
いる為ですね。


ではSDNの場合のパケットの流れを見てみましょう。

図4 SDNでのRouting

SDNのRouting


1階層少ないだけでかなりすっきりしました。
更に L3 Switchの機能は各Leafで実装している為実は Host AからDefault Gatewayに
Pingを行った場合返答するのは Host Aが接続しているLeafになります。
SDNで作ったネットワークでは無駄なパケットは他に流れないメリットが有ります。

特に大規模のネットワークの場合この恩恵は機器のサイジングに影響してくる為
コストメリットも出てきます。
SDNはそういう点でも非常に理想的と言えますね。

レガシーネットワークの場合 例えば実際の帯域が10Gbps確保されていても有効活用が
上述の様に難しい為 どうしてもTrafficの偏りが発生して 平均しても10%未満の1Gbps程度
しか利用されないネットワークが多いです。

SDNでは交通整理が末端(Leaf)で行われる為比較的上流の帯域を有効活用出来ます。
具体的にいうと上流の帯域をそれ程用意せず複数にトラフィックを分散させて効率よく
ネットワークを運用する事が可能です。



終わりに

まだまだSDNには未成熟な部分も多いですが今後クラウドを含めたネットワークの主流
となってくる事は間違いないと思いますので今後もSDNには期待を膨らませたいと思います。

また今回は我々のSDNの機能としてはまだ実装しておりませんが Load BalancerやFirewallもSDNの機能の一部として実装して行きたいと思っております。
そこまで実装して初めて Software Defined Networkと言えるのではと思います。

AmebaとしてもネットワークをSDN化する事により大きなメリットが出ると考え今Testを行っております。

ネットワークのPortability化 を目指しロケーションを意識せずに利用出来るネットワークを目指し今後も構築を進めて行きたいと思います。

まだまだ過渡期の技術では有りますが今後標準化されていく事は間違いないと思っておりますのでSDNとネットワーク エンジニアに今後ますます注目していって頂ければと思います。

おすすめオブジェクト指向練習方法

$
0
0

はじめに

みなさんはじめまして。
アメーバ事業本部ゲーム部門でJavaエンジニアをやってる朝倉です。


突然ですが、みなさんはオブジェクト指向できてますか?こんな風に考えていませんか?
・Javaでプログラミングしてるから
・UMLで設計しているから
・継承やインターフェースを使ってるから

残念ながら、これらはオブジェクト指向ではありません。

オブジェクト指向とは、
責務をもったオブジェクトがお互いに協調してシステムを構築する
ことです。

頭では理解できても、実際にコードに適応することはなかなか難しいですよね。
楽器やスポーツのように、オブジェクト指向も練習しないと身に着けることができません。

そこで今回は、オブジェクト指向エクササイズという、オブジェクト指向の練習方法を紹介します。


オブジェクト指向エクササイズって?

「オブジェクト指向エクササイズ」とは、
書籍「ThoughtWorksアンソロジー」の「第5章 オブジェクト指向エクササイズ」で紹介されている、
優れたオブジェクト指向設計の原理を自分のものにし、実際に使えるようになるためのエクササイズです。

ほぼ必然的にオブジェクト指向になるコードを書くように強制する9つのルールを、
1000行程度のプロジェクトに適用するオブジェクト思考の練習方法です。
9つのルールは、あくまで練習なので普遍的なルールではありません。

それでは、「9つのルール」を紹介していきます。

ルール1.1つのメソッドにつきインデントは1段落までにすること

みなさんは、どこから手をつけてよいかわからないほどの巨大なメソッドを見たことはありませんか?
巨大なメソッドは、複数の仕事をやっていることが多いです。そのため凝集度が低くなってしまいます。

各メソッドが厳密に1つの仕事を行うように、メソッドごとに制御構造またはコードブロックを1つだけにしましょう。
インデントがなくなるまで、リファクタリング:メソッドの抽出をするだけです。

次のコードにルール1を適応してみます。
    public void method1() {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                if (i == j) {
                    s.append("■");
                } else {
                    s.append("●");
                }
            }
            s.append("\r\n");
        }
        System.out.println(s.toString());
    }

2回メソッドの抽出をして完成です。
    public void method1() {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            appendLine(s, i);
            s.append("\r\n");
        }
        System.out.println(s.toString());
    }

    private void appendLine(StringBuilder s, int i) {
        for (int j = 0; j < 10; j++) {
            appendMark(s, i, j);
        }
    }

    private void appendMark(StringBuilder s, int i, int j) {
        if (i == j) {
            s.append("■");
        } else {
            s.append("●");
        }
    }

ルール2.
else句を使用しないこと

うんざりするほどネストされていたり、スクロールしなければ読めないほどの条件文を見たことありませんか?
さらに既存の条件文に単純に分岐を増やすほうが、適切な解決方法を考えるよりも楽なので修正のたびに条件文はどんどん複雑になっていきます。

簡単な条件文であれば、ガード節や早期リターンに置換えましょう。
また、複雑な条件文は、GoFデザインパターンのStrategyパターンを導入します。
nullチェックの条件文であれば、NullObjectパターンの導入を検討します。

次のコードに早期リターンを使ってみます。
    public String getGrade(int score) {
        if (score > 80) {
            return "A";
        } else {
            if (score > 60) {
                return "B";
            } else {
                return "C";
            }
        }
    }

早期リターンによってelse句がなくなります。
    public String getGrade(int score) {
        if (score > 80) {
            return "A";
        }
        if (score > 60) {
            return "B";
        }
        return "C";
    }

次は、Strategyパターンを使ってみます。
    public class Calculator {
        
        public static final int ADDITION = 1;
        public static final int SUBSTRACTION = 2;
        
        private int type;
        
        public Calculator(int type) {
            this.type = type;
        }
        
        public int calculate(int a, int b) {
            int result = 0;
            if (type == ADDITION) {
                result = a + b;
            } else if (type == SUBSTRACTION) {
                result = a - b;
            }
            return result;
        }
    }


Typeによってアルゴリズムが変わるので条件文がなくなります。
    public class Calculator {
        
        private Type type;
        
        public Calculator(Type type) {
            this.type = type;
        }
        
        public int calculate(int a, int b) {
            int result = type.execute(a, b);
            return result;
        }
    }
    
    public interface Type {
        int execute(int a, int b);
    }
    
    public class Addition implements Type {
        public int execute(int a, int b) {
            return a + b;
        }
    }

    public class Substraction implements Type {
        public int execute(int a, int b) {
            return a - b;
        }
    }

ルール3.すべてのプリミティブ型と文字列型をラップすること

int型などのプリミティブ型は、それだけではなんの意味ももたない単なるスカラ値です。
メソッドの引数がint型の場合、メソッド名や変数名で意図を表現するしかありません。
もしメソッドが「金額」オブジェクトを引数として受け取れば、意図がわかりやすくなりコンパイラのチェックも可能になります。

プリミティブ型はそのまま使用しないで、意味のあるファーストクラスオブジェクトとして扱いまます。
String型もプリミティブ型と同じ扱いをします。
ファーストクラスオブジェクトとすることで関連する操作も、メソッドとしてまとめるやすくなります。

String型のインスタンス変数をファーストクラスオブジェクトにします。
    public class Rule3 {
        private String name;
    }

String形をラップしたクラスを作ると関連する操作もまとめる事ができます。
    public class Rule3 {
        private Name name;
    }
    
    public class Name {
        
        private String value;
        
        // 操作をまとめる
    }

ルール4.1行につきドットは1つまでにすること

1行の中に複数のドットがある場合、その処理は間違った場所で実行されています。
複数のドットは、多くのオブジェクトを知りすぎていてカプセル化に違反している証拠です。

操作できるオブジェクトは、
・オブジェクト自身
・インスタンス変数のオブジェクト
・引数で渡されたオブジェクト
・メソッド内でインスタンス化したオブジェクト
です。
デメテルの法則(最小知識の原則)に従いましょう。

複数のドットがあるのでデメテルの法則に違反しています。
    public class Rule4 {
        
        private User user;
        
        public void method() {
            user.getAddress().method();
        }
    }
    
    public class User {
        
        private Address address;
        
        public Address getAddress() {
            return address;
        }
    }
    
    public class Address {
        
        public void method() {
            
        }
    }


デメテルの法則に従うように修正するとドットが1つになります。
    public class Rule4 {
        
        private User user;
        
        public void method() {
            user.method();
        }
    }
    
    public class User {
        
        private Address address;
        
        public void method() {
            address.method();
        }
    }
    
    public class Address {
        
        public void method() {
            
        }
    }

ルール5.名前を省略しないこと

同じ単語を何度も入力しているのなら、コードの重複が発生しています。
メソッド名が長くなっているのなら、複数の責務を持っています。

1つか2つの単語だけを扱うようにして省略しないようにしましょう。

名前が長くなっているメソッドは複数の責務を持っています。
    public void updateAndPlay() {
        
    }

メソッドを分割して責務をはっきりさせます。
    public void update() {
        
    }
    public void play() {
        
    }

ルール6.すべてのエンティティを小さくすること

50行を超えるクラスは、複数の仕事をしています。
10ファイルを超えるパッケージは、複数の目的があります。

50行を超えるクラス、10ファイルを超えるパッケージは作らないようにします。
クラスは単一責任の原則(SRP)、パッケージは閉鎖性共通の原則(CCP)に従いましょう。

ルール7.1つのクラスにつきインスタンス変数は2つまでにすること

オブジェクトのインスタンス変数が増えるほど、そのクラスの凝集度が低下していきます。
巨大なオブジェクトは、重複コードが発生し管理不可能なほど複雑になっていきます。

1つのインスタンス変数を管理するクラスと2つの独立した変数を調整するクラスの2種類のみ作成します。
インスタンス変数に関わる振る舞いは、自然とインスタンス変数と同じオブジェクトに作成されます。

インスタンス変数が3つあるので凝集度が低いです。
    public class User {
        private String name;
        private String phone;
        private String email;
    }

名前と連絡先のオブジェクトを抽出します。
    public class User {
        private Name name;
        private Contact contact;
    }
    
    public class Name {
        private String value;
    }
    
    public class Contact {
        private String phone;
        private String email;
    }

ルール8.ファーストクラスコレクションを使用すること

このルールは、ルール3と非常に似ています。
コレクションは非常に便利ですが、単なるプリミティブな型と同じで意図を表すことができません。

コレクションを独自のクラスでラップします。
コレクションをラップしたクラスには、多くのインスタンス変数を持たせないようにします。
フィルタなどのコレクションに関する振る舞いをまとめることができます。

コレクションをそのまま使っています。
    public class Rule8 {
        
        private List<User> users;
    }

コレクションをラップしたクラスを作って振る舞いもまとめます。
    public class Rule8 {
        
        private Users users;
    }
    
    public class Users {
        
        private List<User> users;
        
        // 操作をまとめる
    }

ルール9.Getter、Setter、プロパティを使用しないこと

インスタンス変数の値が他のオブジェクトから簡単に取得できるようになっていると、
振る舞いはそのインスタンス変数とは別の場所に実装されてしまいます。
その結果、コードの重複が発生してしまいます。

public変数やgetter,setterなどのアクセッサメソッドの使用は禁止です。
「求めるな、命じよ」を意識しましょう。

次の例は、amountに関する振る舞いがオブジェクトの外に出てしまっています。
    public class Rule9 {
        
        public void method(Item item) {
            int value = item.getAmount();
            item.setAmount(value + 1);
        }
    }
    
    public class Item {
        
        private int amount;
        
        public void setAmount(int amount) {
            this.amount = amount;
        }
        
        public int getAmount() {
            return amount;
        }
    }

アクセッサメソッドを取り除くことによって、amountに関する振る舞いがオブジェクトの中に納まります。
    public class Rule9 {
        
        public void method(Item item) {
            item.addAmount(1);
        }
    }
    
    public class Item {
        
        private int amount;
        
        public void addAmount(int value) {
            amount = amount + 1;
        }
    }

まとめ
オブジェクト指向エクササイズいかがでしたでしょうか?

9つのルールに完全に従うのはきついですよね?
特に、ルール4,7,9を完全に適応するのはかなり難しいと思います。

あきらめずにオブジェクト指向エクササイズに取り組んでいけば、
オブジェクト指向の理解が深まっていくのが実感できると思います。

オブジェクト指向エクササイズで、オブジェクト指向をマスタしましょう!!

最後まで読んでいただき、ありがとうございました。

Cassandraのデータ設計で注意していること

$
0
0

Ameba Smart Phone PlatformのAPI開発を担当している狭間と申します。今回はAmeba Smart Phone Platformで使用しているCassandraのデータ設計時に気をつけていることを実際に起きた事例を交えてお話したいと思います。

Cassandraのverstionは1.1.5を使用していて、100台構成のクラスタを組んでいます。ピーク帯ではおよそ50000write/sec、40000read/secのリクエストを処理していて、およそ45TBのデータを保持しています。そのような条件下で発生した事例と対処方法を紹介させていただきます。

Ameba Smart Phone Platformでの使用例

Cassandraでは以下の様なデータ構造をもっていてデータの最小単位はColumnで以下の様な構造をしています。

Column
namevaluetimestampttl

CassandraではColumnFamilyと呼ばれる単位でデータがまとめられいて(RDBでいうテーブルに近いイメージ)、ColumnFamilyは複数のrowを持ち、rowは複数のColumnを持ちます。Columnは動的に追加することが可能で、Row keyとColumnのnameを指定してデータを取得します。

RowkeyとColumnのnameを指定してデータを保存、取得するというのが最も単純な使い方ですが、nameがソートされて格納されることを利用して範囲指定で一括取得などを行うことができます。nameはデータ型を指定でき(long、UTF-8など)、そのデータ型に応じたソート順で保存されます。

我々は使っているデータ構造として代表的なものを幾つか紹介します。
開発当初よく使用していたのは単純なKVSの用に利用するパターンです(開発初期は他のデータストアの採用も検討していたので)。下記の図の用にnameを固定にしてrow keyを指定してvalueを取得するような形で利用します。valueにはJson形式のデータを格納していることが多いです。
Row KeyColumn
namevalue
1_"value"
2_{"aaaaa":"bbbb"}
3_{"ccccc":"ddddd","eeeee","fffff"}

上記のKVS方式でも実現できるのですが、格納するJsonのフィールドが多くなってくると、読み込み時に必要のないフィールドまで取得してしまったり、更新時に一度全データを取得する必要があったりと、無駄が多くなってくるのでの下図の用にcolumn nameをフィールド名にしてRDBのテーブルの様な形でデータを保存しています。
Row KeyColumn
namevalue
1name太郎
bloodTypeA
birthday1980-01-01
2name花子
bloodTypeO
birthday1981-01-01
Cassandraではcolumnがnameのデータ型によってソートされるので、それを利用して下記のようにunix timeをnameに指定することでタイムラインの様なデータ構造を比較的容易に実現することができます(実際には衝突回避のためにunix timeとatomicなカウンターを組み合わせています)。が、この形は気をつけないととんでもないことになります。理由は後述します。

Row KeyColumn
namevalue
11404044261data1
1404044265data2
1404044275data3
21404044261data1
1404044269data2
1404044278data3
またnameの値を指定でColumnを取得することができるのでColumn nameを検索キーの様な形で使用することもできます。下記の様な形式ででRow keyがユーザのID、Column nameにユーザのフレンドのユーザIDが入っていて、valueにフレンドになった時刻が入っているとすると、Row keyとColumn name指定でデータを取得できるのでIDが1のユーザとIDが2のユーザがフレンドかどうかの判定を行ったりすることができます。
Row KeyColumn
namevalue
12{"time" : "2014-06-30T02:12:33Z"}
3{"time" : "2014-05-30T02:12:33Z"}
21{"time" : "2014-06-30T02:12:33Z"}
31{"time" : "2014-05-30T02:12:33Z"}

実際に運用してきて起きた問題と対策

運用当初ははデータ量も少なくそれほど問題にはならなかったのですが、サービスが拡大するにつれ様々な問題が発生しました。ここではアプリ起因となって発生した問題とその時に行った対策を紹介したいと思います。

特定のnodeのみ負荷があがる
運用開始してしばらくしてから、特定のnodeのみ負荷があがるという現象が発生しました。データのレプリケーション数分のnodeの負荷だけが上がっていたので、そのnodeが保持しているデータに問題があるだろうとあたりを付け調査した所、特定のデータのreadが大量に発生していたことが原因でした。

マスタ系のデータを先ほどの例の4番目のような形(column nameを検索キーとして使用するよう形)で保存していて、全ユーザーのアクセスが一つのキーに集中していました。そもそもなんでそんなものをCassandraに入れるんだよと言われると何も言えないのですが、、、データ反映に遅延があっても問題ないデータだったのでアプリ側でキャッシュすることでとりあえずしのいでいます。

Cassandraはwriteに比べreadは弱く、またkey単位での分散なのでこういったデータは保存しないほうが良いです(お前が言うなって感じでしょうが)。まだ移行はできていないのですが、別のデータストアへの移行を検討中です。
データ設計の話ではないのですが、監視はやっておいた方がよいと思います。当時は監視もきちんと行われていなくて、実際に問題が発生するまで気づかず、発生してからもJMXの値を手動で更新して調査を行っていたので、原因の特定にも時間がかかりました。現在はGrothForecastなどで異常なデータアクセスなどがないか監視を行っているので、こういった問題が起きそうかどうか事前に把握できるようになっています。監視周りに関しては弊社の@oranieがブログに書いていますので、興味があるようでしたらそちらもご覧になって下さい。

特定のnodeのデータが肥大化する
運用を開始して半年くらい経ってからnodeごとのデータの偏りが顕著になってきて、一部nodeだけディスク容量が足りなくなるという自体が発生しました。肥大化していたのはユーザのフレンドのアクティビティを時系列に保存しているところでした。

アクティビティの情報はcolumn nameをunix timeにしてユーザのフレンドが何らかのアクションを起こすたびにcolumnを追加するという方式で実装していて、ttlを設定して一定時間経過後に消えるようにはしていたのですが、それ以外に特に制限はなくcolumnが無限に増えていくような設計になっていました。そのためフレンドが多いユーザのrowは大量のカラムを持つこととなり、そういったユーザのデータを保持しているnodeのみデータが肥大化するという結果になりました。

これに関してひとまずnodeの追加を行ってディスクがいっぱいになったnodeのデータを分散して持つことでしのいだのですが、row keyでの分散を行っているcassandraではcolumnが際限なく増えていくような設計は避けたほうがいいと思います。これに関してはRow keyを日別に作ってRowを分割するような方法で対応しました。

Columnの削除はtombstoneと呼ばれるプロパティが設定され、データが読み込まれなくなるだけでcompactionが行われるまで削除されません。columnの追加、削除を大量に行われるような機能を作ると気づいたらディスクが足りないなんてことになるかもしれないので、そのような機能を作る場合には注意しておいたほうがよいと思います。

またColumnの削除にはもう一つ気をつけたほうがいいことがあって、それはtombstoneがついたcolumnは読み込み時にスキップされているだけであるということです。そのため削除される可能性のあるcolumnを持つRowをColumn nameの範囲指定で一括取得するような処理を行う場合には注意が必要です。先ほど紹介したフレンドのアクティビティの例ではColumnのnameが時刻でttlによってColumnの削除を行っているので、範囲指定で一括取得する際に現在時刻とColumn追加時に指定しているttlから残っているColumnの中で最も古いものの時刻を計算して、必ずその範囲内で取得するようにしています。

まとめ
Cassandraは構築が比較的容易でデータ構造も柔軟なため非常に扱いやすいように見えますが、実際に運用を始めると一筋縄でいかない部分が多いです。

Cassandraの採用を検討している方、実際に運用されている方の少しでも参考になれば嬉しいです。またもっとこうしたほうがよい等のご意見があれば参考にさせていただきたいのでぜひ教えて下さい。




TOTEC2014 インフラチューニング(チューニンガソン)で優勝したはなし

$
0
0

インフラ&コアテク本部の仲山です。 「TOTEC2014 インフラチューニング」という社内チューニンガソンイベントで優勝をいただいたので、 技術ブログを書くことになりました。

TOTECは、サイバーエージェントグループ内の技術者コンテストで、 インフラ、フロントエンド、サーバサイドの分野ごとに、 「チューニンガソン」と呼ばれる形式でその速度向上を競い合います。

今回の「インフラチューニング」では、 参加者はソフトウェアのソースコードを改変できないため、 あくまでミドルウェア等の変更、チューニングや、サーバ構成の最適化のみで闘います。

主なレギュレーション

  • 運営があらかじめ用意したMediaWikiの応答速度を競う。
  • ソースコードの変更は禁止だが、設定ファイルの編集は可能。
  • 運営の計測サーバから、複数のURLに対してリクエストが送られ、その応答時間を合算したものがスコアとなる
  • レンダリング等は含まず、ページへのHTTPリクエストのみ
  • 起動状態のサーバ4台が渡されるので組み合わせは自由
  • MySQLのSQLダンプを食えるデータストアを使わなくてはいけない
  • DBのデータはディスクへの永続化がされなくてはいけない
  • データの更新結果が5分以内に反映されなくてはいけない
  • 終了直後にサーバを再起動する

最終的な構成

こちらが最終的なサーバ構成です。
なお、終了時に最終構成を手元にコピーし忘れたので、メモ書きだよりです。

最終サーバ構成

この記事は、この構成に至るまでの試行錯誤を淡々と描く物です。
過度な期待はしないでください。

方針だて

以下のような方針で進めることにします。

「MediaWikiがそんなにチューニングされていないわけがない」

一般的なチューニングテクニックはMediaWikiの開発サイドで一通り試されているはずで、 DBクエリとかも、せこい「ごまかし」的なやり方しか余地はなさそう。 なので、あまり一つの場所にこだわりすぎないようにする。 あと、色々試した先人の知恵がごろごろ転がっているに違いないのでそれを探し回る。

「前半は1サーバで素直なPHP環境でのチューニングを進める」

基本的なチューニング結果が最終的なスコアを底上げするはずなので、 Varnishの導入は後半になってからおこなう。 ボトルネック箇所が変わる可能性があるので、 サーバ分散させるのも最後になってからにする。

「割り切る」

細かくベンチ取って比較する時間は無いので、割り切って仮説ベースでざっくり進める。
時間が掛かる割に効果が薄そうなことは後回しにする。

[11:00] 開始

一番最初にやるべきは、初期状態の確認、保存と性能監視です。 初期状態の保存として、/etc以下をまるっと複製したり、rpm -qa | sort を保存したりしておきます。

性能監視は、sysstatは既に入っていたので、それに加えてdstatとmuninを入れておきます。

また、渡された4台ともc3.largeであるのを確認しました。
(AWSだとインスタンス上から http://169.254.169.254/latest/ 以下を見に行くとメタデータが取れます。)

この時点で、AWS上のAmazon Linuxなので、OSのチューニングにも(余程時間が余らない限り)手を出さないことも決めました。

[11:30] nginx + php-fpm環境の構築

Apache+mod_php5環境が用意されていたという噂ですが、 全くガン無視して nginx + php-fpm 環境に差し替えました。

PHPは21世紀なのに標準では非スレッドセーフなので、 Apache+mod_php5環境だとprefork MPMで動かす必要があり、 最大接続数がPHPのプロセス数に縛られます。 nginx + php-fpmの構成であれば、 FastCGIで切り離されているためそのあたりの柔軟性を確保することができます。 この手のチューニングだと、こうやって「疎結合」を挟むことで、 構成変更を容易にすることが重要になります。

(補足) より正確に書くと、PHP本体はスレッドセーフですが、 PHP本体をマルチスレッドにしたいわけではないのと、 ZTSを有効にしてPHPのリビルドが必要になる割に得られる物は多くないためです。 Apache+mod_fcgid + php-fpmという選択肢もありますが、nginxの方が慣れているので……。
また、Apache+mod_php5のままでnginxを前に追加するというアプローチもありますが、 無駄が多いので候補に挙がりませんでした。

また、PHP 5.5 で性能が改善されたという噂も聞いていたので、 ついでにPHP自体もバージョンアップしておきます。 みんな大好きAPCのかわりに、PHP 5.5でZend Optimizer由来のOPcacheが本体に取り込まれたため、 OPcacheを有効にします。 また、タイムスタンプ確認系だとか、load_comments, save_commentsみたいな必要なさそうな設定は全部殺します。

[12:00] 独自にベンチマークしながら設定試行錯誤

dstatとかtopを眺めつつ。
この時点では完全にPHPプロセスがボトルネックであるようです。

運営の計測を待っていると永遠に時間が掛かる(うえに施策と計測で時差が出る)ので、 アクセスログに出る計測リクエストを眺めがら、自分でabで計測します。 この時点でこれぐらいの性能が出ていました。

# ab -k -c10 -n1000 'localhost/wiki/index.php?title=%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8'


Requests per second:    18.15 [#/sec] (mean)


# top
top - 12:12:24 up  3:59,  2 users,  load average: 3.31, 4.30, 2.72
Tasks:  89 total,   2 running,  87 sleeping,   0 stopped,   0 zombie
Cpu(s): 40.0%us,  1.5%sy,  0.0%ni, 57.3%id,  1.0%wa,  0.0%hi,  0.0%si,  0.2%st
Mem:   3859076k total,  1750232k used,  2108844k free,    94156k buffers
Swap:        0k total,        0k used,        0k free,  1279920k cached


  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 2652 mysql     20   0 1335m  99m 6184 S 13.0  2.7   7:12.04 mysqld
25668 nginx     20   0  463m  35m  18m S  9.3  0.9   0:03.38 php-fpm-5.5
25665 nginx     20   0  459m  31m  18m S  9.0  0.8   0:03.37 php-fpm-5.5
25670 nginx     20   0  457m  29m  17m S  9.0  0.8   0:03.54 php-fpm-5.5
25667 nginx     20   0  459m  31m  17m S  8.0  0.8   0:03.07 php-fpm-5.5
25669 nginx     20   0  459m  31m  18m S  8.0  0.8   0:03.24 php-fpm-5.5
25662 nginx     20   0  457m  29m  17m S  7.7  0.8   0:03.24 php-fpm-5.5
25663 nginx     20   0  457m  29m  18m S  6.7  0.8   0:03.22 php-fpm-5.5
25664 nginx     20   0  461m  33m  18m S  6.0  0.9   0:03.03 php-fpm-5.5
25661 nginx     20   0  459m  31m  17m R  5.7  0.8   0:03.22 php-fpm-5.5
25666 nginx     20   0  457m  29m  17m S  3.7  0.8   0:03.01 php-fpm-5.5
25693 totec201  20   0 15140 1196  912 R  0.3  0.0   0:00.03 top
    1 root      20   0 19488 1596 1280 S  0.0  0.0   0:00.74 init

ただ、mysqldも無視できないぐらいには負荷があるようです。

MediaWikiの設定ファイルや公式ドキュメントを見ていると、 memcachedに対応しているようなので、導入しておきます。

おひるごはん。
サンドイッチ、ボリュームあっておいしかったです。

[13:00] DBのチューニング

どうやら、「版の多いページ」が重い(計測上はタイムアウト)ようで、 タイムアウトのペナルティを避けるべく、調査に入ります。 自分でabかけてSQL見てみてみると、GROUP BYでの集計クエリでどうやっても重そう。
EXPLAINすると、見事に「Using temporary; Using filesort」です。

とりあえずmy.cnfがほぼ空っぽなので、先人の知恵をパチってきます(kazeburo様ありがとうございます)。
https://github.com/kazeburo/mysetup/blob/master/mysql/my55.cnf
(参考: MySQLの設定ファイル my.cnf をgithubにて公開しました & チューニングポイントの紹介

これをベースに、永続化はしろと言われたが消えたら死ぬとまでは言われていないので innodb_flush_log_at_trx_commit=0 を設定したり、 今回だとデメリットが少なそうなクエリキャッシュの有効化などを入れます。 sort_buffer引き上げとかも試して見ましたがほとんど変化がなく、 最後のあがきで、サブクエリJOINの性能向上が入ってるらしいMySQL 5.6に入れ替えてみます、が、 これも効果が無く、やはり設計からどうにかしないとだめだがどうしようもないようです。

※ 実はここでMySQL 5.6向けにmy.cnfを修正するのを忘れていました。

プログラムの修正が禁止でもmysql-proxyを挟んでSQLを無理矢理書き換えることもできるのですが、 すぐにクエリの改善策が浮かばなかったので、一旦放置することにします。

[14:00] Varnish導入

ルーキー部門優勝者の岩永さんがスコアを一桁引き上げてインチキ説が出始めた頃ですが、 そろそろ素のPHP構成で一通り普通のことはやったので、 予定通りこちらもVarnishを導入します。

「mediawiki varnish」でググれば先頭にManual:Varnish cachingという公式マニュアルの記事が出てきます。秘伝のたれっぽいdefault.vclが出ているので、もうこのままぶっ込みます。 また、Varnish導入時に罠とされる、データ更新時のキャッシュ破棄もMediaWiki標準で対応しているようで、 願ったり叶ったりです。偉大なり先人の知恵。

この段階で、単純なスコアは一桁上がります。

[15:00] Varnishの調整

Varnishで重いURLを調査します。
共有メモリを使う独特なロギング形式ですが、 リクエストをテキストで保存するにはvarnishncsaを使います。

# vim /etc/sysconfig/varnishncsa:
DAEMON_OPTS="-a -w $logfile -D -P $pidfile -F '%h %l %u %t \"%r\" %s %b %D fb:%{Varnish:time_firstbyte}x hit:%{Varnish:hitmiss}x hdl:%x{Varnish:handling}x'"


# sort -n -k11  /var/log/varnish/varnishncsa.log | less

これを見ながら、Varnishのストレージをmallocに変更したりします。 このあたりで今回のピーク性能(スコア93064)がでていました。

そして、ここでMediaWiki側からのPURGEリクエスト設定を忘れていたので投入します。 これで残念ながらスコアが少し落ちます。 どうも今回の計測方式ではあまり反映されなかったようで、PURGE無しでも良かったかもしれません。

[16:00] サーバの分散化

当初の予定通り、サーバの分散を実施します。

もう完全にPHPがボトルネックなので、まずはnginx + php-fpmを3台に乗せ、 さらにMySQLを残り1台のサーバに分けます。 その状態でabとかを掛けてもDBサーバに余裕がありまくりんぐなので、 最終的には、4台ともnginx + php-fpmを動かすことにしました。 ただ、メモリが潤沢では無いし重いクエリは依然残っているので、 MySQLを同居させるサーバだけはphp-fpmのプロセス数を半分に絞って微調整しておきます。

※ 本来は、Varnishからの振り分け頻度も変えなければダメでした……。

ただ、色々触りつつも、「決め手」が見つからないまま17時を迎えます。

[17:00] ゴールに向けた準備

今回のチューニンガソンがいわゆる本番環境と違うところは、 あらゆる保守性を捨て去って良いところです。 少しでもスコアを稼ぐために、監視系の処理やログ保存処理をを片っ端から殺します。

  • munin
  • munin-node
  • varnishlog
  • varnishncsa
  • mysql slowlog

また、不要そうなPHP拡張も切っておきます。

[17:30] 再起動テスト

終了直後のサーバ再起動というレギュレーションなので、 実際に再起動してきちんと復帰するかをテストします。 で、予想通りchkconfig漏れがありました。

また、sendmailの起動に時間が掛かっていて(掘り下げていませんがおそらくDNSのタイムアウト待ち?)、 nginx等の起動が待たされていたので、これも割り切ってsendmail自体の自動起動を止めました。

ラストスパートです。
再起動直後は各種キャッシュが空っぽになるので、以下を /etc/rc.local に仕込んでおきます。

  • /var/lib/mysql 以下のファイルをcat hoge > /dev/null
  • sever1/wiki/index.php?なんちゃら へのHTTPリクエスト

[17:55] 静かな心で終了を待つ

下手にいじって壊すと嫌なので、再起動した状態で5分前から触るのをやめ、 念のためブラウザからの動作確認をクリッククリックだけします。

ここで諦めたので試合終了です。

[18:00] 試合終了と心残り

試合終了時点で、心残りが2点ありました。

「最後のボトルネック」

実は最後の時点ではサーバ1台の時よりもスコアが若干落ちていて、 サーバ性能を使い切れていない状況でした。 「どこかにボトルネックがある」のはわかるもののそれが特定できず、 通信がサーバ間になり通信遅延が増えた分だけ損をしている状況です。

時間内に解消できることを目指して4台構成のまま最後を迎えましたが、 結局はそのままゴールとなりました。

「残された不一致」

計測リクエストの結果が、本来あるべき内容と異なると「不一致」としてスコアに残るのですが、 contentPage1(~4)というURLで0.1%程度が不一致となっていました。

キャッシュのパージもできているのでほぼリアルタイムで反映されるはずで、 不一致となってしまうのは何か問題があるはずなのですが、 時間不足でそれを追うことができませんでした。

まとめ

最終的に、以下のようなチューニングを実施しました。

  • PHPの実行環境をmod_php5からphp-fpmに変更し、5.5にアップグレード、opcacheを有効化、心なしかスピード凶寄りの設定
  • MediaWiki標準設定を投入したVarnishを前段に投入
  • memcachedによるMediaWiki標準キャッシュの有効化
  • MySQLを5.6にアップグレードと、心なしかスピード凶寄りの設定
  • 監視等の全カット
  • 再起動後にキャッシュあたためます

反省点もいろいろあります。

  • 最後のボトルネック(前述)
  • 残された不一致(前述)
  • 前半のPHP/MySQLチューニングははもっとスピード感持ってやれたはず。
  • 計測リクエストの分析が甘かった。Varnishレベルでもっとズルができたはず。

結局はごく一般的なサーバチューニングを一通りやりきったという感じで 「決め手」には欠ける感じですが、実際には決め手がないのが普通でもあります。
普通のことをやりきった事で1位を頂くことができたというように考えています。

サーバチューニングって楽しいよね!

MySQL初心者に贈るインデックスチューニングのポイントまとめ2014

$
0
0

サイバーエージェント公式ブログをご覧の皆さんこんばんは、インフラ&コアテク本部の須藤(@strsk)です。普段はAmebaのソーシャルゲーム全般のインフラを見つつ、日本語ラップの啓蒙をしながら弊社社員を素材にコラ画像をつくったりしています。好きなAAは麻呂です。

はい、というわけで今回はMySQLインデックスチューニングの基本的な流れについてまとめてみました。
ソーシャルゲームは更新も参照もめちゃくちゃ多いです。数秒のレプリケーション遅延も致命的なので適切なテーブル、クエリとインデックス設計が重要です。(何でもそうですけど)インデックスが多くなると更新コストなどが懸念されますが、インデックスが正しく使われていないクエリを放置している方が悪です。そんなこんなで、割と例も偏ったりしてるかもしれませんがあしからず。

前提としてはInnoDBを想定しています。MyISAMはほとんど使っていません。

なぜインデックスが必要か


インデックスが使われていないクエリは、はじめこそ影響は少ない(見えない)もののレコード件数が増加するに連れレスポンスが劣化します。
DBのレスポンスが劣化すればWebサービス自体のレスポンスも低下するし、レプリケーション遅延の原因にもなります。また、フルテーブルスキャンなどはCPU負荷になるため1、リリースされたあるクエリが原因で一気にCPUリソースが枯渇しサービス停止に追い込まれる、なんて可能性も否定できません…。クリティカルな負荷がかかる場所だからこそ適切にインデックスを作成する必要があります。

スロークエリログを出力する


解析するためにはスロークエリログが出力されてなければ話になりません。また、出力しているからといって安心してもいけません。
long_query_timeに設定した値より短いクエリは残らないため、1秒に設定している裏で0.8秒かかるクエリが何万コールもしている、ということもあり得ます。
1秒未満でも設定できるので、最初に0.5秒~0.1秒あたりに設定し、スロークエリを改善しながら調整していきます。

log_queries_not_using_indexesを設定すると、long_query_timeの設定に関わらずインデックスが使用されていないクエリを出力することができますが、ゲームのマスターデータでレコード件数も少なくインデックスが必要ないクエリなど、許容しているレベルのクエリも出てしまうため時と場合によって使い分けます。

/etc/my.cnf
slow_query_log = 1
slow_query_log_file = mysql-slow.log
long_query_time = 0.1
#log_queries_not_using_indexes

オンラインで変更する場合
mysql> set global slow_query_log = 1;
mysql> set global long_query_time=0.1;

スロークエリログの設定をすると、このようにクエリの実行時間(秒)やロック時間(秒)とクエリの内容が出力されます。
# Time: 140829  3:34:08
# User@Host: root[root] @ localhost [] Id: 2156371
# Query_time: 23.355119 Lock_time: 0.000034 Rows_sent: 1 Rows_examined: 28057953
SET timestamp=1409250848;
select count(*) from hoge;

スロークエリログをローテートする


スロークエリログを出力したらローテートの設定もします。統計を取るときも便利だし、開いた時に1年前のクエリから表示されていた時の絶望からも免れることができます。

/etc/logrotate.d/mysql
/var/lib/mysql/mysql-slow.log {
create 644 mysql mysql
notifempty
daily
rotate 30
missingok
compress
delaycompress
dateext
sharedscripts
postrotate
# just if mysqld is really running
if test -x /usr/bin/mysqladmin && \
/usr/bin/mysqladmin ping &>/dev/null
then
/usr/bin/mysqladmin flush-logs
fi
endscript
}

細かい設定は良きに計らってください。

スロークエリの統計を取る


ログが出力され始めたら、解析の準備をします。目grepしたいところですが、凡人にはちと厳しいため、統計を取ってインパクトの大きいものから解析します。
mysqldumpslowでも良いですが、percona-toolkitpt-query-digestを使ったほうが見やすくて好きです。

Percona Toolkitインストール
$ sudo yum -y install perl-Time-HiRes perl-IO-Socket-SSL perl-DBD-MySQL
$ sudo rpm -ivh http://www.percona.com/redir/downloads/percona-toolkit/LATEST/RPM/percona-toolkit-2.2.10-1.noarch.rpm

解析方法
$ pt-query-digest mysql-slow.log > digest.txt

digest.txt
上部に統計情報とレスポンスタイムとコール率の高い順にランキングが表示されます。
# A software update is available:
# * Percona Toolkit 2.2.6 has a possible security issue (CVE-2014-2029) upgrade is recommended. The current version for Percona::Toolkit is 2.2.10.


# 5.3s user time, 80ms system time, 28.53M rss, 216.25M vsz
# Current date: Thu Aug 28 21:42:16 2014
# Hostname: hoge-db03
# Files: /var/lib/mysql/mysql-slow.log
# Overall: 10.48k total, 57 unique, 0.16 QPS, 0.05x concurrency __________
# Time range: 2014-08-28 03:40:02 to 21:42:15
# Attribute total min max avg 95% stddev median
# ============ ======= ======= ======= ======= ======= ======= =======
# Exec time 3002s 100ms 11s 286ms 777ms 284ms 180ms
# Lock time 1s 41us 378us 96us 119us 16us 98us
# Rows sent 13.66M 1 174.31k 1.33k 6.31k 5.51k 0.99
# Rows examine 825.18M 2.00k 1.46M 80.61k 211.82k 110.61k 36.57k
# Query size 5.05M 154 791 504.95 755.64 100.54 487.09

# Profile
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ================== =============== ===== ====== ===== =============
# 1 0x3BEFCC5114487A23 1268.7555 42.3% 4211 0.3013 0.36 SELECT tablename
# 2 0x323595A45502EE39 315.5999 10.5% 1026 0.3076 0.19 SELECT tablename
# 3 0xD0473BC5F5324984 176.0897 5.9% 634 0.2777 0.00 SELECT tablename
# 4 0x9FA860819FCAB0B9 174.2407 5.8% 439 0.3969 0.47 SELECT tablename
# 5 0xB9B067F6AF21AD27 149.3055 5.0% 370 0.4035 0.25 SELECT tablename
# 6 0x957F7C8D78DF45F4 97.7765 3.3% 770 0.1270 0.00 SELECT tablename
# 7 0xDF95841E1D35E78C 85.7142 2.9% 483 0.1775 0.12 SELECT tablename
# 8 0xB8E3A489E461A7D7 82.2773 2.7% 244 0.3372 0.14 SELECT tablename
# 9 0x4E29A68B8903FFB3 72.5987 2.4% 323 0.2248 0.43 SELECT tablename
# 10 0x2839418CA78A9FEB 61.5735 2.1% 103 0.5978 0.00 SELECT tablename
# 11 0x91AAB488213B2825 58.6673 2.0% 126 0.4656 0.23 SELECT tablename

下部にクエリの詳細と平均レスポンスタイムのグラフが表示されます。このクエリはほぼ100ミリ秒(※訂正)→100ミリ秒から1秒の間 で応答していますが、ときどき10秒以上かかっていることもあるようです。これはひどいですね。
# Query 1: 0.06 QPS, 0.02x concurrency, ID 0x3BEFCC5114487A23 at byte 3388347
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.36
# Time range: 2014-08-28 03:40:02 to 21:41:41
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 40 4211
# Exec time 42 1269s 100ms 11s 301ms 777ms 327ms 180ms
# Lock time 42 426ms 50us 253us 101us 119us 12us 98us
# Rows sent 0 4.11k 1 1 1 1 0 1
# Rows examine 23 197.02M 3.36k 847.55k 47.91k 130.04k 51.64k 30.09k
# Query size 39 2.00M 492 498 497.70 487.09 0 487.09
# String:
# Databases hoge
# Hosts
# Users hoge
# Query_time distribution
# 1us
# 10us
# 100us
# 1ms
# 10ms
# 100ms ################################################################
# 1s ##
# 10s+ #
# Tables
# SHOW TABLE STATUS FROM `database` LIKE 'tablename'\G
# SHOW CREATE TABLE `database`.`tablename`\G
# EXPLAIN /*!50100 PARTITIONS*/
select hoge, fuga from tablename where ( hoge = 1 and fuga = 2 and hogehoge = 3 ) order by upd_datetime DESC\G


クエリを解析する


ようやくExplainの出番です。対象のクエリの先頭に"EXPLAIN"をつけて解析します。

EXPLAIN SELECT hoge, count(fuga)  from xxxxxxxxxxx_8      where id = 4             and upd_date  +----+-------------+-------------------+-------+---------------+---------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------------+-------+---------------+---------+---------+------+---------+-------------+
| 1 | SIMPLE | xxxxxxxxxxx_8 | index | NULL | PRIMARY | 12 | NULL | 4857810 | Using where |
+----+-------------+-------------------+-------+---------------+---------+---------+------+---------+-------------+
1 row in set (0.00 sec)

と、こんな風に解析結果が表示されます。各項目について見ていきます。

select_type
ここにDEPENDENT SUBQUERYUNCACHEABLE SUBQUERYが出ていたら要注意です。DEPENDENT SUBQUERYはインデックスチューニングで改善できる可能性がありますが、UNCACHEABLE SUBQUERYが出ている場合は読んで字のごとくキャッシュできないのでインデックスどうこうではなく、クエリ自体を見なおしたほうが良いです。ただ今回サブクエリの話は出てきません(☝ ՞ਊ ՞)=☞)՞ਊ ՞)

type
対象テーブルへのアクセス方法を表示しますが、indexALLが出たら要注意です。indexは一見良さそうですがインデックスのフルスキャンなので遅く、ALLはテーブルのフルスキャンなのでさらに遅いです。上の解析結果はindexなのでチューニングの余地がありますね。

key
クエリに使用されたインデックスが表示されます。意図したインデックスが使われていないということもあるので確認します。NULLの場合はインデックスが使われていません。

key_len
使用されているキーの長さ(バイト)です。キー長は短いほうが良しとされますが、user_id(int),status(int)で作成した複合インデックスが使われているのに、key_lenが4だとuser_idまでしか利用されていないということがわかります。インデックスが使われているのにスロークエリが出ている場合、このパターンが多いです。データタイプごとに必要な容量は公式ドキュメントをチェケラーしましょう。

MySQL :: MySQL 5.1 リファレンスマニュアル :: 10.5 データタイプが必要とする記憶容量
(追記)ご指摘いただいたんですが、MySQL5.6ではDATATIMEなどで記憶容量が変更されているようなので下記のほうがまとまってて良いです。
MySQL :: MySQL 5.6 Reference Manual :: 11.7 Data Type Storage Requirements

rows
検索に読み取る必要がある推定レコード数が表示されます。インデックスが使われていようとここが数万を超えるようだとスロークエリとして出力されることが多いです。上の解析結果では480万行と明らかに多いことがわかります。まずはここを減らせるようにインデックスを作成します。

Extra
注意すべきはUsing filesortUsing temporaryです。Using temporaryはソート時のテンポラリテーブルが必要な場合に表示されますが、極力少ないほうが良いです。Using filesortはインデックスを利用しないクイックソートなので大抵遅くなります。レコード件数が多ければ多いほど致命的になるのでインデックス作成が必須になってきます。後述しますが、Using IndexはCovering Indexが利用されている場合に表示されます。

インデックス確認


mysql> SHOW INDEX FROM tbl_name;

インデックスを作成する前に現状のインデックス構成を把握しておきます。確認するには"SHOW INDEX 構文を利用します。
mysql_show_index
よくチェックする項目を説明します。書いてない項目はそんなに見ることはないです。

Key_name
そのままインデックスにつけた名前です。もちろん主キーはPRIMARY。

Column_name
これもそのまま、各インデックスのカラム名です。

Cardinality
ユニークな値の見積もりです。見積もりなので正確な値ではないですが、カーディナリティが高ければインデックスを利用する可能性が高くなります。逆にカーディナリティが低いカラムにインデックスを作成しても効果が薄いとも言えます。

インデックス作成


mysql> CREATE INDEX index_name ON tbl_name(index_col_name, ...);

インデックス作成はCREATE INDEX構文を利用します。ALTER TABLEでもいいですが、CREATE INDEXのほうがシンプルで好きです。

いくつかのパターンごとにチューニング方法を説明していきます。
なお実際のチューニングでは、おそらくバックアップはしているでしょうから、バックアップDBでインデックスを作成しEXPLAINの結果を比較しながら適切なものを探すのが良いと思います。
説明文中ではいろいろはしょってCREATE INDEX文の"(index_col_name, ...)"内のみ表記します。

mysql> SELECT ... WHERE col1 = x AND col2 = y;

インデックスの基本はWHERE句の順番どおりに作成します。この場合であれば(col1,col2)です。ただし、既にcol1からはじまる別のインデックスを利用していてcol2のカーディナリティが低い場合、もしくはcol1のカーディナリティが高かったりユニークな値の場合は既に絞りきっているため効果は薄いです。WHERE句がそのあともcol3 = z AND col4 = …と続く場合も含めて、rowsの値がちゃんと減っているか確認しながら適切なカラムまで指定します。

mysql> SELECT ... WHERE col1 = x ORDER BY col2;

ORDER BY句があるときに適切なインデックスがないとUsing filesortが出ます。基本は順番通り(col1,col2)で作成します。

mysql> SELECT ... WHERE col1 = x ORDER BY col2 LIMIT 10;

これにLIMITがついている場合、かつcol1のカーディナリティが低い(検索効率が悪い)場合は(col2)で作成しソートした順にWHERE句で抽出したほうが10件呼ばれた段階で処理が完了するため高速になります。

mysql> SELECT ... WHERE col1 = x AND col2 = y ORDER BY col3;

WHERE句に複数のカラムがあるパターンでも順番どおりの(col1,col2,col3)で作成しますが、col2のカーディナリティが低い場合は(col1,col3)なども有効です。ソート時のインデックス利用はkey_lenに含まれない場合もあるので注意ですが、ExtraでUsing filesortが出てこなくなればOKです。

mysql> SELECT ... WHERE col1 = x AND col2 > y ORDER BY col3;

WHERE句にレンジスキャンの条件が入っている場合、順番通りのインデックス(col1,col2,col3)ではソートにインデックスを利用することができません。その場合はレンジスキャン(col1,col2)かソート(col1,col3)で比較して作成します。構成次第ですが(col1,col3)の方が良い結果になりやすいことが多いように思います。

mysql> SELECT ... WHERE col1 = x AND col2 > y AND col3 > z;

レンジスキャンが複数あるパターン。レンジスキャンはひとつのカラムまでしか利用できないので、これも(col1,col2)(col1,col3)でrowsの値などを比較します。

mysql> SELECT ... WHERE col1 = x AND col2 = y ORDER BY col3 DESC col4 ASC;

ORDER BYの条件に昇順と降順が混在している場合、これはどうやってもインデックスが効かないのでクエリ自体を見直しましょう。

Covering Index


上記では検索効率の悪いカラムをなるべくインデックスに含めない方針で説明してきましたが、呼び出しに必要なカラムで主キー以外のすべてがセカンダリインデックス内にあればCovering Indexとなりさらに高速に結果を返すことができます。これは、InnoDBのインデックス構造の恩恵でランダムリードが大量にあるクエリなどで効果が大きくなります。ここまで来てなんですが積極的に使っていったほうが良いです。なおCovering Indexになる場合、ExtraにはUsing indexが表示されます。

mysql> SELECT pk_col FROM t1 WHERE col1 = x AND col2 = y;

この場合は(col1,col2)のセカンダリインデックスがあれば主キー(pk_col)がセカンダリインデックスのリーフに格納されているのでCovering Indexになります。

mysql> SELECT pk_col,col1 FROM t1 WHERE col1 = x ORDER BY col2;

この場合も(col1,col2)でおそらくCovering Indexになります。

mysql> SELECT pk_col FROM t1 WHERE col1 = x AND col2 > y ORDER BY col3;

この場合、(col1,col2,col3)でCovering Indexになりますが、同時にUsing filesortも出てしまいます。ランダムアクセスがない分こちらが高速になる可能性がありますが、リソース状況などを考慮してどちらを取るか決めたほうが良いでしょう。

注意点


  • 検証は本番と同じ環境(レコード数)で行う
    インデックスを作成しても、テーブルの30%以上のデータにアクセスする場合インデックスは利用されません。データ量によってオプティマイザの選ぶインデックスが変わるためなるべく同一状況で検証したほうが良いです。
  • フルテーブルスキャンでもrowsが減れば速くなる
    速いと判断すればオプティマイザがフルテーブルスキャンを選択することもあります。オプティマイザに任せずインデックスを使いたい場合はFORCE INDEXなどを使います。
  • key_lenを見て期待通りにインデックスが利用されているか確認する
    無駄な複合インデックスや効率的なインデックスを判断するのに有効です。
  • 事前にインデックス作成時間を見積もる
    インデックス作成に限ったことではないですが、数分かかるALTER文を本番稼働中に実行したらその間更新はロックされるのでアウトです。さらに同じ時間だけレプリケーションも遅延してしまうので見積もることが重要です。
    pt-online-schema-changeを使うか、MySQL5.6であればOnline DDLという機能が追加されているためロックされずに実行できます。
    ただし、これによってレプリケーション遅延が回避できるわけではないので注意しましょう。



こんなところでしょうか。いろいろ盛り込み過ぎて抜け漏れや勘違いなどありそうですが、お役に立てばこれ幸いです。マサカリコメントもお待ちしています(震え声)

Special Thanks


つまりR.E.S.P.E.C.T.

漢(オトコ)のコンピュータ道: MySQLのEXPLAINを徹底解説!!
漢(オトコ)のコンピュータ道: Using filesort
漢(オトコ)のコンピュータ道: オトコのソートテクニック2008
漢(オトコ)のコンピュータ道: 知って得するInnoDBセカンダリインデックス活用術!

実践ハイパフォーマンスMySQL 第3版/オライリージャパン

¥5,184
Amazon.co.jp

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド/技術評論社

¥3,564
Amazon.co.jp

俺達のFabric 〜余計な仕事はFabricに任せよう〜

$
0
0

どうも、ガールフレンド(仮)で窓際エンジニアをやっていたり、ウチの姫さまがいちばんカワイイで窓際エンジニアをやっていたりする Wataru です。(PCはmacです) 窓が近いとエアコン戦争が激しいわけですが、やっと秋も近づいてきて戦争も終わりが見えてきたのでしょうか?残暑お見舞い申し上げます。え?遅い?


さて、今回はFabricの紹介をさせて頂きたいと思います。



もしあなたが千手観音のようにたくさんの手を持ち、サーバのオペレーションをできるとしたら、どうでしょう? そう、そんな神様のような事をできるのがFabricというツールです。

Fabricって何?

まあまあ大げさなことを書きましたが、Fabricはコマンドラインのツールです。Pythonでできていて、SSHの作業を効率化してくれるものです。アプリケーションのデプロイや管理がすごく楽になるんです。


リモートやローカルのシェルコマンドを実行したり、ファイルのアップロードやダウンロード、それらを複数サーバに並列実行など、様々な便利な機能があります。


例えばローカルのJavascriptをGrant等で色々ゴニョゴニョして、それをリモートのサーバにアップロードして、Apache再起動なんてことを複数サーバ同時に実行などもコマンド一発で実行可能にできるのです。


まーApache再起動する必要があるかどうかはちょっと置いておいて、複数のサーバ間でのコマンドをうまいことつなぎあわせて動かしていく事ができるのです。


同様のツールでCapistranoとかtomahawk等がありますが、シンプルさやシェルスクリプトからの移行の手軽さなどからFabricが私は好きです。

インストール

さて、早速インストールしてみましょう。FabricはPythonで動くのでまずPythonをインストールして下さい。またPython2.5~2.7で動かす必要があるのでPythonはpyenvとvirtualenvで管理するのがお勧めです。

macの場合

brewでサクッと入ります。

$ brew install pyenv pyenv-virtualenv

インストールしたら下記を.zshrcか.bashrcに書きを追記して下さい。

export PYENV_ROOT="${HOME}/.pyenv"
if [ -d "${PYENV_ROOT}" ]; then
    export PATH=${PYENV_ROOT}/bin:$PATH
    eval "$(pyenv init -)"
fi

上記の設定をsourceコマンドなどで反映しておいて下さい。


ここから一気にfabricをインストールしますね。

$ pyenv install 2.7.8
$ pyenv virtualenv 2.7.8 fabric-operation
$ source ~/.zshrc
$ pip install fabric
$ source ~/.zshrc

これでインストール完了です。

CentOSの場合

pyenvとvirtualenvのインストール方法が少し異なります。


CentOSの場合はbrewが無いので、gitでcloneします。

$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ git clone git://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins

あとはmacの.zshrcの書き換えの部分から同じです。

windowsの場合


ちょっとわかりません・・・

使ってみよう

まずおまじないとして適当なディレクトリを作って、そこに.python-versionというファイルを作りましょう。

fabric-operation

こんな風に書いておくと先ほどインストールしたPython2.7.8が選ばれるようになります。


Fabricは基本的にはfabfile.pyというファイルを基準に動きます。vimで言うところの.vimrcみたいなものでしょうか?(ちょっとちがくね?) とにかくまずfabfile.pyを作っていきます。簡単な例を一つ。


from fabric.api import *
 
def teach_about_woman():
    with settings(warn_only=True):
        run('man woman')

これは女性の扱い方のマニュアルを見せてくれと頼むコマンドです。


あ、そうそう、fabricもvimとかzshとかと同じように~/.fabricrcっていう設定ファイルと作るとデフォルトで呼び出してくれます。とりあえずSSHで利用するパスワードとかユーザとか書いとくといいかもしれません。

user = orenonamae
password = orenohimitu

作成したら、fabfile.pyと同じ場所で次のコマンドを実行してみてください。


$ fab -H localhost,orenoserver teach_about_woman

結果

$ fab -H orenoserver,anokonoserver teach_about_woman
[orenoserver] Executing task 'teach_about_woman'
[orenoserver] run: man woman
[orenoserver] out: No manual entry for woman
[orenoserver] out:
 
Warning: run() received nonzero return code 1 while executing 'man woman'!
 
[anokonoserver] Executing task 'teach_about_woman'
[anokonoserver] run: man woman
[anokonoserver] out: No manual entry for woman
[anokonoserver] out:
 
Warning: run() received nonzero return code 1 while executing 'man woman'!
 
Done.
Disconnecting from anokonoserver... done.
Disconnecting from orenoserver... done.

どうでしょう?それぞれのホストからそんなもんねーよって返ってきましたか? ばばばっと指定したホストでコマンドが実行されたのがわかったかと思います。 このように複数のホストに対して指定したコマンドを実行できるのです。


ここまでで作ったファイルを一応まとめておきます。

~/
├── .fabricrc
└── fabric
    ├── .python-version
    └── fabfile.py

もうすこし実践的に

なんだよ、それだけかよと思ったあなた。 いやいやいや、こんなもんじゃありませんよ!!もう少し実践的なやつをやってみましょう。


次の例はWebサーバのhostsファイルを書き換えて、Apacheを再起動するものです。


from fabric.api import *
 
@parallel(pool_size=2)
def edit_hosts():
    sudo('echo "# Do not remove the following line, or various programs" > /etc/hosts')
    sudo('echo "# that require network functionality will fail." >> /etc/hosts')
    sudo('echo "127.0.0.1   localhost.localdomain localhost" >> /etc/hosts')
    sudo('echo "::1 localhost6.localdomain6 localhost6" >> /etc/hosts')
    sudo('echo "" >> /etc/hosts')
    sudo('echo "{host}  `hostname`" >> /etc/hosts'.format(**env))
    sudo('echo "" >> /etc/hosts')
    sudo('echo "# DB" >> /etc/hosts')
    sudo('echo "192.0.2.0   anokonopc" >> /etc/hosts')
 
    sudo('/usr/local/apache/bin/apachectl -k graceful')

echoでゴニョゴニョ書いててちょっとダサい感じもしますが、'echo "{host} hostname" >> /etc/hosts'.format(**env)こんなふうに自分のIPに対して自分のHost名を記述するなんてことも指定できます。 この辺をうまく使うと、偶数のサーバは特定のIPに向ける、等といったことも簡単にできます。


こんな感じで実行してみましょう。


$ fab -H orenoserver,anokonoserver edit_hosts
 
[orenoserver] Executing task 'edit_hosts'
[anokonoserver] Executing task 'edit_hosts'
・
・
・
[orenoserver] out:
[anokonoserver] out:
 
Done.

バババババっと実行結果が表示され、複数のサーバが並列で処理した事が確認できたと思います。 @parallelを使うと指定したHostへの処理が並列に実行されます。pool_sizeを指定することで最大同時実行数を制限することもできるので、例えばサーバが10台あるのを一気に実行するのはちょっとという場合は、2台づつ実行等と指定することもできるわけです。

応用?

Fabricは単体で使っても十分に便利な事がおわかり頂けたかと思います。 このFabricをJenkinsと組み合わせることで複数サーバへのアプリのデプロイやファイルの配布等といった使い方もできます。


実際、弊社のガールフレンド(仮)というゲームでは、サーバに対する運用オペレーションは開発環境・本番環境含め、ほとんどのオペレーションをJenkinsとfabricを組み合わせた形で行えるようになっています。 このようにすることで運用面で開発、ステージング、本番環境をできるだけ一致させた状態を保つ事ができ、Twelve-Factor Appの思想に近づけると考えています。


Fabricを使っていく規模を広げていくと、fabfile.pyだけではファイルが肥大化していってしまいます。ガールフレンド(仮)ではfabfile.pyから他のpythonファイルをimportすることで視認性をあげています。参考までにファイル構成をご紹介します。


.
├── README.md
├── a10_change_vip
│   ├── vipa_change_prd.list
│   ├── vipb_change_prd.list
│   └── change_mainte_a10.sh
├── fabfile.py
├── git.py
├── id_rsa-cy_tomcat
├── img.py
├── jq
├── requirements.txt
├── sensu.py
├── server.json
├── server_list.py
├── test.py
└── voice.py

git.pyやvoice.py等と用途ごとにpythonのファイルを分けて管理しています。またjqコマンドや組み合わせて使うshellスクリプトなども同じプロジェクトとして管理しています。 server.jsonというファイルで一部のサーバのIPを管理していたりもしていますが、全体のサーバのIPは別のサーバ管理用のWebベースのツール(独自作成 by 佐野さん)のものを使って、そこからAPI経由でfabricが取得するような仕組みをとっています。サーバの追加や除外などの管理が一元化されているのです。下記がその画面です。


またHubotと連携さて、障害時にスマホから緊急対応できるような仕組みを最近では導入したみたいですね^^ まだまだ使い方は無限大なのでしょうか。


Fabricはけして新しいツールではありませんが、古いツールにはフォースが宿っています。 そう、マスター・ヨーダも「フォースはお前と共にいるのだ、いかなるときも」と言っておりました。


まだ使ったことの無い方は是非一度Fabricを使ってみてはいかがでしょうか?

ACIとロードバランサー連携について

$
0
0
どうもこんにちわ

インフラ&コアテク本部でNWエンジニアしていると思われる田中です。
今回は、以前篠原が記載した記事の環境とロードバランサーとの連携について少しご紹介できればと思います。本記事はまだ検証段階ですので内容に不備があるかもしれませんがあしからず。。。

今現在私が検証している内容としては、
CiscoさんのACI環境とF5さんのLB(BIG-IP)との相互接続
です。
そもそもACIって何?という方は、こちらを参考にしてください。
「それでもなんのこっちゃか分からないぜ!」
「だから何が出来るんだ!」

という方、一緒に僕と検証しましょう!


本題:APICを使って負荷分散環境の構築!
今回はC社さんがリリースしたコントローラーのAPIC(ACIのコントローラー)を使って、BIG-IP と連携したサーバの負荷分散を実施してみたいと思います。細かい手順については割愛させて頂きますのでご了承ください。
オフィシャルなデモとしては、youtubeにあがっているので参考にしてみるといいと思います。

構成
雑な構成図ですが一旦こんな感じの検証をしているんだなという雰囲気だけでもわかって頂ければと。


下準備
1.APIC上でTenant/PN/BD/Appを用意しておくこと
2.APICとLBのmgmtセグメントは同一セグメントであること
3.PhysicalDomain=VLANセグメントの定義を作成
4.Contracts=ポリシーの用意
5.F社さんのDevicePackageをAPICにインポート

と何を言っているのかわからないかも知れませんが、そういうものがあるんだなと思って頂ければ助かります(・_・;)


1.Device Clusterの作成
DeviceClusterとは、
PhysicalまたはVM上で稼働しているL4-L7サービス(LB・FWなど)をACI Fabric上で稼働させるための論理インターフェースの設定や、デバイス情報を定義する所
と私は解釈しています。

さて、実際にDeviceCluseterを作成するにあたって下記3つのステップが必要になってきます。
=======================================
1.プレコンフィグされているmgmtテナントでDeviceClusterの作成
2.DeviceClusterのマネージメントIPや、論理インターフェースの定義
3.実際のロードバランサーのマネージメントIPや、物理インターフェースと②で定義した論理インターフェースのマッピング定義
=======================================

ステップ1


まず、"Tenant" メニューを選択し、"mgmt"テナントを選択。
そして、"L4-L7 Services"を開いて"Create Device Cluster"を選択。
次にステップ2に移りましょう。

ステップ2

ここでは、"EPG"以外の項目を全て埋めていきます。

"Cluster Info"にはDeviceClusterの名前、使用するDevicePackage、マルチなのかシングルテナントのみでの利用※1なのか、L4-L7機器が物理なのか仮想なのかを指定します。
追記:BIG-IPの場合だと、APICで指定したDeviceCluster名がDeviceGroups名になるので気をつけましょう。


"Cluster Management Interface"という項目ですが、
現VerでのF社さんのDevicePackageだとHA構成が組めないみたいなので、対象機器のmgmtアドレスとログイン/パスワードを指定してください。


"Logical Interfaces"項目では、あとで物理ポートとのマッピングする際の外向け、内向けの定義を行います。
基本的にNameとTypeは同じ名前にしておいた方が無難です。

※1 今回mgmtテナントでDeviceClusterを作成しましたが、それ以外のテナントでDeviceClusterを作成することも可能です。
理由として、mgmtテナント以外で作成したDeviceClusterは基本的に作成したテナントのみでの使用が考えられているため、複数のテナントで同じDeviceClusterを使用したいので本検証ではmgmtテナントで作成しました。



ステップ3

ステップ3では、ステップ2で定義した論理インターフェース等を踏まえて、稼働しているロードバランサーのマッピングを行います。

"Device Info"にはロードバランサーの名前を指定します。
"Management Interface"という項目は、
ステップ2で設定した設定内容と一緒のものを指定してください。


最後に"Interfaces"の指定ですが、ここがかなり重要です。
上図の場合だと、Nameに
BIG-IPで作成したTrunkインターフェース名を指定(BIG-IP上では"ext.int"というTrunkインターフェースを作成しています)、
Pathには事前にAPIC上で作成したインターフェースポリシーを指定(C社さんのvPCテクノロジーのポリシー)、
Logical Interfaceにはステップ2で作成した"external,internal"を指定しています。

※Trunkインターフェースを組まずに作成する場合は、もっと簡単にできるかと思います。
F5が提供しているドキュメントを見ながら作成すれば、一連の流れが記載されているので問題無く進めるかと思います。

さて、これでステップ1-3で終わりました。
問題なく終われば下図のように、APIC経由でBIG-IP上にDevice Groupsが作成されているはずです。



2.DeviceClusterのExportとServiceGraphの作成
ここでは、作成したDeviceClusterをマルチテナントで使えるようにしたので、構成図の"LBTest"テナントへExportし、ServiceGraphを作成します。
ServiceGraphとは、ACI Fabric内で定義したApplication間をつなぐL4-L7機能を提供するサービスと思って頂ければいいかなと思います。


作成したDeviceClusterのExport方法は、
"Tenant" メニューを選択し、"mgmt"テナントを選択。
そして、"L4-L7 Services"を開いて"Export Device Cluster"を選択。

すると下図の画面が表示され、"Device Cluster"に先ほど作成したDeviceClusterを指定し、
"Tenant"にExportしたいテナント名を記載してSUBMITをクリック。

Export先のテナント"LBTest"にてDeviceClusterがImportされているはずです。

exportdevicecluster


次にServiceGraphの作成ですが、


"Tenant" メニューの、"LBTest"テナントを選択。そして、"L4-L7 Services"を開いて"Service Graphs"を選択しServiceGraphを作成していきます。
基本的にドラッグ&ドロップ操作で完結するので、難しくないかと思います。

下図のようなウィンドウが出てきたら、Devicesウィンドウから"Virtual-Server"を右のウィンドウにドラッグ&ドロップ。
次に、"Consumer EPG"を選択し、VirtualServerの"ext"インターフェースにドラッグ、"Provider EPG"も同様に"int"インターフェースにドラッグして線をつなげてあげればServiceGraphの「もと」が完成です。
あと、"Name"にはServiceGraph名を指定してあげてください。


この「もと」が作成したあとは、Virtual-ServerのFunction Nodeを編集していきます。
"Service Graphs"の中に上記で作成した"WebGraph"があるはずなので、それを選択をし”Function Node"を選択してください。
すると下図のようなパラメータ設定画面が出てくるので、マニュアルに従って各値を入力していきます。

この各パラメータ値の内容は超重要で、少しでも入力ミスがあるとAPIC⇔BIG-IPの連携が出来ません。ただ、本ブログでは割愛させて頂きます。ごめんなさいm(_ _)m


3.ContractにServiceGraphの適用
いよいよ大詰めです。
作成したServiceGraphを
Contractに対しマッピングして、ClientからServerに対して通信を確立させましょう!

まずはじめに、App ProfileとContractsのマッピングを行います。
事前に作成済みのApplication Profile配下のServer-EPGで"Consumed"に対してContractを適用します。Consumed,ProvidedというのはContractを適用する向きについてです。こちらについてはC社さんのドキュメントを見て頂ければと思います。


同じようにClient-EPGでもContractsを適用するのですが、Client-EPGではContractの向きを"Provided"にしておいてください。
すると、下図の様なClient -> Contract -> Serverといった流れがAPIC上で定義されます。



最後に作成したServiceGraphをContractsへのマッピング方法ですが、

"Security Policies"の"Contracts"を選択、事前に作成していた"WebContract"を選択し、作成したServiceGraphをドロップダウンリストから選択してあげればOK。
これでAPIC→BIG-IPに対して、DevicePackage経由でServiceGraphで指定した各値が反映されていきます。
以上で一連の作業は完了です!

下図は反映後のBIG-IPです。自動的にパーティションが切られていますが、ACI上のテナント毎でServiceGraphを作成すると”Route Domain”が作成されるためです。そのため別テナントで同じようにServiceGraphを作成し反映すると、新たな"Route Domain"とパーティションが作成されます。





最後に
結構最後はすっ飛ばしましたが、APICを使って負荷分散環境の構築についての説明でした!
実際に通信・負荷分散出来るかどうかなどは、どこかでご紹介できれば・・・(;・∀・)
ところどころ抜けている所がありますが、こんな感じなんだなと雰囲気だけでも伝わってもらえれば幸いです。

(完)

fluentd + Elasticsearch + kibanaでCassandraモニタリング

$
0
0
はじめまして。インフラ&コアテク本部の鳥垣と申します。普段はAmeba Smart Phone PlatformやAmebaの基幹系サービス全般のインフラを見る仕事をしております。
昨今fluentd + Elasticsearch + kibanaを使ったリアルタイムモニタリングが流行っていますが、これを使ってCassandraのステータスをモニタリングするシステムを作ってみましたので、そのお話をさせていただければと思います。

構築のきっかけ

こちらのサイトにてdstatのモニタリングをkibanaでやっている記事を拝見し、Cassandraのステータスも同じようにリアルタイムグラフの描画ができないかと考えました。
以前にWebSocketで監視もリアルタイムにという記事でもあるとおりリアルタイムモニタの仕組みはありましたが、kibanaの検証も兼ねてリアルタイムのグラフ描画にチャレンジしてみました。

システム構成

  • ElasticSearchは3台でクラスタ構築しています。
  • indexの保持期間は3日間にしています。
  • ElasticSearchサーバのIP変更などが発生した場合などを考慮してfluentdの取得データは一旦kibanaサーバに集約するようにしています。

セットアップ

ElasticSearch
  • 公式サイトから最新版のRPMをダウンロードしてインストール。
  • Heapをメモリの半分までを割り当てる。
  • 3台でクラスタ組むように設定(負荷分散と冗長性を考慮してクラスタを構成)。
  • ElasticSearchのステータス監視用にプラグインのHQheadをインストール。
  • elasticsearch.ymlの設定は以下のとおり(変更箇所のみ抜粋)
cluster.name: cluster name
node.name: "node name" 
node.master: true
node.data: true
 
index.number_of_shards: 5
index.number_of_replicas: 2
discovery.zen.minimum_master_nodes: 2
discovery.zen.ping.timeout: 3s
 
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["node ip", "node ip","node ip"]

KibanaとNginx
  • yumにてNginxをインストール。Nginxはデフォルト設定。
  • 公式サイトから最新版のkibanaをダウンロードして解凍し、ドキュメントルートに配置。
fluentd
  • kibanaサーバに集約用のfluentdをインストール。受信用設定とElasticSearchにデータを送信する設定を入れる。
  • 各Cassandraノードサーバにfluentdをインストール。gemにてfluent-plugin-map, fluent-plugin-elasticsearchをインストール。Cassandraのステータスを取得する設定を入れる(詳細は後述)。


Cassandraステータスの取得

Cassandraのステータス取得はJolokiaを使います。今回は以下の値をグラフにしてみました。

bean attribute 項目名
org.apache.cassandra.db:type=CompactionManager PendingTasks COMPACTION PENDING
org.apache.cassandra.internal:type=FlushWriter PendingTasks FLUSHWRITER PENDING
org.apache.cassandra.internal:type=HintedHandoff PendingTasks HINTEDHANDOFF PENDING
org.apache.cassandra.request:type=ReadStage PendingTasks READSTAGE PENDING
org.apache.cassandra.request:type=MutationStage PendingTasks MUTATIONSTAGE PENDING
org.apache.cassandra.db:type=StorageProxy RecentReadLatencyMicros STORAGEPROXY READ LATENCY
org.apache.cassandra.db:type=StorageProxy RecentWriteLatencyMicros STORAGEPROXY WRITE LATENCY

上記をfluentdにて取得するために、fluent-plugin-jolokiaを使用しています。

fluentdの設定は以下になります。(例としてReadStage Pendingのみ記載します)

<source>
  type jolokia
  tag jolokia.cassandra.RS
  jolokia_url http://node_ip:8778/jolokia/
  jmx_bean org.apache.cassandra.request:type=ReadStage
  run_interval 5s
</source>

<match jolokia.cassandra.RS>
  type copy
  <store>
    type map
    tag  "map.cassandra.jolokia.ReadStage"
    time time
    record {"value" => record["value"]["PendingTasks"], "stat" => "ReadStage-Pending", "host" => "hostname"}
  </store>
</match>

※値は5秒ごとに取得しています。

また、上記のJolokiaからの取得以外にもnodetool cfstatsからcolumn familyのステータスを取得してグラフ化したりもしています。
cfstatsからは以下の値を取得しています。
  • Pending Tasks
  • Read Latency
  • Write Latency
上記を各KeySpaceごとに全column familyの値を取得しています。
ただし、column familyが100を超えるほどあるため、ポーリング間隔は10秒ごとにしています。

Kibanaグラフ作成

ブラウザにてkibanaにアクセスしグラフを作成します。
ReadStage Pendingのグラフを作成する場合は「QUERY」にて以下のように項目を入力します。
  • stat:"ReadStage-Pending"を入力
  • topNを選択(全ノードを1グラフに重ねて表示させるため)
  • Fieldは「host」を入力
上記にて、作成したグラフが以下になります。

上記グラフはCassandraノード100台分のReadStage Pendingをグラフ化したものです。

hostnameに「-」が入っている場合、topNを使った複数サーバのグラフを重ねて描画する箇所でうまく描画できなかったため、「-」を「_」に変換しました。
※Cassandraを再起動した場合、
fluentdの再起動も必要になります(Jolokiaからの取得ができなくなってしまうため)。

まとめ

kibanaを触ってみた所感を簡単にまとめてみました。

好いなと思った所
  • グラフをホストで重ねて表示ができる。
  •  異常があるホストがすぐにわかる。
  • 秒単位でのグラフ表示が可能
  •  fluentdで値がとれればなんでも秒単位描画できる。
ここはちょっとなあと思った所
  • 最初のグラフを作るときが大変。。。
  •  1回作成すればJSONでエクスポートできるので、似たようなグラフを作りたいときはそれを編集してインポートすることはできます。
  • グラフ作成に慣れが必要かなと。
  • 簡単に差分表示ができない(GrowthForecastのように差分表示機能が実装されていない)。
  •  総計値しか取得できないようなものをグラフ化する場合は、前回の値との差分を計算してから描画など工夫が必要になります。
  • ElasticSearchのCPU負荷がすごいことになるw
  •  グラフ数や閲覧人数にもよりますが、ひどい時はCPU負荷だけでサーバのロードアベレージが80くらいはいきますw
     なのでElasticSearchサーバはCPUコア数が多いサーバを使ったほうが無難です。
     今回のシステムではElasticSearchサーバのCPUは24コアで構築しています。
あとkibanaのリフレッシュ間隔を5秒にしたらクライアントPCでブラウザのCPU使用率がすごいことになったりしましたw
画面のリフレッシュ間隔は5分程度が無難かと思います。

また、このシステムの実績を生かして今では他サービスにてApache,TomcatMySQLなどのステータスもKibanaでリアルタイムでモニタできるようにしておりまして、特にイベントなどで負荷が局所的に高まるときなどは大変重宝しております。

今後として

ElasticSearchの負荷がやたら高くなるので、この負荷をなんとか抑える方法がないか模索していこうと思っています。
また、Cassandraサーバでまだモニタリングできそうなところ(JVMのスレッド数やディスクIOなど)もグラフを増やしていければと考えております。

もしこれからリアルタイムモニタリングを導入したいとお考えの方の参考になればと思います。
また、もっとこうすれば良くなるよなどご意見あればぜひ教えていただけると幸いです。

アドテクスキルアップゼミ カラムナーデータベース検証まとめ

$
0
0

皆様こんにちは。
アドテク本部カラムーデータベースゼミチームです。

今回の記事ではゼミチームが行った検証結果について発表させていただきます。

また、この記事につきましては 11/12 に行われた db tech showcase Tokyo 2014 にて発表させて頂きました内容になります。

※追記
Impala / Presto の File Format についてご指摘を頂きましたのでデータロード及びまとめの部分に追記しました。


アドテクスキルアップゼミ
カラムナーデータベース検証まとめ

目的


広告システムでは大量のデータをデータベースに入れて解析を行います。
小規模から中規模なデータはRDBMSで行えますが、数TBを超えると
RDBMS以外の選択肢を探さないといけません。

ビッグデータ用のデータベースは比較資料が少なく、
また、あったとしても検証条件が不明な物や、データ量が少ない物しかないため
今回はビッグデータに対応できるカラムナーデータベースを比較し、
データ量ごとに最適なデータベースを検証することにしました。

アドテクスキルアップゼミとは


本題の前に、ちょっとアドテクスキルアップゼミについて説明させていただきます。
アドテクノロジー領域におけるサイバーエージェントグループ横断組織の
アドテクスタジオではアドテクスキルアップゼミという制度があり、
研究の成果を事業に生かすことを目的とした研究費用サポート制度があります。
ビッグデータの性能検証には大量のデータをインポート・処理しないといけないため
結構な費用がかかるのですが、研究費用から捻出されています。

また、大量のデータをインポートする検証作業にはとても長い時間がかかりますが、
制度では80時間まで通常業務の時間を使っても良いというルールがあり、
今回はその限られた時間の中で行いました。

検証データベース


今回は下記のデータベースを検証することにしました。

  • Redshift (AWSで利用できるSaasのDWH用データベース)
  • Matrix (Redshiftの元になっているActian社製データベース)
  • InfiniDB (InfiniDB社(旧Calpont社)製データベース)
  • Impala (Cloudera Hadoop)
  • Presto "connect to Hive" (Facebook社製のHadoop上で動く分散SQLエンジン)
  • BigQuery (Google Cloud Platformで提供されているSaas)

検証データ


検証データには実際に本番(Redshift)で利用されている実データを利用しました。
データセットは約1.5TB、約10TBの2つのパターンを用意し、それぞれ本番の環境であるRedshift上から取得しています。
1.5TBと10TBは未圧縮状態でのサイズです。
RedshiftからUnloadコマンドでデータをS3へ取得する時間は、10TBで約9時間で、
ベンチマークには実際に使われているQueryを使って検証しました。

検証環境


検証環境はBigQuery以外はAWSで行っています。
Redshiftで使えるインスタンスとまったく同じものを選択することができず、
各ミドルウェアで必要なメモリ量・ディスク量などによって検証する構成を変えています。
ただ、どのデータベースも違う構成だと検証にならないため、選定基準を決めています。

下記に検証データベースごとの検証環境をまとめます。

検証データベースインスタンスタイプインスタンス費用/hインスタンス選定理由
Redshiftdw1.xlarge x 8$9.52元データで使われている構成と同一
Matrixhs1.8xlarge x 3$13.8インストール要件などの関係でこの構成
InfiniDBhs1.8xlarge x 2$9.2Redshiftと同程度の金額
Impalac3.4xlarge x 9$9.19Redshiftと同程度の金額
Prestoc3.4xlarge x 9$9.19Redshiftと同程度の金額
BigQuery--課金体系が違うため記載なし


Redshift

RedshiftはSaasなのでAWS Management Consoleで
立ち上げるインスタンスと数を入力して作成。


Matrix

インサイトテクノロジー社にサポートして頂き、
EC2上にインストール。


InfiniDB

AWS MarketplaceよりAMIから起動。


Impala

Cloudera Managerを利用してインストール。


Presto

こちらを参考にしてインストール。


BigQuery

S3にGzip圧縮されているデータをインポートするため
解凍するためにGCEを利用。

データロード編


データインポート時間

検証データベース1.5TB10TB
Redshift9時間15分21秒24時間57分47秒
Matrix-約50時間
InfiniDB約8時間約40時間
Impala数分数分
Presto数分数分
BigQuery約1時間40分約12時間

※約がついているものについては全て計測していなかったため、
1ファイルをロードした際にかかった秒数で容量を割り、
全体の容量から試算したものです。


Redshift

今回インポートするデータは元々がRedshiftからUnloadしたものなので、
元クラスタのテーブル作成時のクエリーを使用してテーブルを作成しました。
データのロードはcopyコマンドでs3からロードしています。
計測表に記載した時間はこのロード時間を表します。

1.5TBのロード時間が約9時間に対して、約7倍のデータである10TBが(9時間の7倍である)63時間でなく、約25時間であるのはインスタンスの台数やテーブル数の関係しているかと思います。
10TBはインスタンスが1.5TBの2倍あります。これらのことから約25時間であったと考えます。

Matrix

ETLツールであるKNIMEというGUIツールを利用してインポートを行いました。
S3から直接データをロードしようとしましたが、ETLツールにバグがあり、
一度S3からHDFS上にデータを移動させて、HDFSからロードを行っています。
(既にこのバグはActian社で修正済み)
このため実際のインポートよりは遅い可能性があります。
ロード自体はETLツールでGUIから直感的に行えるので一番簡単でした。
計測表に記載した時間はHDFSからMatrixへのロード時間を表します。

InfiniDB

インポートツールのcpimportを利用してインポートを行いました。
S3から直でロードすることができないため、
s3cmdを利用して一旦ローカルへダウンロードしてからインポートしています。
計測表に記載した時間はローカルからcpimportでロードした時間を表します。

Impala

S3からHDFSへのデータダウンロードはhadoopコマンドを利用して実行しました。
sudo -u hdfs hadoop fs -cp s3n://[AWS_ACCESS_KEY]:[AWS_SECRET_KEY]@path /user/hdfs/
S3上のデータは圧縮されているため、HDFS上でデータを解凍しました。
sudo -u hdfs hadoop fs -cat /user/hdfs/filename.gz | pigz -d | sudo -u hdfs hadoop fs -put - /user/hdfs/filename.txt
Hiveメタストアから同期。HiveへのロードはHueにログインし、Metadataストアから実行しました。
計測表に記載した時間は解凍されたファイルからロードした時間を表します。
追記) File Format の指定をしていなかったので TEXTFILE 形式となります

Presto

HiveメタストアにConnectするため、インポートのフローはImpalaと同じです。
計測表に記載した時間は解凍されたファイルからロードした時間を表します。
追記) File Format の指定をしていなかったので TEXTFILE 形式となります

BigQuery

BigQueryにはGoogle Cloud Storageからロードを行いました。
このためAmazon S3に置いてあるデータをGCEインスタンスで取得し、解凍を行ってからGoogle Cloud Storageに配置しました。
計測表に記載した時間はGoogle Cloud StorageからBigQueryへのロードした時間を表します。

今回は試せませんでしたが、GCS Online Cloud Importを利用すればデータをS3からCloud Storageに直接持ってくることも可能なようです。
https://cloud.google.com/storage/docs/online-cloud-import


計測編


計測クエリーについて

計測クエリーは実際に使われているクエリーなので公開出来ませんが、1.5TBで1種類、10TBで4種類のクエリーを試しています。

下記にクエリーの特徴をまとめます。

  • 1.5TBの1個目はLIKEで前方一致を行うクエリーです。
    例: SELECT DISTINCT id FROM table WHERE str LIKE 'string%'

  • 10TBの1個目はGROUP BYを行ってcountを行うクエリーです。
    例: SELECT id,count(*) FROM table GROUP BY idx

  • 10TBの2個目はサブクエリーの結果をさらにGROUP BYするクエリーです。
    例: SELECT id,count() FROM (SELECT id, count() as count FROM table GROUP BY id ORDER BY count DESC LIMIT x) as table WHERE column = column GROUP BY id,count

  • 10TBの3個目はサブクエリーによるテーブル2つをJOINしてWHEREをかけるクエリーです。
    例: SELECT id FROM
    (SELECT id,count() as count FROM table GROUP BY count DESC LIMIT x) as table1,
    (SELECT id,count() as count FROM table GROUP BY count DESC LIMIT x) as table2

    WHERE table1.id = table2.id

  • 10TBの4個目はGROUP BYとORDER BYをした上で、min,count関数を使うクエリーです。
    例: SELECT min(id),count(*) FROM table WHERE column = 'string' GROUP BY id ORDER BY count DESC

クエリー実行時間

検証データベース1.5TB10TB:110TB:210TB:310TB:4
Redshift639.7s60.9s73.0s72.8s176.2s
Matrix-19.3s53.5s35.8s109.9s
InfiniDB1123.0s209.2s300.3s275.6s1778.3s
Impala4725.8s6309.9s6381.7s6364.7s6339.5s
Presto5096s9295sFailed9502s6274s
BigQuery6.5s12.0s9.1s8.6s8.4s

Redshift

元々Redshiftで実行していたクエリーなのでそのまま実行します。

Matrix

こちらもRedshiftと互換があるためそのまま実行します。

InfiniDB

こちらもそのままで実行します。

Impala

こちらもそのままで実行します。

Presto

こちらもそのままで実行します。 10TB:2のクエリーだけエラーが発生し、結果を取得できませんでした。

BigQuery

BigQueryではそのままのSQLでは動かない部分がありましたので、一部クエリーの書き換えが必要でした。

まとめ編


総評

既にRedshiftによる運用をしているということもあり、Redshitを基準としました。
かなり主観的な部分がありますが、そこはご容赦ください。
あくまで今回の検証のデータおよび環境ではこのようになったということで、参考にして頂ければと思います。

項目Redshift(基準)MatrixInfiniDBImpalaPrestoBigQuery
環境構築のしやすさ
データロードのしやすさ×
インスタンスタイプ選択肢-
インポート性能
クエリ性能
SQL互換性
コストパフォーマンス

Redshift

導入のしやすさやパフォーマンスではかなり良い選択肢だと思います。
ただ、データが数TB、数十TBということになってくるとかなりコストが増えていくということには注意する必要があると思います。

Matrix

今回ETLツールのバグによりロードするまでに時間がかかりましたが、
バグの報告から1週間後には修正してくれたようで、
Actian社のサポート力の高さが分かりました。

構成上、3台のインスタンスを使っていますが1台はMasterノードでCompute Nodeが2台です。
Redshiftと比較すると、Redshiftの方が速いクエリーもあればMatrixの方が速いクエリーもありました。
ただ、それほど差は無く全体的にRedshiftと同等のパフォーマンスが出ています。
今回クエリーを実行している際にTOPコマンドでリソースの使用状況を確認しましたが、
Disk IOが殆ど使われていなく、CPUで頭打ちになっていました。
hs1.8xlargeは35ECUしかないインスタンスタイプでE5-2650 1つ分の様です。
オンプレミスでCPUをスケールアップすればRedshiftより性能を出すことが可能であると思われます。

InfiniDB

今回検証してから今回のまとめ記事を書く間に開発元が解散してしまったようです。
AMIから簡単にインスタンスを作成でき、
既存がMySQLにデータが入っているようであれば互換性も高かっただけに残念です。

Impala / Presto

今回の構成ではクエリーがとても遅かったです。
Redshiftと比べるとEC2上でやる場合にはコスト的に不利だと思います。
しかし、オンプレミス環境であれば話も違ってくるかもしれません。
HDFSからのデータロードがとても速く、
既存でHadoopを使っている場合には選択肢としてよさそうです。
Presto はまだ出てきてから間もないということもあり、今後に期待したいと思います。
追記)こちら構築時の設定不備により、File Format が TEXTFILE となっていたようです
結果として今回の検証では比較対象としてふさわしくありませんでした。お詫び申し上げます。

BigQuery

とにかく速かったです。とはいえ JOIN を多用するような複雑なクエリーを回そうとすると処理がタイムアウトになりました。
第一正規形のテーブルを回したり、アプリケーション内でどうにかするのであればビッグデータ最強のデータベースだと思います。
単純にデータベースを他から移行するのは難しく、既存システムの移行は設計段階から見直す必要もありそうです。
回すクエリー次第で値段が高くなることも考えられますが、 データを貯めておく費用がとても安く、リザーブドの仕組みもあります。
データ量が大きくなっても処理時間が変わらないというのは、 データ分析において非常に重要で他の製品にはない強みがあります。
データ量が大きくなることが見込まれる場合は他の製品ではいつか限界がくる為、 データの消し込み等を考慮する必要があります。
BigQueryではそこの運用を考慮しないでいいという点が非常に優位な点だと思います。
その為、データ量が大きくなればなるほどBigQueryはその優位性を発揮することになると思います。



以上、いかがだったでしょうか。
今回のケースでは性能面で BigQuery が圧勝ということになりましたが、データ分析に求められる要件は必ずしも性能ではありません。
運用コストやオペレーションのしやすさ等様々な比較要素があります。
大事なことはデータ分析の要件にあったプロダクトを選択することです。
今回の記事がみなさまのプロダクト選定の助けになれば幸いです。

かんたんパッケージマネージャDuo

$
0
0

みなさん、こんにちは。Ameba事業本部ゲーム部門の平木(Layzie) と申します。

最近はSteamで安いゲームを漁ってばかりの毎日です。このエンジニアブログでは初めて執筆になります。



さて、今回エンジニアブログで何を書こうか悩んだのですが、悩んだ結果Duoというパッケージマネージャの紹介をしようということになりました。

このDuo、GitHubのStar数は結構多いんですが、(2014/11現在2618Star)あまり紹介されてる記事とかが無いので紹介してみようと思った次第です。



Duo


Duoの特徴



昨今、フロントエンドで使えるパッケージマネージャは色々あると思います。厳密に言うとサーバ側のJavaScript実装ですがNode.jsのパッケージマネージャであるnpmにフロントのJavaScriptライブラリを登録して使うというパターンもありますし、新規プロジェクトの雛形を作ってくれるYeomanでも採用されているTwitter社が作っているbowerなども有名です。そもそも最初からフロントエンドで使うHTML/CSS/JavaScriptをセットで扱いやすくするcomponentなどが有名でしょう。(余談ですが、componentとかググラビリティが低いので調べものの時に苦労しますよね)

最近ではもう何でも入れてやる位の意気込みを感じるwebpackなどもあります。



そんな中出てきたDuoの一番の特徴は GitHubにアップされているリポジトリをそのままパッケージとして使用する というところが挙げられます。次に package.jsonなどの設定ファイルが不要 という2点が既存のパッケージマネージャと違う部分ではないかと思います。



公式サイトでの紹介では、component、Browserify、そしてGoの影響を受けていると書いてますが、確かにこれらの良いところどりな感じのフロントエンドパッケージマネージャになっています。



インストール



インストールはnpmで行うことができます。もはやお馴染みの感じじゃないかと思うんですが、以下のコマンドでインストールしていきます。



$ npm install -g duo


他のパッケージマネージャと違いこのDuoは使う前に.netrcファイルにGitHub APIを使うための設定が必要です。API制限の回避や、プライベートリポジトリを使用するための措置です。



machine api.github.com
login <YOUR ID>
password <YOUR TOKEN>


passwordに入れるのはGitHubのアクセストークンなので、こちらから設定してあげてください。自分はデフォルトの権限にしています。



使用例



さて、実際に簡単なプロジェクトを作って使ってみることにします。まずは公式のソースをちょっとアレンジしてHTMLとJavaScriptを書いてみます。



index.html



<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Duo.js Test</title>
</head>
<body>
<p>Your ID:</p>
<p class="display"></p>
<script src="build.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>


index.js



var uid = require('matthewmueller/uid');
var fmt = require('yields/fmt');

var msg = fmt('Your unique ID is %s!', uid());

var display = document.querySelector('.display');
var text = document.createTextNode(msg);

display.appendChild(text);


ユニークなIDを生成して文字列のフォーマットをしてそれをHTMLに出力するという簡単なものですね。ここでお気付きの方もいると思いますが、JSの方の12行目でCommonJSライクにrequire()している部分がありますが、注目したいのは中の文字列です。'matthewmueller/uid''yields/fmt'という形で呼び出しています。これはそれぞれ、uidfmtというcomponentのライブラリをGitHubから直接呼び出ししているのです。require()したあとは普通に使っています。



またHTMLに今はないbuild.jsを読み込んでいますが、これはこれからduoコマンドを使ってビルドしたあとのJavaScriptになります。それではビルドしましょう。



$ duo index.js > build.js

building : index.js
installed : matthewmueller-uid@0.0.2
installed : yields-fmt@0.1.0
built : index.js


このように、初回のみなのですがJavaScriptでrequire()したライブラリを勝手にインストールしてくれます。元々はindex.htmlindex.jsしか無い構成だったのですが、ビルド後には勝手にcomponents/というディレクトリが切られて以下のようになります。



.
├── build.js
├── components
│ ├── duo.json
│ ├── matthewmueller-uid@0.0.2
│ │ ├── History.md
│ │ ├── Makefile
│ │ ├── Readme.md
│ │ ├── bin
│ │ │ └── uid
│ │ ├── component.json
│ │ ├── index.js
│ │ ├── package.json
│ │ └── test.html
│ └── yields-fmt@0.1.0
│ ├── History.md
│ ├── Makefile
│ ├── Readme.md
│ ├── component.json
│ ├── index.js
│ └── test
│ ├── index.html
│ └── test.js
├── index.html
└── index.js

5 directories, 19 files


components/内に上記で紹介したライブラリがダウンロードされているのが分かりますね。ビルド後のJSは以下のようになっています。



(function outer(modules, cache, entries){

/**
* Global
*/

var global = (function(){ return this; })();

/**
* Require `name`.
*
* @param {String} name
* @param {Boolean} jumped
* @api public
*/

function require(name, jumped){
if (cache[name]) return cache[name].exports;
if (modules[name]) return call(name, require);
throw new Error('cannot find module "' + name + '"');
}

/**
* Call module `id` and cache it.
*
* @param {Number} id
* @param {Function} require
* @return {Function}
* @api private
*/

function call(id, require){
var m = cache[id] = { exports: {} };
var mod = modules[id];
var name = mod[2];
var fn = mod[0];

fn.call(m.exports, function(req){
var dep = modules[id][1][req];
return require(dep ? dep : req);
}, m, m.exports, outer, modules, cache, entries);

// expose as `name`.
if (name) cache[name] = cache[id];

return cache[id].exports;
}

/**
* Require all entries exposing them on global if needed.
*/

for (var id in entries) {
if (entries[id]) {
global[entries[id]] = require(id);
} else {
require(id);
}
}

/**
* Duo flag.
*/

require.duo = true;

/**
* Expose cache.
*/

require.cache = cache;

/**
* Expose modules
*/

require.modules = modules;

/**
* Return newest require.
*/

return require;
})({
1: [function(require, module, exports) {
var uid = require('matthewmueller/uid');
var fmt = require('yields/fmt');

var msg = fmt('Your unique ID is %s!', uid());

var display = document.querySelector('.display');
var text = document.createTextNode(msg);

display.appendChild(text);

}, {"matthewmueller/uid":2,"yields/fmt":3}],
2: [function(require, module, exports) {
/**
* Export `uid`
*/

module.exports = uid;

/**
* Create a `uid`
*
* @param {String} len
* @return {String} uid
*/

function uid(len) {
len = len || 7;
return Math.random().toString(35).substr(2, len);
}

}, {}],
3: [function(require, module, exports) {

/**
* toString.
*/

var toString = window.JSON
? JSON.stringify
: function(_){ return String(_); };

/**
* Export `fmt`
*/

module.exports = fmt;

/**
* Formatters
*/

fmt.o = toString;
fmt.s = String;
fmt.d = parseInt;

/**
* Format the given `str`.
*
* @param {String} str
* @param {...} args
* @return {String}
* @api public
*/

function fmt(str){
var args = [].slice.call(arguments, 1);
var j = 0;

return str.replace(/%([a-z])/gi, function(_, f){
return fmt[f]
? fmt[f](args[j++])
: _ + f;
});
}

}, {}]}, {}, {"1":""})


中身はDuoで使う諸々の設定と読み込んだライブラリ、実際に自分が書いたJSが一緒になっているのが分かります。HTMLを見てみるとリロードごとにIDが変っていくのが分かると思います。



uid


基本の使用例はこのような感じなんで、とても簡単に使えるのが分かると思います。やっぱりpackage.jsonなどを書かないでもライブラリを使えるのは手軽に試したいときなどには重宝しますね。



Duoができること



ここからは基本使用例以外で、Duo.jsができることを軽く紹介していこうかと思います。



ローカルファイルの読み込み



先程のJSを以下のように変更するとローカルの別のファイルを読み込むことが可能になります。ここでは若干ムリヤリ感ありますが、display.jsを作ってここでDOM操作を担当します。index.jsではそれを呼び出すことにします。



index.js



var uid = require('matthewmueller/uid');
var fmt = require('yields/fmt');
var display = require('./display');

var msg = fmt('Your unique ID is %s!', uid());

display('.display', msg);


display.js



function display(selector, text) {
var target = document.querySelector(selector),
msg = document.createTextNode(text);

target.appendChild(msg);
}

module.exports = display;


ビルドします。



$ duo index.js > build.js

building : index.js
built : index.js


HTMLをリロードしても先程と変わりなくユニークIDが見れると思います。



ライブラリのバージョン指定しての読み込み



以下のようにするとバージョンを指定してライブラリをrequire()することができます。



var reactive = require('component/reactive@0.14.x'); // 0.14系最新版を使う
var tip = require('component/tip@master'); // リポジトリのmasterブランチを使う
var shortcuts = require('yields/shortcuts@0.0.1:/index.js'); // 0.0.1を使いエントリポイントとして、index.jsを使う


bowerで作られているライブラリに対しては特に一番下のようにエントリポイントとしてメインのJSを指定しないと上手く動かないことがあります。



CSSもJSと同じようにライブラリを読み込みできる



componentなどでもできますが、DuoではCSSも紹介してきたような方法で同様にライブラリを呼び出しすることができます。



先程のHTMLにCSSを加えてみましょう



index.html



<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Duo.js Test</title>
<link rel="stylesheet" href="build.css" type="text/css" media="all" />
</head>
<body>
<p>Your ID:</p>
<p class="display"></p>
<script src="build.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>


index.css



@import 'necolas/normalize.css';

p {
font-size: 20px;
}

.display {
font-size: 32px;
color: red;
}


CSSの場合はrequire()の代わりに(当たりまえですけど)@importを使います。ここでは有名なnormalize.cssを呼び出してビルドしてみます。



$ duo index.css > build.css

building : index.css
installed : necolas-normalize.css@3.0.1
built : index.css


CSSも先程JSライブラリがインストールされたcomponents/の下にインストールされます。



.
├── build.css
├── build.js
├── components
│ ├── duo.json
│ ├── matthewmueller-uid@0.0.2
│ │ ├── History.md
│ │ ├── Makefile
│ │ ├── Readme.md
│ │ ├── bin
│ │ │ └── uid
│ │ ├── component.json
│ │ ├── index.js
│ │ ├── package.json
│ │ └── test.html
│ ├── necolas-normalize.css@3.0.1
│ │ ├── CHANGELOG.md
│ │ ├── CONTRIBUTING.md
│ │ ├── LICENSE.md
│ │ ├── README.md
│ │ ├── bower.json
│ │ ├── component.json
│ │ ├── normalize.css
│ │ ├── package.json
│ │ └── test.html
│ └── yields-fmt@0.1.0
│ ├── History.md
│ ├── Makefile
│ ├── Readme.md
│ ├── component.json
│ ├── index.js
│ └── test
│ ├── index.html
│ └── test.js
├── display.js
├── index.css
├── index.html
└── index.js

6 directories, 31 files


ビルドされたCSSにはきちんとnormalize.cssが含まれているのが分かります。



build.css



/*! normalize.css v3.0.1 | MIT License | git.io/normalize */

/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/

html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}

/**
* Remove default margin.
*/

body {
margin: 0;
}

/* HTML5 display definitions
========================================================================== */

/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}

/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/

audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}


/*
* 途中省略
*/

p {
font-size: 20px;
}

.display {
font-size: 32px;
color: red;
}


HTMLを見てみましょう。スタイルが変更されているのが分かります。



uid2


他にも、ビルド中にsassなどを処理できるプラグイン機構も搭載されていたりするのも良いです。主要なCSSプリプロセッサなどは使えるようになっていますね。



まとめ



簡単にですが、Duoについて紹介しました。このような簡易なスタイルでパッケージ管理をあまり意識せずに開発が進められるのが一番の魅力なんではないかと思います。segment.ioが関わっているようなので、componentの上位互換のような匂いがします。



あとは、参考にもされているBrowserifyとの違いですが、Node.jsのAPIを使いたいかどうか?というところが主に大きい違いになると思います。



自分も業務ではまだDuoを使っていないので、中・大規模な開発に耐えられるのか?というようなところはまだ未検証なのですが、例えば、小規模の開発などやモックを作るなど試行錯誤が必要な場合などには、設定ファイルがないという身軽さが生きてくるパッケージマネージャではないかなと考えています。



ということで、みなさんもぜひ試してみてはいかがでしょうか?


サイバーエージェントでRailsを使っているコミュニティサービスのお話

$
0
0

アメーバ事業本部コミュニティ事業部の大﨑 (@hiroosak)です。
24LOGというサービスの開発を担当しています。

サイバーエージェントでは、コミュニティサービスが多数存在しているのですが、
実は、その中のいくつかのサービスでRailsが使われています。

パシャペ テルミー 24LOG

ですが、社内・社外問わずあまり知られていないようです。
なので、今回は、Railsを使ったプロジェクトでは
どのように開発が行われているかを簡単に紹介したいと思います。 

基本構成

基本構成

構成図の概要は上の図の通りです。
(監視サーバなどは上の図から省いています)

静的なファイルは、社内の共通ストレージに置き、検索については
社内のラボチームで用意したシステムと連携しています。
なのでプロジェクトのチームでは、主に app, db, cache, queue を見ています。

ちなみに、rubyのバージョンは主に2.0, railsは3.2です。
各構成はChef Server, もしくは Ansibleで管理されています。 

DB

MySQLのバージョンは基本は5.5です。
どのプロジェクトもマスター-スレーブ構成で、
Rails側では、ar-octopus を使っての
master slave接続切り分けを行っています。

フェイルオーバーは
マスターならHAProxyを使ってのマスタースレーブ入れ替え。

スレーブなら検知後にalias IPからslaveを外すようになっています。 

Cache

主にRedisを利用しています。Redisもmaster, slave構成になっており
redis-sentinel を使った監視を行っています。

Rails 側からは redishiredis を利用してアクセスしています。
また用途によってdb番号を別々にしており、最初は1組だったのが
ある程度サービスの規模が大きくなったタイミングなどで
用途別にredisサーバーを分割する、といったこともしています。 

Queueing

どのサービスもRedisを使っているのもあり、
ほとんどの場合キューイングは resque を利用しています。
これは「いいね」系のアクションの処理であったり
iOS, Androidへのプッシュ通知の送信などで利用しています。 

また定期実行用に resque-scheduler も利用しています。 

デプロイ 

Github Enterpriseを使いながら、大体以下のような作業を行っています。

  • GitHub Enterprise にあるリポジトリのdevelopブランチにpull-request
  • merge後、Jenkinsがrspec実行、staging環境にデプロイ
  • みんなでstaging環境を確認
  • developブランチからmasterにpull-request
  • merge後、Jenkinsがproduction環境にデプロイ
デプロイはCapistranoを利用しています。
基本的には、インフラチームなどチーム外のメンバーも触れる場合があるので
unicornのrestartやらLBの切り離しなどの作業は
出来るだけJenkinsにjobを書いています。


終わりに

今回、概要だけですが、
社内でのRailsを使ったコミュニティサービスについて紹介しました。


社内のごく一部のプロジェクトのみの利用なので
あまり表に出てこないのですが、
実はきちんと利用しているんだよという認識が広まれば幸いです。

CA エンジニア Advent Calendar 2014 + 公式エンジニアブログの紹介

$
0
0

この記事は、CyberAgent エンジニア Advent Calendar 2014 の1日目の記事です。

初日は、秋葉原ラボの柿島が担当します。しょっぱなですがここの宣伝をさせていただきます。

ようこそ!サイバーエージェント公式エンジニアブログへ!

このブログではサイバーエージェントのエンジニアの技術、文化や環境の発信をしています。

おおよそ隔週の木曜日更新です。
記事はサイバーエージェントのエンジニアが自主的だったり、依頼されたりしながら持ち回りで書いています。(いつも協力してくださる社内の皆様ありがとうございます。)執筆依頼や記事の公開といった運営業務はエンジニア3人とbotでやっています。更新日が近づくとhipchatで教えてくれるbotのおかげで、更新を忘れることが少なくなりました。

bot


さてさてサイバーエージェントの公式エンジニアブログ、これまでに書かれた記事の一例を紹介させていただきます。

技術的な記事では、インフラ&コアテク本部の須藤(@strsk)が書いた

MySQL初心者に贈るインデックスチューニングのポイントまとめ2014
http://ameblo.jp/principia-ca/entry-11923272810.html

文化や環境の記事では、2012年に新卒で入社したエンジニアの白木みつか(@32ka)が書いた

キラキラ女子を支える技術
http://ameblo.jp/principia-ca/entry-11531046149.html

こんな記事があったりします!その他の過去記事も右側のカラムからどうぞ!

また、@principia_caでは当ブログの更新情報などをお伝えしています。

ぜひぜひフォローをお願いします。

今回、このエンジニアブログの運営チームの呼びかけで集まってくれたエンジニアの方々とAdvent Calendarに参加します。

CyberAgent エンジニア Advent Calendar 2014

http://www.adventar.org/calendars/358

いつもの定期更新とは違って、このブログだけではなく、エンジニア個人のブログなども活用してやっていきます。上のADVENTARのページがこの企画の中心となりますが、この公式エンジニアブログでも後追いで各記事へのリンクをはっていきます。


日付担当者内容(予定)
12/1kakishimaCA Advent Calendar 2014 + 公式エンジニアブログの紹介
12/2kakishimaRedis Cluster のリシャーディングとorphaned masterの話
12/3kuwa_tw未定
12/4yoshity未定
12/5nekoruriSwift(仮)
12/6kamatama41未定
12/7oinumeGoLangの何かについて書きます
12/8stormcat24未定
12/9horimislime未定
12/10sakatake未定
12/11yoheiMuneフロントエンジニアにもできるMongoDBでログを分析(仮)
12/12mtknnktm未定
12/13T_2uGUI関係
12/14yasuharu.sawada未定
12/15YAMITZKY未定
12/16neo6120未定
12/17yuichiro.nakazawa1ログ解析にnorikraを使ってみた
12/18sitotkfm未定
12/19strskMySQL
12/20kakerukaeruMySQLかCI関連のなにか
12/21k66dango未定
12/22ogaclejapan未定
12/23brfrn169未定
12/24shiro166未定
12/25kakishimaプレゼント企画


プレゼント企画

今回、CyberAgent エンジニア Advent Calendar 2014の読者に抽選で何かをプレゼントします。初日(12/1)と最終日(12/25)を除いた記事のどこかに応募に必要なキーワードが3つに分割されて隠れています。キーワードを完成させて12/25に発表される応募方法で応募してください。

pre


ということで、クリスマスまで CyberAgent エンジニア Advent Calendar 2014をよろしくお願いします!では!

エンジニアの僕が写真を存分に使って社内の紹介

$
0
0
CyberAgent エンジニア Advent Calendar 2014 3日目の記事です。

3日目は、インフラ&コアテク本部の桑野が担当します。今日はエンジニアの僕が写真を存分に使って社内の紹介をしてみたいと思います。

「ようこそ、サイバーエージェントへ」こちらは受付です。

受付



「ここが僕のデスクです」普段はこんな感じで作業しています。モンゴの調子はどうかな。デスク

「MyS○Lはダメだよ!そこは○ongoDBじゃないと!」ミーティングスペースはこんな感じです。

ミーティング


「このコーヒーはうまい」無料で飲める自動販売機も充実しています。

コーヒー



「今日のお弁当はなにかな?」お昼にはちょっとお得なお弁当の販売もあります。

お弁当


「勉強会開催しようかな」勉強会が開催できるような、大きなセミナールームもあります。最大でこの2倍の広さになります。(勉強会のご用命があれば是非お問い合わせください)セミナー1


「これテストにでるからね」セミナー2

「そして、1つめのキーワードはこれ」セミナー3


「この椅子に座ると心が落ち着くんだ」
リラクゼーションルームも用意されています。

リラク


「腰が痛い、あーそこそこ」
職業柄長い時間座ることが多いため、腰が痛くなることもあります。そんなときはマッサージルームも利用できます。
マッサージ


「今日はサラダが配られる日か」
最近、社員の健康を考えてサラダが配られることがあります。オイシックスさんマジオイシックスさん。

サラダ


「美味い、もといオイシックス(宣伝費は頂いておりません)」
これで明日からも元気に働こう。

サラダ2


「いかがでしたか」少しでも社内の様子が伝わったなら幸いです。

受付2

PR: 500 Mentina(メンティーナ) デビュー

アメーバピグへのGoogle BigQuery導入までのもろもろ設定記

$
0
0

この記事は、CyberAgent エンジニア Advent Calendar 2014 の6日目の記事です。
5日目はnekoruriさんのAmeba等で利用しているOpenStack Swiftを利用したオブジェクトストレージ
7日目はoinumeさんです。


こんにちは、ピグ事業部のIshimura(Twitter, Github)といいます。アメーバピグのサーバサイドエンジニアをしています。ユニットテストとリファクタリングが好物です。

今回はタイトル通りなのですが、アメーバピグでGoogle BigQueryに実際にログを突っ込むまでに行った設定を記します。アメーバピグではBigQueryを各種施策の検討・評価のための分析用に利用する予定です。

BigQueryの特徴やメリットはググれば(Googleだけに)たくさん出てくるので割愛しますが、一言で言うと「はやい、やすい、うまい」です。

システム構成

アプリサーバ(約30台)からログを収集用のサーバ(2台)にFluentdのforwardプラグインで送って、そこからfluent-plugin-bigqueryを使ってBigQueryのテーブルにinsertするという鉄板パターンです。Fluentd様々です。

ログの種類

アメーバピグでの各種行動時(会話・買い物など)で発生する行動ログを送っています。フォーマットはTSVで、ピーク時でだいたい4000行/sくらい発生します。1時間ごとにログローテされていて、「activity.log.2014-12-06_01」的なファイル名でログを吐きます。

Fluentdの設定

■アプリサーバ側
<ポイント>
tailで受けてforwardで流しているだけなので特筆する点はあまり無いのですが、ファイル名が動的に変わるので、日時のフォーマットを指定して(%Y-%m-%d_%H)動的にtailするファイルを変えていたり、fluent-plugin-config-expanderを利用して、hostnameをタグにつけていたりしています。(調査時に受け取る側で特定のhostからログをちゃんと受け取れているかをチェックするため)
<source>  type config_expander  <config>    type tail    path /usr/local/app/logs/activity.log.%Y-%m-%d_%H    format tsv    pos_file /var/log/td-agent/activity.log.pos    refresh_interval 2s    keys datetime,activity_id,user_id,content    tag bq.activity.${hostname}  </config></source><match bq.activity.**>  type forward  buffer_type memory  buffer_chunk_limit 256k  buffer_queue_limit 1024  retry_wait 5  retry_limit 5  flush_interval 1s  num_threads 1  <server>    name aggr_server01    host xx.xx.xx.xx    port 24224  </server>  <server>    name aggr_server02    host yy.yy.yy.yy    port 24224  </server></match>
■収集用のサーバ側
<ポイント>
後述するテーブルローテーションに対応するため、fluent-plugin-rewrite-tag-filterでログ内の日付フィールド(datetime)から年月をタグにつけ、fluent-plugin-forestでタグに付与された年月に対応したテーブルにinsertできるようにしています。

insert量のチューニングでは、最初は余裕を見ているつもりでbuffer_chunk_records_limit=300, buffer_chunk_limit=768kにしていたのですが、なぜか「413:Request Entity Too Large」がたまに発生したので、更に値を下げてGoogle側の制限の半分に設定しました。
<match bq.activity.**>  type rewrite_tag_filter  capitalize_regex_backreference yes  # Datetime format :=> "2014-01-23 12:34:56"  rewriterule1 datetime ^(\d{4})-(\d{2})-.+$ rewrited.bq.activity.$1$2</match><match rewrited.bq.activity.**>  type forest  subtype bigquery  <template>    method insert    flush_interval 0    try_flush_interval 0.05              queued_chunk_flush_interval 0.01    buffer_chunk_records_limit 250   # BQ側の最大値が500行    buffer_chunk_limit 512k          # BQ側の最大値が1mb    buffer_queue_limit 1024          # チャンクをいくつ保持しておくか(メモリの許す限りなるべく多めに)    retry_limit 5                        retry_wait 1s                        num_threads 32                   # 送信処理を行うスレッド数(多めにしておく)    auth_method private_key    email **********@developer.gserviceaccount.com    private_key_path /etc/bigquery/**********.p12    project **********    dataset pigg    tables pigg_activity_${tag_parts[-1]}    schema_path /etc/bigquery/bq_schema_pigg_activity.json  </template></match>

テーブルローテーション

BigQueryはスキャンしたデータのサイズで課金額が増えるので余計な課金を防ぐために月ごとにテーブルを分ける事にしました。ただ、毎月テーブルを手動で作るのはプログラマの美徳に反するので以下のRubyスクリプトをcronで定期実行して翌月分のテーブルを作成しています。

※注意

google-api-ruby-clientは実装時点(2014/11)で、普通にgem  installした場合、依存ライブラリ(retriable)のバージョンアップの関係で実行時にエラーになるので、gituhubのmasterのコードをspecific_installを使ってインストールしました。
<参考>:  https://github.com/google/google-api-ruby-client/issues/164
require 'date'class Pigg  class BigQueryApiClient    def initialize(private_key_path, email)      require 'google/api_client'      require 'google/api_client/client_secrets'      require 'google/api_client/auth/installed_app'      require 'json'      client = Google::APIClient.new(        :application_name => 'pigg-activity-table-checker',        :application_version => '1.0.0'      )      key = Google::APIClient::KeyUtils.load_from_pkcs12(private_key_path, 'notasecret')      client.authorization = Signet::OAuth2::Client.new(        :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',        :audience => 'https://accounts.google.com/o/oauth2/token',        :scope => 'https://www.googleapis.com/auth/bigquery',        :issuer => email,        :signing_key => key)      client.authorization.fetch_access_token!      bq_api = client.discovered_api('bigquery', 'v2')      @client = client      @bq_api = bq_api    end    def table_exists?(project, dataset, table)      res = @client.execute(                :api_method => @bq_api.tables.get,                :parameters => {                    :projectId => project,                    :datasetId => dataset,                    :tableId => table                }      )      # 404(NotFound)も正常と見なす      unless res.success? || res.status == 404        raise "#{res.status} #{res.body}"      end      res.success?    end    def insert_table(project, dataset, table, schema_file_path)      body = { :tableReference => { :tableId => table } }      schema = open(schema_file_path) do |io|        JSON.load(io)      end      body['schema'] = { :fields => schema }      res = @client.execute(          :api_method => @bq_api.tables.insert,          :parameters => {              :projectId => project,              :datasetId => dataset          },          :body_object => body      )      unless res.success?        raise "#{res.status} #{res.body}"      end    end  endend# 翌月のactivityログ用のBigQueryのテーブルがあるかをチェックし、無ければ作成する# 書式は(pigg_activity_yyyymm)private_key_path = '/etc/bigquery/**********.p12'email = '**********@developer.gserviceaccount.com'project = '**********'dataset = 'pigg'schema_file_path = '/etc/bigquery/bq_schema_pigg_activity.json'table_id_format = 'pigg_activity_%Y%m'# 来月のテーブルIDを生成するtable = Date.today.next_month.to_time.strftime(table_id_format)client = Pigg::BigQueryApiClient.new(private_key_path, email)# テーブルが無ければ生成unless client.table_exists?(project, dataset, table)  client.insert_table(project, dataset, table, schema_file_path)  puts "#{Time.now} Table(#{table}) created."end
こんな感じで今は安定してBigQueryにログを送れている状態です。実際クエリを叩いてみた感想はやっぱり噂通りの速度でした。。思い立ってから2週間程度でこの環境が手に入るという先人たちの努力に感謝ですねっ。

あわせて読みたいアメーバピグ関連の記事たち

アメーバピグにおけるDB構成&対応記
Node.js Cluster+Socket.IO+Redisによるリアルタイム通知システム
ピグ麻雀のアルゴリズム
アメーバピグのソケットサーバーたち
Viewing all 161 articles
Browse latest View live




Latest Images