鳩舎

レースしない

Go Conference 2013 Autumn で Gondler の話をした

Go Conference 2013 Autumn で命短し恋せよ乙女というタイトルで Gondler の話をしてきました。

他にもたくさんおもしろい発表があって、個人的には Hatena で Go の REST サーバーを立てて運用している話とかおもしろかったです。 Golang in Production !!

次回も喋れたら喋りたいなとおもっているので、今のうちからいろいろ頑張ってみようと思います。

Gondler という bundler for golang を作った

2番煎じ乙。 Gondler という Bundler みたいなツールを go 向けに作りました。gom という先行実装があるのでもう作らんでもええかな感があったのですが、Ruby っぽいけど Ruby じゃない Gomfile とか、bundle exec 相当の機能がないとか、そういう理由でわざわざ自作しました。

基本的には bundler なので、バージョン、というよりコミットハッシュとかをロックするのに使います。

fork 元の gom との違いは

  • インストール先ディレクトリがデフォルトでは .gondler になっている(変更可能(gom は変えられない))
  • Gondler 環境下の repl が起動できる(gondler repl)
  • Gomfile を自動生成する機能はない
  • Travis 用の yml を自動生成する機能はない
  • bundle exec 相当の gondler exec があるgom にも gom exec ありました
  • Ruby 実装である(gom は go 実装)
  • Gomfile は Ruby による DSL なので Ruby のようにスクリプトを埋め込める
  • git と同じようにカスタムコマンドが作れる(gondler-your-command)

ぐらいでしょうか。

依存パッケージを固定できるというのは、複数人開発の時必須の機能なはずなので、 go が多用されてるっぽい Google 内部では一体どれほどの混乱が起こっているのかわかりませんが、とにかくほしいよねということで作りました。 Gomfile 自動生成くらいは、気が向いたらサポートするかもしれません。

気に入ったら Star してくれたら嬉しいです!

channel lock

channel でブロックするようなコードが書ける。例えばスレッドの並列数を制限したいとか、そういう簡単な用途で使うのに取り回しがいい。

package main

import (
  "fmt"
  "time"
)

func main() {
  go func() {
    for {
      fmt.Printf(".")
      time.Sleep(100 * time.Millisecond)
    }
  }()

  queue := make(chan int, 3)

  time.Sleep(1 * time.Second)
  queue <- 1
  fmt.Println(1)

  time.Sleep(1 * time.Second)
  queue <- 2
  fmt.Println(2)

  time.Sleep(1 * time.Second)
  queue <- 3
  fmt.Println(3)

  go func() {
    time.Sleep(3 * time.Second)
    <-queue
  }()

  time.Sleep(1 * time.Second)
  // Lock
  queue <- 4
  fmt.Println(4)
}

0.1 秒毎に . が出力されて、 1 秒ごとに 1,2,3 と出力していく。queue は3つまで許容するので、4つめの Lock のところで queue に 4 が送信できなくてロックする。その上にある goroutine で 3 秒後に queue から 1 つ受信して捨てると、 4 が送信できるようになって動く。

出力はこんな感じになる

..........1
..........2
..........3
..............................4

4 のときだけ 3 秒ロックしてるのがわかる。ところでこの queue から受信するコードをなくすとどうだろう。ついでに邪魔だから . を出力していた goroutine もなくしてしまおう。

package main

import (
  "fmt"
  "time"
)

func main() {
  queue := make(chan int, 3)

  time.Sleep(1 * time.Second)
  queue <- 1
  fmt.Println(1)

  time.Sleep(1 * time.Second)
  queue <- 2
  fmt.Println(2)

  time.Sleep(1 * time.Second)
  queue <- 3
  fmt.Println(3)

  time.Sleep(1 * time.Second)
  queue <- 4
  fmt.Println(4)
}

例外が出る。

1
2
3
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /Users/rosylilly/tmp/go_channel_test/main.go:24 +0x22e
exit status 2

全部の goroutine が 寝てて、何もしてないからデッドロック状態だ、ということらしい。デッドロック検知して自分で死ぬ。モダンな感じだ。

ノーモア・モアレ

ImageMagick で普通に画像をリサイズするとモアレがひどいことになる。

具体的には LANCZOS Filter でリサイズするとひどい。

なので Bicubic を使って、かつ線がボケないようにリサイズを行いたいときには、以下のようにする(golang w/ imagick)。

wand.SetImageInterpolateMethod(imagick.INTERPOLATE_PIXEL_BICUBIC)
wand.ResizeImage(w, h, imagick.FILTER_LANCZOS2_SHARP, 1)

LANCZOS2 のシャープフィルタと併用するとなかなか綺麗になる。また、この時には SetOption で jpeg:size を利用していると、その時点で荒いリサイズになってしまってモアレが出るので注意

golang JSON struct tag

go で JSON のパースする時は、こんな感じ

package main

import(
  "fmt"
  "encoding/json"
)

var text = []byte(`{"Name":"sho"}`)

func main() {
  type Config struct {
    Name string
  }

  config := &Config{}
  json.Unmarshal(text, &config)

  fmt.Printf("%+v\n", config)
}

ただ、これだと Namename しかパースしてくれない。構造体のメンバの名前は "Addr" にしたいけど、 JSON では "address" と書きたいみたいな要求があるときは、tag をつけておくといい。

package main

import(
  "fmt"
  "encoding/json"
)

var text = []byte(`{"onamae":"sho"}`)

func main() {
  type Config struct {
    Name string `json:"onamae"`
  }

  config := &Config{}
  json.Unmarshal(text, &config)

  fmt.Printf("%+v\n", config)
}

JSON では Underscore case で、 Go では Upper camel case 書きたいみたいなわがままさんはこういうことするといい。 JSON でどうかけばいいかわかりやすくなるし一石二鳥っぽい。

gofu is tofu clone implemented by golang

go 言語かけるようになるとメス鳩のウケが良いと聞いたので頑張って勉強しました。リリースは大安か友引の日を選ぶタイプの鳩、ロージーです。

gofu(護符) という、弊社の id:mirakui さんが作ったイケてる画像リサイズサーバー、 tofu の go 実装です(tofu についてはこのへんを見ると良いです)。

もちろん全部機能が載ってるわけでもなく、ひみつのハッシュ値あたりとかはどう実装されてるのか知らんし今回必要ないのでつけてません。

護符が出来ることは以下で全てです。

  1. S3 から画像をとってきて表示することが出来ます(/image/path.jpg にアクセスすると S3 の /image/path.jpg を取ってきます)
  2. 取ってきた画像からは自動的に Exif その他のメタ情報が消えています。
  3. これらのパラメータでリサイズなどの処理を付与できます。
    • w : 画像の横幅を変更します(リサイズ)
    • h : 画像の縦幅を変更します(リサイズ)
    • q : 画像が jpg なら画像のクオリティを指定できます(1 〜 100)
    • b : 画像のブラーを指定できます(0.0 〜 1.0)
    • c : 画像をクロップします。横幅,縦幅,開始位置x,開始位置y のように指定します(ex: 100,200,10,50)
  4. JSON による設定ファイルでいくつかの挙動が変更出来ます。詳しくはコードを見てください(ドキュメントない)。
  5. LRU Cache を積んでいるので S3 から受け取った Blob をキャッシュします。デフォルトの保持数は 1000 エントリです。LRU Cache は廃止され、現在はファイルによるキャッシュになっています。 dir.cache にディレクトリを指定することが出来ます。現在はファイルベースでの LRU みたいなものをやろうと試行錯誤中です。
  6. 設定ファイルで fcgitrue にすると、 FCGI として起動します。 nginx とのお供にどうぞ。デフォルトでは普通の HTTP サーバーとして起動します。
  7. ログフォーマットを text/template による記法で自由設定することが出来ます。combinedltsv を指定すると、組み込みのフォーマットが採用されます。

多分これ以上の機能は実装してません。

とりあえず手元の MBA で動かしてる限りでは、1429 × 949 で 912KB の jpg を S3 から引っ張ってきて 100x100 にリサイズする処理で1回目は 2625ms 。2回目は LRU Cache が効いて 58ms でした。そこそこ。

並列性とかよくわかってないで書いてるので、複数のコネクションでリクエストが大量にくるとメモリリークかなんか起こして死ぬのではないかと疑っていますが、きっと golang だし大丈夫でしょ、ぐらいでまだ手を付けていません。

golang 簡単にそれっぽいプログラム書けて快適だし、皆も遊ぶといいと思います。

No more 仏滅。

golang s3

go 言語から S3 にアクセスする。

goamz つかえばあとは大体どうとでもなるみたい。

Mac に bzr が入ってなかったのでまず入れる。

$ brew install bzr
$ echo "export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH" >> ~/.zshrc

つぎは goamz/s3 のインストール

go get launchpad.net/goamz/s3

そこそこ時間かかる。

入ったらコード書いてみる。

package main

import (
  "fmt"
  "launchpad.net/goamz/aws"
  "launchpad.net/goamz/s3"
)

func main() {
  auth, err := aws.envauth()
  if err != nil {
    panic(err)
  }

  s3clinet := s3.new(auth, aws.apnortheast)
  bucket := s3clinet.bucket("rosylilly")

  data := []byte("hello, s3!!")
  err = bucket.put("sample.txt", data, "text/plain", s3.bucketownerfull)
  if err != nil {
    panic(err.error())
  }

  content, err := bucket.get("sample.txt")
  if err != nil {
    panic(err.error())
  }
  fmt.println(string(content))
}

ほぼサンプルのコピペ、書き込んで読み込んで表示するだけ。これで動く。bucket.Get で取得も出来る。

err にはステータスコードとか入ってるので、例外処理とかはそこでやる感じになる。