VOYAGE GROUP エンジニアブログ

voyagegroup_techのブログ
VOYAGE GROUPエンジニアブログです。

2012年03月

第3回チューニンガソンで2位になりました!!

こんにちは!株式会社adingoでエンジニアをしている前田(@brtriver)といいます。
弊社の岩川(@hogehiko)とペアで参戦し2位に入賞することができました!
TOPのスコアは本当に僅差でしたので、運も味方に付けた結果なのですが、簡単にレポートしてみたいと思います。



事前準備
前日に作戦会議といいつつドイツビールがおいしいお店で二人してただ呑んだくれるという状態で何も準備せずに当日を迎えました。
もしかして、緊張せずに寝れたことが大事だったかもしれません。 

作業分担
お題はblojsomというJavaアプリケーションのチューニングでした。
PHPのアプリでなかったことは想定内でしたが、JavaでTomcat縛りというのは予想していませんでした。
Javaまわりは岩川が詳しいので私はMySQL周りを担当とある程度分業してチューニングをすることに
参考スコアが63だったので、この値を目標にがんばることに。

開始
スタート時のスコアは以下のとおり
Score: 17.844 (get=16.900, comment=0.944(1), check=1.000)

アナウンスでは初期ベンチが23ぐらいと聞いていたのでかなり低いなぁという印象。

最初の準備
設定を色々変更し、アプリケーションの再起動もかなりの回数を重ねるだろうということで
restart.shというシェルスクリプトを用意し、再起動やDBのコメントデータのtruncateなどをあらかじめ用意。
また、どの設定や変更が有効なのかを把握するために、一度の多くの変更は行わずにこまめにベンチを取得するようにしました。
当たり前のことなのですがこのおかげもあって、突然スコアが劇的に下がった場合に原因がわからないということもなく安定していたと思います。
サーバーの状態はtopとvmstatで確認というレベルでした。


チューニングの記録
MySQLの設定は最初からある程度行われている状態でした。
メモリ周りの設定を増やすことでスコアは24.377に。

まだまですね。
そして、スロークエリなども確認したのですが全く出ていないのでボトルネックにはなっていない印象。

java周りでは、Tomcatのメモリを増やして調整することで一気に48.278に!
この時点の中間発表で2位になっていたので、「もしかしたら...!!」という期待をもちつつ気合が入りました。

さらに、Tomcatのスレッド数を減らしたり、MySQLのバージョンを5.5に上げる+スレッド数を調整した結果スコアも60近くになりました。

このころに自身のベンチで最高値を叩いていました。
Score: 63.246 (get=49.400, comment=13.846(14), check=1.000)

しかし、このあと何故か60台を超えることなく、50台をさまようことに....

だいたい思いつく施策はやった感があったので、あとは本番の計測でベストな値を出すためにはどうすればよいか?という点を相談していました。

ベンチ結果は複数回実行するとスコアに変化が見られました。
作業開始当初はアプリケーション再起動後に複数回ベンチを叩くことでスコアが上昇する傾向だったのが、
JVMを6から7に上げたあたりから再起動後最初のベンチが一番良いスコアを叩く傾向に変わって来ました。

直接の原因はわからないですが、本番で一番よい結果を出すには、終了直前にアプリケーションを再起動させて何も触らない状態で終わることだろうと判断。
ラスト15秒ぐらいで一番最初に用意したrestart.shを叩いてあとは神に祈るのみという状態でした。

一番最後に自分たちでベンチした結果は
Score: 59.477 (get=58.499, comment=0.977(1), check=1.000)

結果
hogehiko_brtriver002


2位 hogehiko & brtriver ペア / 59.486



また、弊社のもうひとつのチーム(@kenichikat)が相方が障害対応のため急遽個人参戦にもかかわらず10位入賞と大検討していました!

まとめ
今回のチューニンガソンはJavaの環境設定をどこまで最適化できるかが大きかったと思います。
nice値の調整などまだまだできることがあったことを考えると優勝できていた可能性があっただけに悔しいです><
5時間という時間制限は想像以上に短く、疲労感もありますが、充実感が半端ないので是非皆さんには参加してほしいイベントです!

メタプログラミングR:重い関数をキャッシュする

こんにちは、VOYAGE GROUPの水越(@Akiyah)です。

最近、仕事でデータ解析環境「R」を使ってデータベースからデータを取り出して加工することが多いです。
そういったとき、データベースからデータをとる部分はSQLでまるまる取得して、その後Rでもりもり加工するのが好きです。
ですがその場合、何度も実行するとSQLの処理が重いしデータベースに負担をかけることになるので、ちょっと困っていました。

そこで今回、Rのメタプログラミングで関数呼び出しをキャッシュ化してみることにしました。
キャッシュと言ってもずっと同じ値を返すのではデータベースに新しいデータが入ったときに更新されないで困るので、ある一定期間(例えば24時間)を過ぎたら最新の値をとるように作ります。

キャッシュ無し

まず最初に、"重い処理"という文字列を出力する関数を作ります。
heavy_func1 <- function() {
  print("重い処理")
  result <- rnorm(1) # ランダムな数字
  return(result)
}
このprint("重い処理")が重いSQLであると思ってください。
heavy_func1を何度か実行すると、
> heavy_func1()
[1] "重い処理"
[1] -2.146503
> heavy_func1()
[1] "重い処理"
[1] 0.07116749
> heavy_func1()
[1] "重い処理"
[1] -0.277692
何度も"重い処理"が実行されているのがわかります。

グローバル変数を用いたキャッシュ

それではキャッシュするバージョンを作ってみましょう。Rでは実行環境(ワークスペース)にデータを持たせて、Rの終了時にファイルに保存して次のR起動時に読み込む事ができるので、グローバル変数にキャッシュを入れておけば今回の用途には十分です。単純に書くとこうなります。
heavy_func2.cache <- NULL
heavy_func2 <- function() {
  now <- Sys.time()
  if(!is.null(heavy_func2.cache)) {
    if (10 > now - heavy_func2.cache$updated_at) {
      return(heavy_func2.cache$value)
    }
  }
  print("重い処理")
  result <- rnorm(1) # ランダムな数字
  heavy_func2.cache <<- list(value=result, updated_at=now)
  return(result)
}
<<-は永続代入と言って、関数定義からグローバル変数に代入するときに使います。キャッシュ(heavy_func2.cache)にはキャッシュする値(value)と更新日時(updated_at)のリストを入れておきます。

実行してみると、
> heavy_func2()
[1] "重い処理"
[1] -0.06969717
> heavy_func2()
[1] -0.06969717
> heavy_func2()
[1] -0.06969717
"重い処理"がはじめの一回しか呼ばれていない事がわかります。そして戻り値はキャッシュが効いて同じ値を返しています。キャッシュの更新時間はここでは10秒にしているので10秒経つともう一度処理が実行されて、値も新しいものになります。
> heavy_func2()
[1] "重い処理"
[1] 0.8203284
ちなみにRでは変数名に"."(ピリオド)が使えます。逆に言うとピリオドが使われていたからと言ってそれはJavaやRubyなどの言語のようなオブジェクトの属性へのアクセスと言うわけではありません。

明示的なグローバル変数宣言を削除

実はこのままだと困る事があります。関数定義の直前にキャッシュのグローバル変数を定義しているので、たとえば関数定義を別ファイルにして何度も呼び出すと、その度にキャッシュが消えてしまうのです。そこを改善してみます。
heavy_func3 <- function() {
  now <- Sys.time()
  if(exists("heavy_func3.cache")) {
    if (10 > now - heavy_func3.cache$updated_at) {
      return(heavy_func3.cache$value)
    }
  }
  print("重い処理")
  result <- rnorm(1) # ランダムな数字
  heavy_func3.cache <<- list(value=result, updated_at=now)
  return(result)
}
グローバル変数を直接定義するのではなく、変数が定義されているかどうか確認する関数existを使うようにした事で、明示的なグローバル変数の定義を消す事ができました。やっとメタプログラミングっぽくなってきましたね。もうちょっと進めてみます。

明示的なグローバル変数アクセスを関数経由に

heavy_func4 <- function() {
  now <- Sys.time()
  cache_name <- paste("heavy_func4", "cache", sep=".")
  if(exists(cache_name)) {
    cache <- get(cache_name)
    if (10 > now - cache$updated_at) {
      return(cache$value)
    }
  }
  print("重い処理")
  result <- rnorm(1) # ランダムな数字
  assign(cache_name, list(value=result, updated_at=now), envir=.GlobalEnv)
  return(result)
}
グローバル変数へのアクセスを全て関数(exists, get, assign)経由にしました。assignの引数にある.GlobalEnvはグローバル環境を表します(このあたりをもっと有効に使う例はまた今度)。

キャッシュを別関数化

そろそろ仕上げです。このキャッシュの仕組みを共通化して別の関数に出します。
cache.get <- function(key) {
  now <- Sys.time()
  cache_name <- paste(key, "cache", sep=".")
  if(exists(cache_name)) {
    cache <- get(cache_name)
    if (10 > now - cache$updated_at) {
      return(cache$value)
    }
  }
  return(NULL)
}

cache.set <- function(key, value) {
  now <- Sys.time()
  cache_name <- paste(key, "cache", sep=".")
  assign(cache_name, list(value=value, updated_at=now), envir=.GlobalEnv)
}

heavy_func5 <- function() {
  cache <- cache.get("heavy_func5")
  if(!is.null(cache)) return(cache)
  print("重い処理")
  result <- rnorm(1) # ランダムな数字
  cache.set("heavy_func5", result)
  return(result)
}
キャッシュしたい関数の最初と最後にちょっと差し込めばキャッシュ化が可能になりました! ふー、一段落ですね。

残りタスク

    残りタスク
  • キャッシュの更新時間を設定できるようにする
  • キャッシュを高階関数化して関数を渡すとキャッシュ化した関数を返すようにする
  • グローバル環境を使わないようにする
  • キャッシュ化する関数のソースコードが変更された場合はキャッシュをクリアするようにする
残りタスクは解決できたらまた報告します。
記事検索
QRコード
QRコード