14
GoでKVSを書けるのか Moriyoshi Koizumi

GoでKVSを書けるのか

Embed Size (px)

DESCRIPTION

Shibuya.pm #12 発表資料です

Citation preview

Page 1: GoでKVSを書けるのか

GoでKVSを書けるのかMoriyoshi Koizumi

Page 2: GoでKVSを書けるのか

Goとは• Plan9の開発者であるKen Thompson, Rob Pike, Russ Coxらが開発した新しいコンパイル型プログラミング言語

• コンパイル言語でありながら動的な型の扱いが可能

• Erlang や Ada のような CSP (並行プログラミング)を言語組み込みでサポート

• Dと同じようにネイティブコードに変換して実行する形式を取りつつ、オブジェクトのGCを行う

Page 3: GoでKVSを書けるのか

Goで適当なKVSを作る

•サーバソケットの作成、接続の待受および受容•セッションの維持•プロトコルのハンドリング• Key-Value を格納するバックエンド

Page 4: GoでKVSを書けるのか

ソケットの扱い

•net パッケージを利用•待受: net.Listen("tcp", "127.0.0.1:11211")

•受容: net.Accept()

•煩雑なsockaddr周りのこととかは全部パッケージが面倒見てくれます!!

Page 5: GoでKVSを書けるのか

セッションの維持

•標準パッケージを使って、pollerを使ったイベント駆動なサーバは今のところ書けない (syscall直接呼ばない限り)

•普通に Goroutine を使うgo handleConn(sock);

Page 6: GoでKVSを書けるのか

func main() {    if len(os.Args) < 2 {        fmt.Fprintf(os.Stderr, "usage: %s addr:port\n", os.Args[0]);        os.Exit(255);    }     l, e := net.Listen("tcp", os.Args[1]);    if e != nil {        error("An error occurred (%s)", e.String());        os.Exit(1);    }     // blocking channel    backend_ch := make(chan *kvsBackendConn);     go backend(backend_ch);     for {        ch, e := l.Accept();        if e != nil {            error("An error occurred (%s)", e.String());            os.Exit(1);        }        go handleConn(ch, backend_ch, false);    }    os.Exit(0)} 

Page 7: GoでKVSを書けるのか

プロトコルのハンドリング

opcode := int(header[1]);key_length := (int(header[2]) << 8) | int(header[3]);extra_length := int(header[4]);data_type := int(header[5]);body_length := (int(header[8]) << 24)                | (int(header[9]) << 16)                | (int(header[10]) << 8)                | int(header[11]);

まあこうなっちゃいますよね

Page 8: GoでKVSを書けるのか

KVバックエンド

•とりあえずビルトインのmapを使ってみる• 1個のGoroutineとして実装 (アクター指向的に)•バックエンドとセッションを担うGoroutine間のチャネルを同期的なものにすることでロックを行う (mutexとか明示的に書かなくても済む)

Page 9: GoでKVSを書けるのか

func backend(ch <-chan *kvsBackendConn) {    kvs := make(map[string] []byte);    for req := range ch {        switch (req.command) {        case 0: // get            value, e := kvs[string(req.key)];            if e {                req.resp_ch<- (&kvsBackendConn{command:0, value:value})            } else {                req.resp_ch<- (&kvsBackendConn{command:0, value:nil})            }            break;        case 1: // set            kvs[string(req.key)] = req.value;            break;        }    }}

backend_ch<- (&kvsBackendConn{ command: 0, key: key, value: nil, resp_ch: recv_ch });result := <-recv_ch;resp_extra := make([]byte, 4);if result != nil {    resp = buildResponse(opcode, 0, ...)} else {    resp = buildResponse(opcode, 1, ...)}

Page 10: GoでKVSを書けるのか

できたー

Page 11: GoでKVSを書けるのか

で、パフォーマンスは?

実装 / パラメータ 所要時間 QPSGo / GOMAXPROCS=1 1.563 6397.95Go / GOMAXPROCS=2 1.564 6393.86Go / GOMAXPROCS=3 1.579 6345.18Go / GOMAXPROCS=4 1.696 5896.22

memcached (1.4.4) 0.429 23310.02

GOMAXPROCSの値が性能に影響してないのはローカルでやってるからかと。意外と善戦

memslap --test=get --concurrency=4 --binary --verbose--servers=127.0.0.1 --execute-number=100 --initial-load=10000--tcp-nodelay

on Xeon W3520 @2.66GHz

Page 12: GoでKVSを書けるのか

netパッケージの実装•結構、てか相当適当•これで性能出すのは割と大変では?

Poll serverWrite()FD FD FD

FD FD FD FD

FD

Read() FD

Write()

Read()

FD

epoll(2)/kqueue(2)

channelcommunication

once.Do(startServer)

syscallでblockしているGoroutineを起こすのにpipeを利用 (!)

Page 13: GoでKVSを書けるのか

まとめ•LLじゃないのにイベント駆動型じゃないネットワークコードが楽勝で書けるぜ!!!!と思いきや…

•つまるところchannelとfdを混ぜて使うにはpipe(2)やeventfd(2)を使うしかなく、アレな感じになってしまうので

•結局直接触らせろ!! (pollerに!!) という話になってしまう

Page 14: GoでKVSを書けるのか

提 供

moriyoshi