2017年を振り返って

今年は仕事で関わっているプロダクトが大きな転換期を迎えて、様々な経験ができました。 ミドルウェアを自ら作り上げ、データをオンラインで移行し、運用を始めるというのはなかなか経験できないことだと思います。 サービスは以前より安定し、穏やかな年末を過ごしています。

今年は初めてカンファレンスで登壇しました。 慣れないことばかりで色々と戸惑いましたが、沢山の方に発表を聞きに来ていただいて嬉しかったです。 マネージドサービスを組み合わせて1つのソフトウェアを作り、それをサーバーレスミドルウェアとして抽象度を上げて捉えることができるようになったもの、このカンファレンスに参加してよかったことでした。

今年は19記事書きました。 特に、以下の記事は多くの方に読んでいただきました。

一年の後半にアウトプットが減速しているのは、カンファレンスの登壇に体力を使ってしまったこと、Prime VideoやAbemaTVを見ながらぼーっとする時間が増えたこと、プロダクトコードのリファクタリングに随分と入れ込んでしまったことなどが原因だと思います。

年始はコンパイラーやインタープリターに興味があり、インタープリターを書いてみたりLLVMを試したりしていたのですが、3月ぐらいには興味も薄れていきました。8月くらいまでは仕事が忙しく余裕がありませんでしたね。その後Rustを真面目に書き始め、システムコールに興味を持ち、そしてMackerelのシステムメトリックに興味が移り、go-osstatを作り始めました。最近はエディターの実装に興味を持ち、色々と調べています。ことごとくトレンドを外した感じに共感を持てますね、まぁ自分のことですが。

Vim周辺の変化で言うと、vimshellを辞めてterminalを使いだしたことくらいですが、他はほとんど変化はありませんでした。lightlineのスター数は順調に伸び、2500を突破しました。ユーザー数は増えてもissue報告はそんなに増えていないし、ほとんどが設定の仕方に関する質問なので楽なものです。

今年は投資を始めた年でした。これまで一切経済のニュースとか興味がなかったのですが、今のペースで経済が成長していくと現金で持っているのがアホらしく思えてくるので、少しずつ時間を取って勉強し始めています。まだド素人なので特に語れることはありません。

生活面、仕事面共に大きな変化はありませんでしたが、緩やかに成長できた一年だったと思います。来年は少しずつアクセルを踏んで頑張っていきましょう。

山村美和「なんでも本気でやるから楽しいんじゃん。」

ばらかもん

Go言語のsyscall.Sysctlは最後のNULを落とす

カーネルのパラメータを引いたり設定したりする時に便利なのが sysctl コマンドです。

 $ sysctl kern.ostype
kern.ostype: Darwin

このコマンドのシステムコールをGo言語から叩いて、OSの種類を引いてみましょう。

func main() {
    ret, _ := syscall.Sysctl("kern.ostype")
    fmt.Printf("%s\n", ret)
}
Darwin

問題ないですね。 数字を返すものを叩いてみましょう。

 $ sysctl machdep.cpu.feature_bits
machdep.cpu.feature_bits: 9221959987971750911
func main() {
    ret, _ := syscall.Sysctl("machdep.cpu.feature_bits")
    val := *(*uint64)(unsafe.Pointer(&[]byte(ret)[0]))
    fmt.Printf("%d\n", val)
}

出力結果

9221959987971750911

unsafeパッケージは使いたくないだって? アーキテクチャエンディアン固定になっちゃうけどなぁ。

   val := binary.LittleEndian.Uint64([]byte(ret))
9221959987971750911

次は extfeature_bits を見たい?

 $ sysctl machdep.cpu.extfeature_bits
machdep.cpu.extfeature_bits: 1241984796928
func main() {
    ret, _ := syscall.Sysctl("machdep.cpu.extfeature_bits")
    val := binary.LittleEndian.Uint64([]byte(ret))
    fmt.Printf("%d\n", val)
}

実行してみます

panic: runtime error: index out of range

goroutine 1 [running]:
encoding/binary.binary.littleEndian.Uint64(...)
    /usr/local/Cellar/go/1.9.2/libexec/src/encoding/binary/binary.go:76
main.main()
    /private/tmp/main.go:16 +0x1d7
exit status 2

!!!> index out of range <!!!

なにが起きたのか。 syscall.Sysctl の返り値 (string) の長さを見てみると、すぐにわかります。

ret, _ := syscall.Sysctl("machdep.cpu.feature_bits")
fmt.Printf("%d\n", len(ret))
ret, _ = syscall.Sysctl("machdep.cpu.extfeature_bits")
fmt.Printf("%d\n", len(ret))
8
7

え、つまりこれは… https://github.com/golang/go/blob/8776be153540cf450eafd847cf8efde0a01774dc/src/syscall/syscall_bsd.go#L474

   // Throw away terminating NUL.
    if n > 0 && buf[n-1] == '\x00' {
        n--
    }
    return string(buf[0:n]), nil

な、なんということを…

つまりstructでも…?

ret, _ := syscall.Sysctl("kern.boottime")
fmt.Printf("%d\n", len(ret))
15

お、おう… わかった… わかったよ…

  • sysctl.Sysctl は最後のNULを落とす。
  • 文字列を返すような場合はよい挙動だが、数字や構造体の場合は注意が必要。64bit整数を返す場合、その値に依存して8byteだったり7byteだったりする。
  • unsafe.Pointer で数字や構造体にキャストする分には問題ないように思われる (ほんまやろか?)。 + "\x00" してから処理したほうがいいかもしれない。

困っている人もいる () が、指摘されているように sysctl パッケージは変更を受けつけていないので、この挙動が変わることや別の関数が追加されることはなさそう。 そもそも string で返すのなんかおかしくない? []byte で欲しいよね…

それあります! golang.org/x/sys パッケージの unix.SysctlRaw を使いましょう。 https://github.com/golang/sys/blob/53aa286056ef226755cd898109dbcdaba8ac0b81/unix/syscall_bsd.go#L524

func main() {
    ret, _ := unix.SysctlRaw("vm.loadavg")
    fmt.Printf("%d\n", len(ret))
    ret, _ = unix.SysctlRaw("kern.boottime")
    fmt.Printf("%d\n", len(ret))
    ret, _ = unix.SysctlRaw("machdep.cpu.extfeature_bits")
    fmt.Printf("%d\n", len(ret))
}
24
16
8

これで安心して眠れそうですね。

sysctl.Sysctl は最後のNULを落とします。文字列として欲しい時はこれで良いが、バイト列として欲しい時は unix.SysctlRaw を使いましょう。

Go言語のHTTPリクエストのレスポンスボディーとEOF

Reader interface の Read 関数は、どのタイミングで io.EOF を返すのでしょうか。 まずは strings.Reader で見てみましょう。

package main

import (
    "fmt"
    "strings"
)

func main() {
    r := strings.NewReader("example\n")
    for {
        var b [1]byte
        n, err := r.Read(b[:])
        fmt.Printf("%d %q %v\n", n, b, err)
        if err != nil {
            break
        }
    }
}

結果

1 "e" <nil>
1 "x" <nil>
1 "a" <nil>
1 "m" <nil>
1 "p" <nil>
1 "l" <nil>
1 "e" <nil>
1 "\n" <nil>
0 "\x00" EOF

Readの結果は、読み込んだbyte数です。なにもなくなってから io.EOF を返していることがわかります。

ファイルだとどうでしょうか。

package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("main.go")
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }
    defer f.Close()
    for {
        var b [1]byte
        n, err := f.Read(b[:])
        fmt.Printf("%d %q %v\n", n, b, err)
        if err != nil {
            break
        }
    }
}
1 "b" <nil>
1 "r" <nil>
1 "e" <nil>
1 "a" <nil>
1 "k" <nil>
1 "\n" <nil>
1 "\t" <nil>
1 "\t" <nil>
1 "}" <nil>
1 "\n" <nil>
1 "\t" <nil>
1 "}" <nil>
1 "\n" <nil>
1 "}" <nil>
1 "\n" <nil>
0 "\x00" EOF

同じですね。

ではHTTPリクエストだとどうでしょうか。

package main

import (
    "fmt"
    "net/http"
    "os"
)

func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }
    defer resp.Body.Close()
    for {
        var b [1]byte
        n, err := resp.Body.Read(b[:])
        fmt.Printf("%d %q %v\n", n, b, err)
        if err != nil {
            break
        }
    }
}
1 "<" <nil>
1 "/" <nil>
1 "h" <nil>
1 "t" <nil>
1 "m" <nil>
1 "l" <nil>
1 ">" <nil>
1 "\n" EOF

なぜなのか… なぜなのか!!!

検索するとruiさんのエントリーが出てきました。 qiita.com (三年前の記事だった… もしかして… これは常識なのか!?) そして、mattnさんがgolang-nutsでスレッドを立てられていたのでざっと見ました。 Issue 49570044: code review 49570044 も勉強になります。 これはContent-Lengthがセットされたレスポンスボディーを最後まで読んだ後に、すぐにコネクションを使いまわせるようにするための意図した挙動であるということがわかりました。

Readerはわりと使い慣れたinterfaceなのにハマってしまいました。 Go言語を書いていると手癖ですぐにerrを返してしまいがちですが、Readは読み込んだバイトがありながら io.EOF になることがあることに気をつけなくてはいけません。 いや、これはruiさんの記事を同じことを言ってますね… ほんとは常識なのかもしれない…

ライブラリーが Reader 使ったインターフェースを公開しておきながら、io.EOF の扱いが適切でないと簡単にバグを踏んでしまいます。 strings.Reader でテストして満足していると、resp.Body に繋いだ瞬間なぜか挙動が変わるということがあるかもしれません。

Read の返り値のバイト数を捨ててませんか? err != nil でもデータを読み込んでいるかもしれませんよ?

みなさん、気をつけましょう。おわり。

zshの標準エラー出力の色を赤くする

[追記]以下の方法は良くないようです。必ず、このエントリー最後の「stderredを使う」を参照してください[/追記]

最近stderrを赤くするように設定したら、コマンドの出力がかなり見やすくなりました。 f:id:itchyny:20171116233845p:plain

設定はこんな感じに書いてます。

zmodload zsh/terminfo zsh/system
color_stderr() {
  while sysread std_err_color; do
    syswrite -o 2 "${fg_bold[red]}${std_err_color}${terminfo[sgr0]}"
  done
}
exec 2> >(color_stderr)

fg_bold[red] のところを fg[red] とかbg_bold[red] とかするとスタイルを変更できます。 古いzshでは動かないらしいので、古い環境も気にしたい場合は is-at-least 4.3.4 でチェックするとよさそうです。

この設定の元ネタはcoloring stderr - was Re: piping stderrです。 大体はうまくいくし、普段使う分には大きな問題は起きていないのですが、わりと乱暴なことをやっているという自覚はあります。 元ネタのスレッドでも触れられていますが、 echo L1; echo L2 >&2; echo L3; echo L4 >&2 とやると L2L3 が逆に出力されたりします。 あと bash -i したあとに top を起動できないといったりします。

いくつか問題が起きるケースはありそうだけど、二週間ほど試してみて普段端末を触る範囲では困っていないのと、stderrに色が付くのがありがたいので使っています。もっといい方法があれば教えてください。

ついでに使っているPROMPTの設定はこんな感じです。終了コードを元に色を変更しています。

PROMPT="%(?.%{$fg[green]%}.%{$fg[blue]%})%B%~%b%{${reset_color}%} "
PROMPT2="%{$bg[blue]%}%_>%{$reset_color%}%b "
SPROMPT="%{$bg[red]%}%B%r is correct? [n,y,a,e]:%{${reset_color}%}%b "

stderredを使う

上の設定を使うと、 echo foobar | vim - が動かなくなります。これは結構困ります。端末で出力先を無理やり変える方法はやはり色々トラブルの元となるようです。 代わりにstderredを使うのをおすすめします。ビルドする必要はありますが、上の方法よりも直接的な方法なのでトラブルは起きないと思います。 github.com

if [ -f /usr/local/lib/libstderred.dylib ]; then
  export DYLD_INSERT_LIBRARIES="/usr/local/lib/libstderred.dylib${DYLD_INSERT_LIBRARIES:+:$DYLD_INSERT_LIBRARIES}"
  export STDERRED_ESC_CODE=$'\x1b[1;31m'
fi

Serverlessconf Tokyo 2017で『サーバレスアーキテクチャによる時系列データベースの構築と監視』という発表してきました

先日開催されたServerlessconf Tokyo 2017にスピーカーとして参加しました。

2017.serverlessconf.tokyo

Mackerelの今の時系列データベースは、マネージドサービスを組み合わせて作っています。 検証・実装・投入フェーズを終えて、運用・新機能開発フェーズに入っています。そんな中で、監視サービスを提供する私たちが、サーバーレスアーキテクチャで作ったミドルウェアをどのように監視しているかについてお話しました。 何かしら役に立つことや発想の元となるようなことをお伝えできていたらいいなと思います。

私も他の発表から様々なことを学びました。特に面白かった発表を挙げておきます。

真のサーバレスアーキテクトとサーバレス時代のゲーム開発・運用

ゲーム開発を支えるBaaSを開発されるなかで得られた様々な知見についてお話されていました。 プッシュ通知のために大量のコネクションを張りたい場面でAWS IoTを用いるといった話はとてもおもしろいと思いました。 アクセス権限の話でLambdaのメモリーにキャッシュを持つという話をされていて、そんな手があるのかと驚きましたが、物理的な限界がありスケールしないので、使いどころ (というかヒット率に基づくキャッシュの削除) が難しいなと思いました。

サーバーレスについて語るときに僕の語ること

軽快な語り口と盛り上げ方はランチセッションにふさわしく、うまいなぁと思いました。 サーバーレスというのは文字通りだと枠としては大きすぎるんですよね。 スケーラビリティーやイベント駆動により処理を繋げるアーキテクチャも包含する言葉がほしいなぁという気持ちになりました。 よっしゃサーバーレスやとかいってサービスを選んだ時に、その用途が本当にそのサービスの適した使い方なのかどうかはきちんと考えるべきですよね。

Open source application and Ecosystem on Serverless Framework

なるほど確かに、こういう方向に進んでいくよねという気持ちになりました。 サーバーレスミドルウェアという言葉が面白かったです。聞いた瞬間、様々なものが繋がった気がしました。 マネージドサービスを組み合わせて作るという流れはおそらくこれからも続いていくと思いますし、それを組み合わせることで1つの「ミドルウェア」として機能するものを作るのはよくあることだと思います。 そういうサーバーレスミドルウェアは一発で立ち上がるべきだし、利用者が実装内部 (どういうマネージドサービスを使っているか) を気にする必要なく使えるようになっているのが理想の形。 こういうミドルウェアって再利用可能な形でシェアできるといいし、それらを組み合わせてサービスを構築できるとかっこよさそうですよね。 そう、docker-composeみたいなやつが欲しいですね (あるのかしら、まだよく知らないです)。

最後に

私は対外発表は初めてこともあり、とてもよい経験になりました。 慣れていなくてストーリーを組み立てるのに苦労したり、資料を作るのに時間がかかったりしましたが、なんとか終わって今は安堵しています。 E2E監視というところを抽象化して、系全体として動いているかを見るというところに落とし所を見つけられたのは、発表の前日でした。 私たちが作ったのは時系列データベースの「サーバーレスミドルウェア」なんですよね、こういうまとめて見た時の概念を思いついていなかったのは正直くやしい。 k1LoWさんには発表や質疑応答の中でもdiamondに触れていただいて、親近感がわきました。

マネージドサービスを組み合わせてサービスを作ることは、セキュリティの問題やスケーラビリティー、運用コストの削減など様々な問題を解決してくれると思います。 ただし、各マネージドサービスにはそれぞれ使いどころや苦手なユースケースがありますので、他の成功事例だけを見て盲目的にコンポーネントを選ぶのではなく、適した使い方をしているかどうかをきちんと吟味しましょうということですね。 こういうアーキテクチャーに乗ったサービス開発はどんどん加速していくと思いますので、私も技術的に置いていかれないように精進していきたいと思います。

主催の吉田さん、運営スタッフのみなさま、本当にありがとうございました。