golangのGCとかgoroutineの状況を確認するライブラリ

golangで作った長時間動かすアプリで「goroutineリークやメモリリークがないか知りたい」とか「GCの影響がどの程度か知りたい」とかないですか?ありますよね?
そのためのログをダラダラ出力するためのライブラリを公開しました。

# 元々はクローズドなトコで作ったモノを、公開のため完全フルスクラッチで書きなおしてます。

github.com

使い方はmainのアタマとかに適当に組み込むだけです。

func main() {
  // 1分ごとにログにjson出力
  t := stats.SchedulePeriodically(time.Minute, func(s *stats.Stats) { log.Println(s) })
  defer t.Stop()

  // あと本来の処理を……
}

# 標準ロガーがアレなら、お好みのロガー使ってください。

String()で生成されるjsonはその時点のgoroutineの数とruntime.MemStatsをそのままMarshalしただけのやる気ないモノです。

……はい、なんというか、完全に手抜きですね(;・∀・)
とはいえ、まったく情報がないのと比較すると、トラブったときの調査の捗り方が違います( ー`дー´)キリッ

なお、個人的には適当にローカルに書き出しておいて後からjqで眺めてみたりぐらいしかやってないんですけど、ElasticsearchなりNorikraなりでアレコレするのも面白いかと思います。
# その場合はfluent/fluent-logger-golangを使うと捗る……のかな???

そんなこんなで、もうすぐリリースされるハズのgo1.7でGCがどれくらい改善されたか、とか確認できるといいですねー

marisa-trieのgo bindingを書いた

id:s-yataさんが公開してくださっているmarisa-triegolangで使えるようなバインディングを公開しました。

github.com

marisa-trieは非常に省メモリなtrie実装です。
特徴等は公式ドキュメントを参照してください。

インストールも普通にgo getするだけでOKです。よければどーぞ。
# g++とかstdlib++とかは必要になりますけど。

使い方等のドキュメントは……今後の課題ということで(;・∀・)

go getでインスコできるc/c++なライブラリのラッパーの作り方?

go buildはパッケージディレクトリにおいている.cや.ccファイルは一緒にコンパイルしてくれるのですが、サブディレクトリまでは面倒見てくれません。
かといってライブラリのソースをパッケージディレクトリにコピーしたりするとライブラリの更新の追従とかがしんどくなってきます。

そこで今回は、元ライブラリをgitのsubmoduleとした上で、コンパイル対象のファイルをincludeしただけのやる気ない.ccファイルを用意してみました。

このやり方がいいのかわかりませんが、とりあえずはgo getでまとめて全部コンパイルしてくれるし、ライブラリ更新の追従もgit submoduleでゴニョるだけでよいので、結果的にシンプルに出来たような気がします。

他にこういう時のうまいやり方があるのなら、教えて頂けるとウレシイなぁ。。。

golangで書いたアプリケーションをどう動かすか?

まとまりなく、何パターンか列挙します。

アプリケーションコンテナで動かす

通常ステートレスなアプリに限られると思いますけど、dockerで動かすというやり方です。
# 個人的にはdocker 1.12で組み込まれたswarmモードがすごくお手軽でよいと最近思ってます。

バイナリはstatic linkでビルドして、alpineで動かすと軽量でイイカンジです。
Dockerfileは以下みたいなカンジ

FROM alpine
RUN apk add --no-cache ca-certificates
COPY your_app /usr/local/bin/
CMD ["your_app"]

外部サービスにssl/tls接続するのに必要なのでca-certificatesを突っ込んでます。
証明書周りを自分でなんとかするんなら、busyboxにするのもアリかと。

supervisordで動かす

定番ですね。
個人的にはOSのお決まりのサービス管理方法(init.dやsystemd)と異なるレイヤが増えるので好きではないです。

initスクリプトを頑張って書く

/etc/init.d/hogehoge みたいなスクリプトを用意しておいて、以下のように使うイメージです。

# service hogehoge start

中身はnohupを使ってなんちゃってdaemonize、とかですね。
スクリプト書くのは正直ダルいですけど、前処理/後処理を柔軟に書けるので、systemd以前はコレを好んでやってました。

systemdのサービスで動かす

/etc/systemd/system/ 以下にserviceファイルを書いて、systemctlで頑張ります。

serviceファイルはミニマムだと以下のようなカンジです。

[Unit]
Description=hogehoge

[Service]
Type=simple
ExecStart=/path/to/hogehoge
ExecStop=/bin/kill -SIGTERM $MAINPID

[Install]
WantedBy = multi-user.target

ExecStartPre/ExecStartPost/ExecStopPostで前処理、後処理も書けます。
ExecStopPreは存在しませんが、停止時の前処理はExecStopが複数記述できるので、それで対応しましょう

なお、limitsの設定とか実行ユーザの指定も簡単です。

[Service]
LimitNOFILE=65536
User=hogehoge

また、Type=simpleの場合は、起動したタイミングでsystemdは起動完了とみなしてくれるんですが、 アプリケーション的に初期化に時間が掛かるケースもあると思います。

そんな時はアプリをsd_notifyに対応させた上で、Type=notifyにしてあげればイイカンジになります。

というわけでsd_notifyに対応するためのライブラリを書いてみました。
使い方はsampleを見ればすぐわかると思います。

Google App Engineで動かす

書いといてアレですけど、すみません、やったコトないです(;・∀・)
docker以上に軽量でいいんじゃないですかね? 知らんけど。

Herokuで動かす

やったコトないですけど、選択肢の一つとして。

AWS lamda?

公式対応があるといいなぁ。。。
# node経由やり方もある、らしいけど。。。

golangで書いたアプリケーションのstatic link化

「goで書いたアプリケーションは実行ファイルひとつコピーするだけでいいのでインスコ超ラクチン」なんて思ってたんですが、 go1.4からnetパッケージを使っているアプリケーションは、フツーにビルドするとdynamic linkになるようになってました。

$ cd /path/to/your_app
$ go build
$ file your_app
your_app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

そんなわけで別環境にバイナリコピーしても動かないケースが発生して超絶アタマを悩ませることになるのですが、 そんなときは以下のようにbuildすればstatic linkになってくれるようです。

$ go build -a -tags netgo -installsuffix netgo
$ file your_app
your_app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

参考 https://github.com/golang/go/issues/9369

cgoを完全に使ってない場合は以下のようにcgoを無効化してビルドしてもstatic linkになります。

$ CGO_ENABLED=0 go build
$ file your_app
your_app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

なお、アプリケーションやライブラリ側でcgo使ってる場合は、glibcの関係でそれでもdynamic linkになります。 でも以下のようにstatic linkするようにオプションを追加してあげればOKです。

$ go build -a -tags netgo -installsuffix netgo --ldflags '-extldflags "-static"'

# CentOSの場合、glibc-staticがインスコされてないと、ldでコケます。

build後のバイナリを配布したいとか、dockerでalpineにバイナリコピーしてイメージ作ったんだけど動かねぇ……とかっていう場合にどーぞー。

Goでスケールする実装を書く、を読んで

コチラの記事の感想というかなんというか、です。

Goでスケールする実装を書く - GolangRdyJp

なんつーか、概ね伝えたいコトは同意できるんですけど、用語の誤用っぽいモノがあってスッとアタマに入ってこないのです。
# ワタシの解説がアヤシイ場合は是非コメント等でツッコんでいただけるとウレシイです!

"冪等性"について

ある実装が他のコアやプロセス、他のホストで稼働中の場合、 どこにある実装で処理を実行したとしても 引き渡す情報が同じなら結果も同じであることを「べき等」であるという。

このような実装の場合、いくつもその実装が動く環境を用意しておいて、 順番に使われていない実装に丸投げするように分散させることで スケールして大きく性能を確保できる。

冪等性って「繰り返し同じ処理を行っても、結果が一緒」ってことで、数学的にいうと {f(x) = f(f(x))}だし、 chefとかでいうと「とある状態のサーバ」に何度「cookbookを適用」しても「最終的な結果は一緒」ということです。

冪等性があるからといって「何度繰り返しても」ってトコが並列化できるか、というとそんなことはないですよね?

じゃあ元記事でいいたいことはなにかっていうと、たぶん「参照透過性」のコトだと思います

元記事の全体通して s/べき等性/参照透過性/g として読んでみると、そこの分の違和感はなくなります。

"Lock-Free/Wait-Free"について

いや、もう、なんか、えーっと。。。

  • 排他制御を経由しない。(これはWait-Free扱い)
  • 排他制御はあるが待ちに入る状況にならない。

その説明だと、排他制御内の処理を(並列で)実行しないっていってるようなもんで、なんだかなぁと。


Lock-Freeは言葉通り「スレッドがロックしないこと」ということではあるんですけど、もうちょっと具体的にいうと 「mutexとかの排他処理機構をつかうと同時に実行されるとlockしちゃうから使わないようにしよう!」ということです。

Wait-Freeに関しては「他のスレッドの動作に関係なく、有限のステップで操作を完了させられること」なんですけど、具体的な説明は難しいので雑に説明すると「Lock-Freeかつ、"CAS操作を成功するまで繰り返す"みたいな処理がないこと」です。

そんなわけで、そもそも排他制御があったらLock-Freeって言わないと思いますし、Wait-Freeでもないと思います。

"リードオンリーなデータ参照のLock-Free化"について

いや、readonlyな(言い換えるとimmutableな)データの参照はそもそもスレッドセーフですよね?
Lock-Free云々とか文脈でアレコレ言及する必要は全くないかとー。

なお、まったく関係ないですけど、将来的に「NUMAアーキテクチャに対応するため、readonlyなデータは安全にコピーできるので、各goroutineにコピーを持つようにしよう」とか言われる時代がくるんですかね? どうですかね? 教えてエライヒト!

あと「変更するgoroutineが一つで他が参照だけ」というのは、とても有効なパターンだと思います。
他のgoroutineからの変更を考えなくてよいのでWait-Freeにできる(CAS操作の元の値のチェックでの失敗がなくなる)ので。

……という元記事とはまったく違う解説をしてみるテスト


なお「変更するgoroutineが一つ」という場合でも、単純に変数に値を代入するだけだと危険なケースがあります。他goroutineから参照されるケースでは黙ってatomicパッケージのStore系の操作を使いましょう。
# 詳細はgoのメモリモデルを見てください。

そんなこんなで、、、

ここまでツッコんでおいてアレなんですけど、個人的に並列化しやすいアプリを組む場合に気をつけてることは、というと……

  • mutableよりimmutable.
  • 参照透過性を意識して実装する
  • 排他制御をできるだけ排除する
    • できればLock-Freeに。
    • さらに可能ならWait-Freeに
  • 排他制御が必要なら、その範囲をできるだけ狭く本当に必要な分だけにする

という元記事の結論と大差ない内容なのですね。

ですので、元記事は説明がアレで本当にもったいないなぁと思う次第でありまして……

golangのチャンネルでセマフォ的なナニカ

mattnさんのエントリに触発されて、某所で使用したちょっと変わったgolangのチャンネルの使い方をご紹介します。

mattn.kaoriya.net


特定の処理の並列度をある程度までに抑えたい、みたいなコトありますよね?
例えばCPUヘビーな処理の並列数をたかだかコア数くらいまでに抑えたい、とか。

そんなときはバッファ付きチャンネルを用意しておいて、当該処理の前後でそのチャンネルにwrite/readをすることで、セマフォ的な制御ができます。

以下のようなカンジです。

package main

import (
    "fmt"
    "sync"
    "time"
)

var ch chan int = make(chan int, 4) // 並列度を4に制限

func heavyFunc(i int) {
    ch <- 1 // チャンネルのバッファがイッパイになっていたら、ブロックする
    defer func() { <-ch }()

    fmt.Println("start:", i)
    time.Sleep(time.Second)
    fmt.Println("end:", i)
}

func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func(i int) {
            heavyFunc(i)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

並列数が上限に達している際にブロックさせたくない場合は、ちょっと変えて以下のようなカンジにすると良いです。

func heavyFunc(i int) error {
    if len(ch) == cap(ch) {
        // バッファがイッパイだったらさっさとerrorを返す
        return errors.New("too busy!")

    }
    ch <- 1
    defer func() { <-ch }()

    fmt.Println("start:", i)
    time.Sleep(time.Second)
    fmt.Println("end:", i)

    return nil
}

(追記もみてください)
厳密にはlen/capのチェックを同時にくぐり抜けたgoroutineがあるとブロックしてしまう可能性があります。
回避するにはさらなる同期処理が必要になります……けど、そこまでする必要があるケースは少ないんじゃないかなぁ……と。


余談ですけど、これ使うときに並列数をコア数より少なめに設定しちゃうと、結果的にCPUを使い切れなくなるので

  • 並列数はコア数
  • GOMAXPROCSはコア数より少し多め

としておくと重い処理でCPUをフルで使いつつ、平行してそれ以外の処理も細々と処理できるという状況を作れます。

こちらからは以上です


2016-07-07 追記

golangのチャンネルでセマフォ的なナニカ - okzkメモ

len==capのところは、https://play.golang.org/p/CBvBeQ-jO8 のように select を使うとraceが発生しないんじゃないかな

2016/07/07 10:38
b.hatena.ne.jp

上記コメントのPlaygroundのリンクです。

( ゚д゚)ハッ!
channelのwrite時にもselect使えるって知りませんでした! ありがとうございます!!!


ちなみにさくっと諦めてエラーを返すヤツですけど、Web APIみたいなので503 Service Temporarily Unavailable 返すのに使ったことあります。

もっと作りこめばバックプレッシャー制御もできるかもしれませんね。夢ひろがりんぐ

某記事のフォロー

会社でかいた記事はてブのツッコミのフォローをゆるゆる書いてみるテスト

記事中取り消し線多くてゴメンナサイ。

理解が乏しく恐縮なのですがこれは、CSSスプライトみたいなことをアーカイブファイルとインデックスファイルで実現した、という理解でよろしいのでしょうか。 - ngsw のコメント / はてなブックマーク

CSSスプライトみたいに「ページ表示に必要な複数の画像を効率よく配信する方法」ではなく、「それほどアクセス多くない画像ファイルをストレージ上にどう保存するか」という観点で読んで頂けると幸いです。

Swiftはオブジェクトが増えると書き込みパフォーマンスが下がる話、GREEでも言ってたけどやっぱり厳しいんだな。/ Log structured files 自前実装は気合入ってる。 - ono_matope のコメント / はてなブックマーク

オンラインで更新があるわけではないので記事上では"log structured"という表現は避けたのですが、最終的にアーカイブファイルと同じフォーマットのファイルを作る"log structuredなHotな分散ストレージ"の設計も実は進めてました。

異動で完全になかったコトになりましたけど。

退職エントリかとおもったら、異動エントリだった。 - coyu8 のコメント / はてなブックマーク

さすがに会社の公式エンジニアブログで退職エントリを書く勇気はないですw

lsが重たくなるのってさらにファイル名の末尾文字でサブディレクトリに分けて逃げたりするけど、そういうレベルではない?一括コピーはloopデバイス使うとか - nakag0711 のコメント / はてなブックマーク

「1ディレクトリに大量のファイルを作ってしまう」というレベルのハナシではなく、「ファイルシステム全体で数億ファイルとかになったら、IO負荷が上がりやすくなってしまう」という内容でした。

わかりにくくてすみません。

学びあるエントリだった。ただところどころよくわからん単語が出てくることがあったんだけどもあれは社内用語的なものなのか、それとも単純にぼくの無知なのか。 - luccafort のコメント / はてなブックマーク

いろんな単語を解説なしに散りばめてしまったのはゴメンナサイ。
記事内では社内限りの用語は排除したつもりです。

大体はググッていただければ何かしら引っかかる用語だと思いますが、 "small_light系"だけそのままだと検索できないかもしれません。"mod_small_light"や"ngx_small_light"で調べて頂けるとよいかと。

それでも出てこない用語があった場合はコメント等でツッコんでください。