タイトル

Need for Answer

2015年5月28日木曜日

New ReicのScalability analysisって何?

New Relic使ってますか!

というか、New Relicの「REPORTS」項目、見てますか!
今回はその中の、「Scalability analysis」について解説しようかな、というお話。

…「Scalability analysis」とは、読んでそのままの意味でスケーラビリティ分析をしてくれるものです。つまり「インフラがボトルネックになっているか?」がわかるのです!すごい!

では、読み方。2つのキャプチャを見てみましょう。




このグラフから何を読みとくか?というと、「rpm(Requests Per Minutes)が上昇しても、応答速度にムラが無いか?」です。横軸がrpm、縦軸が平均応答速度です。「右端のカラーチャート」と「グラフ内の点」を照らし合わせると、「いつのアクセスなのか?」がわかります。つまり何時頃にはどれくらいのrpmだったのか?がわかります。

そもそもの話ですが、rpmはユーザーアクセス数に比例します。具体的には「夜中は少なく、昼は多い」ということになります。こういった時刻ごとの変化も一目瞭然ですね。

さて「rpmが上昇」ということは、「1台のサーバーで対応するユーザー数が上昇」ということです。…ということは「インフラリソースが不足すると、応答速度が遅くなる」と考えられます。逆に考えると、「応答速度が安定しているということは、インフラリソースは充足している」事になります。

ということで結論としては、「線グラフが水平」「点が集中して上下の幅が少ない」というのが、インフラリソースが充足しているいい感じ、になります。

右肩上がりや、点がバラバラになっている場合は、どこかにボトルネックがあるという事ですね。

2015年5月23日土曜日

golangでredisに超大量にデータを入れ続けるプログラムを書きましたのまき

件名通りなのですが、redisにダミーデータを入れ続けるだけのgolangプログラムを書きました!
みんなもつかってね!

…なーんてプログラム使う人居ないと思うので、書いてた時に学んだことを共有しようかなというお話。


■コンソール出力すると極端に遅くなる
golangが早いのはご存知なことだと思うのですが、思ったよりコンソール出力が遅いです。
超大量データを処理する際、毎行ログ出力するのは無駄が多いです。

100行に一回出力とか間引きした方がいいですよ、ということですね。


■Network I/Oはコネクションプーリング等の工夫が必要
処理が早いということで、CPU以外の部分にもボトルネックが来る可能性があります。
redisへのアクセスを毎回切断していたら、あっという間にTCP接続を使い切ってしまいました。

コネクションを使いまわす、を早い段階で考えたほうがいいですね。


■ifでgoroutineを呼び出すときに、ループ数が極端に多いと落ちる
goroutineはループに入る前にスレッド(みたいなもの)を事前に用意するっぽいです。10万ループとかをgoroutineで非同期化すると、「signal: killed」で落ちます。

…ということでどうするかという話なのですが、「gorc」いうパッケージを使うと回避できます。goroutineの同時並列実行数に制限がかけられるため、リソースを使い切るのが回避できます。すごい。

※追記)sync.Poolとかsync.Cond使うとsyncでもスロットル制御出来る気がします!書いたこと無いけど!


package main

import (
  "crypto/md5"
  "crypto/rand"
  "encoding/hex"
  "flag"
  "fmt"
  "github.com/garyburd/redigo/redis"
  "github.com/mr51m0n/gorc"
  "math/big"
  "runtime"
  "strconv"
  "time"
)

var (
  pool *redis.Pool
  host string = "localhost:6379" // redis-server接続情報
  key int = 150000 // 投入するKEYの数
  val int = 10000 // 文字列の長さ
)

// redis ConnectionPooling
func newPool(server string) *redis.Pool {
  return &redis.Pool{

    MaxIdle: 3,
    IdleTimeout: 240 * time.Second,

    Dial: func() (redis.Conn, error) {
      c, err := redis.Dial("tcp", server)

      if err != nil {
      return nil, err
    }
    return c, err
  },

  TestOnBorrow: func(c redis.Conn, t time.Time) error {
    _, err := c.Do("PING")
    return err
    },
  }
}


// val用にrandomな文字列を生成する
func random(length int) string {
  const base = 36
  size := big.NewInt(base)
  n := make([]byte, length)
  for i, _ := range n {
    c, _ := rand.Int(rand.Reader, size)
    n[i] = strconv.FormatInt(c.Int64(), base)[0]
  }
  return string(n)
}


// gorc
var gorc0 gorc.Gorc


// メイン処理
func main() {
  runtime.GOMAXPROCS(runtime.NumCPU())

  flag.Parse()

  // Timer Set
  start := time.Now()

  // redis Pooling Start
  pool = newPool(host)

  // Redis DataInsert
  for i := 0; i < key; i++ {
    gorc0.Inc()
    go setredis(i, val)

    gorc0.WaitLow(100)
  }


  // Log Print
  fmt.Print(key)
  fmt.Printf("Keys - Set Done.\n")

  // Timer Print
  end := time.Now()
  fmt.Printf("%f秒\n", (end.Sub(start)).Seconds())
}


// stringからmd5から作る
func GetMD5Hash(text string) string {
  hash := md5.Sum([]byte(text))
  return hex.EncodeToString(hash[:])
}


func init() {
  gorc0.Init()
}


// redisにデータを投入するだけの処理
func setredis(k, v int) {

  c := pool.Get()
  defer c.Close()
  defer gorc0.Dec()


  vals := random(v)
  keys := GetMD5Hash(vals)


  c.Do("SET", keys, vals)


  // 100処理ごとにログ表示(非同期なので順番が適当)
  if k%100 == 0 {
    fmt.Print(k)
    fmt.Printf("Keys - Set Done.\n")
  }
}

2015年5月4日月曜日

koboのKoboReader.sqliteをメンテナンスするのまき

電子書籍リーダのkobo使ってますか!
長期間使っていると不安定になりますよね!

…というのは、書籍データの中身をsqliteというファイルに保存しているのですが、『電子書籍データを読み込む度に肥大化し続ける』という困った設計になってます。

今回のお話は、このファイルをシェイプアップする方法です。
Windowsの『PupSQLite .NET Framework4』というソフトを使います。

まずkoboデバイスの「.kobo/KoboReader.sqlite」というファイルを一旦HDDにコピーします。
PupSQLiteに先ほどのKoboReader.sqliteを読み込ませます。

するとこんな画面が出ますので「SQL入力」を押して、タブをオープンします。


さて、ここからはSQLを使います。
『ウィンドウに下のSQLを入力して、稲妻ボタンを押す』という流れです。



件数を確認してみましょう。
select count(*) from content ;
select count(*) from content where ___UserID='cbz_user';

多分すごい数字(数千~数万)が出てきたと思うので、レコードの削除をします!
delete from content where ___UserID='cbz_user';

先ほどのSQLをもう一度流してみましょう。成功していればレコード数が減少しているはずです。
select count(*) from content ;
select count(*) from content where ___UserID='cbz_user';

レコード数は減ったので、膨れ上がったファイルをシェイプアップします。画面上にある「最適化」ボタンを押してください。見た目には変わらないのですが、ファイルサイズが縮小されます。


これでPupSQLiteでの作業は終了です。

あとはkoboの.koboディレクトリにKoboReader.sqliteを書き戻してあげれば作業完了です。