GCP memorystoreの性能を見てみる

# memorystoreとは
- memorystoreはGCPのredisマネージドサービスです
- BasicとStandardの２つのサービスを提供しています。BasicはHAがない構成でStandardはHA（zone replicationとautomatic failover）構成になっています
- 価格はデータ容量ベースで決まります
- 現在のredisのバージョンは3.2です

# 性能
## ワークロード
- 1KBのデータ100万件をmemorystoreに格納します
- データのkey値はstringで1 - 1000000の値が入ります
- 各クライアントは1 - 1000000のkey値をランダムに選択してgetを行います
- クライアント数を増加させていきgetのレイテンシを測定します

## memorystoreの設定
- インスタンスの容量  5GB
- ネットワークスループット  375MB/秒
- redisはデフォルト設定です

## 結果
|クライアント数  |平均レイテンシ（μs）  |最大レイテンシ（μs）  |
|--:|--:|--:|
|1  |350  |638  |
|10  |390  |918  |
|50  |507  |817  |
|100  |1,270  |9,530  |
|200  |2,870  |9,830  |
|400  |6,620  |25,253  |
|600  |10,462  |30,400  |
|800  |14,708  |25,786  |

- redisの性能を本格的に見たのは始めてたのですが、他のディスクベースのNoSQL（HBaseとかCassandraとか）に比べると安定的に早い性能が出てる感じですね
- 800同時接続でも数十ミリ秒のレイテンシなので、ほとんどのシステムで十分と言える性能ではないでしょうか

## 測定プログラム
- 参考に測定プログラムを載せておきます。GOで書かれてます。
- host,portは適宜書き換えてください
- オプションは--threadでスレッド数（クライアント数）を指定、--loadでmemorystoreに測定データをロードするかどうかとなります

```
package main
 
import (
    "flag"
    "fmt"
    "math/rand"
    "os"
    "sync"
    "time"
 
    "github.com/gomodule/redigo/redis"
)
 
type Result struct {
    count int
    total int64
    max   int64
}
 
var rChan = make(chan Result, 10000)
var wg = new(sync.WaitGroup)
 
const HOST = "10.0.1.4"
const PORT = "6379"
const KEYS = 1000000
 
func main() {
    for i, v := range os.Args {
        fmt.Printf("args[%d] -> %s\n", i, v)
    }
 
    t := flag.Int("thread", 1, "thread numbers")
    p := flag.Bool("load", false, "initial load")
    flag.Parse()
 
    if *p {
        load()
    }
 
    for i := 0; i < *t; i++ {
        wg.Add(1)
        go perf()
    }
 
    wg.Wait()
    close(rChan)
 
    count := 0
    var total, max int64 = 0, 0
    for r := range rChan {
        count = count + r.count
        total = total + r.total
        if max < r.max {
            max = r.max
        }
    }
 
    fmt.Printf("count = %d\n", count)
    fmt.Printf("total = %d\n", total)
    fmt.Printf("avg = %f\n", float64(total)/float64(count))
    fmt.Printf("max = %d\n", max)
}
 
func perf() {
    redisAddr := fmt.Sprintf("%s:%s", HOST, PORT)
    fmt.Printf("redis addr: %s\n", redisAddr)
 
    const LOOP = 10000
 
    conn, err := redis.Dial("tcp", redisAddr)
    if err != nil {
        panic(err)
    }
 
    var start, end time.Time
    var total, max int64 = 0, 0
    for i := 0; i < LOOP; i++ {
        rand.Seed(time.Now().UnixNano())
        start = time.Now()
        r, err := redis.String(conn.Do("GET", string(rand.Intn(KEYS))))
        end = time.Now()
        if err != nil {
            panic(err)
        }
        if i%1000 == 0 {
            fmt.Printf("count: %d, out: %s\n", i, r)
        }
 
        t := end.Sub(start).Nanoseconds()
        total = total + t
        if max < t {
            max = t
        }
 
    }
 
    var result Result
    result.count = LOOP
    result.max = max
    result.total = total
 
    rChan <- result
    defer wg.Done()
}
 
func load() {
    const base = "d"
    const length = 1000
    var data string = ""
    for i := 0; i < length; i++ {
        data = data + base
    }
 
    redisAddr := fmt.Sprintf("%s:%s", HOST, PORT)
    fmt.Printf("redis addr: %s\n", redisAddr)
    conn, err := redis.Dial("tcp", redisAddr)
    if err != nil {
        panic(err)
    }
 
    for i := 0; i < KEYS; i++ {
        _, err := redis.String(conn.Do("SET", string(i), data))
        if err != nil {
            panic(err)
        }
    }
}
```