JSONを扱うときにkeyとfieldを対応づける

Goの、encoding/jsonUnmarshal で、例えばJSONデータのkeyが小文字の場合、自動でcapitalizeしてくれる。

pakcage main

import(
  "encoding/json"
)

type A struct {
  A_Id int
  Field string
}

func main() {
  jsonBlob := []byte(`
    {"a_id": 100, "field": "field"}
  `)
  var a A
  json.Unmarshal(jsonBlob, &a)
  fmt.Printf("%+v\n", a)
}

ここで、JSONの要素にsnake_caseなa_idがkeyになっている。 Goが変数や、構造体のフィールド名はCamelCaseを推奨しているので、CamelCaseなフィールド名にしたい。

type A struct {
  AId int
  Field string
}

Goのドキュメントを読んでいると、

type A struct {
  AId int `json:"a_id"`
  Field string `json:"field"`
}

と書いておくと、UnmarshalしたときにKeyに紐づくフィールドを認識してくれることを知る。 文法として、Goのstructはフィールドにtag(型はstring)を付与でき、この情報はreflectで取得できる

func main() {
  a := A{}
  t := reflect.TypeOf(a)
  field := t.Field(0)
  fmt.Println(field.Tag.Get("json")) //=> a_id
}

便利。

effective-goではない何か

去年からGolangを書き散らかしてみて、だいぶセンス感じとれるようになってきたので、Golangを書くときのイディオムのようなものをまとめてみる。

Golangをコーディングするディレクトリ

簡単な、10行20行くらいで済むコードであればどこで書いてもいいのだけれども、 ライブラリだったりモジュールに分けるレベルの規模のコードだと、コーディングするディレクトリは、$GOPATH直下、または$GOPATHへのsymlinkにしたほうが良い。

例えば、ライブラリを作成しようとしてホスティング場所はGitHubだとする。 その作成しているライブラリを使ってサンプルコードを書こうとすると、 import宣言は、

import (
  "github.com/yoppi/a-library"
)

となる。 ただ、$GOPATHにないとビルドできないので、まだGitHubにpushしていない場合はもちろん、「少し修正して書き直す」という繰り返しの中でGitHubにpushしてgo getしないと手元でコード書けない、という状況は辛い。 そこで、書いているライブラリは、$GOPATH直下 -- たとえば$GOPATHが $HOME/.go/ だとすれば、$HOME/.go/src/github.com/yoppi/a-library -- に作成すると、問題なくビルドできる。 ちなみに、$GOPATHに直接作成するか、$GOPATHへsymlink張るかの流派で分かれている、模様。 僕は、symlink派。

ライブラリ兼実行コマンドありのディレクトリ構成

ライブラリとしても使うのだけど、実行コマンド(たとえばa-execという名前だとする)もあるライブラリを作るときは、

a-library
  `
  + a-exec
  |   `
  |   + main.go
  + a-library.go
  ...

と、実行コマンド名をディレクトリ名にして、直下にmain.goとするディレクトリ構成だとわかりやすいと思っている。 こうしておくことで、go get github.com/yoppi/a-library/a-exec で追加でインストールも可能になる。

ちなみに、実行コマンドだけのものを作るときでそこそこ規模が大きくなる場合、

a-exec
  `
  + bin
  |   `
  |   + a-exec.go
  + module.go
  ...

みたいな構成にしている人が多そう。

構造体を生成するコンストラクタ

構造体を生成するコンストラクタ関数名には、型名に接頭語Newを付けることにしている。 むしろ、それ以外の接頭語が付いている名前だと、他の人が書いたものを読んだ時に戸惑ってしまう。 Golangで組み込み関数であるnew()もあることですし。 そして、この関数は型宣言した、直下に宣言しておくとさらに読む人が把握しやすいので便利。

type A struct {
  // ... member
}

func NewA() *A {
  a := &A{}
  // ...
  return a
}

型T

Golangで、任意の型を表現するのはinterface{}。reflectを頻繁に使って問題を解決しようとするプログラミングだと、intarface{}型を駆使することになる。 これを毎回毎回タイプしてたらFPの消費量激しくなって、辛くなってくるので、aliasとして型Tを宣言しておくと便利だと思う。

type T interface {}

init関数でコマンドライン引数をパースする

Golangでは、init()関数は特別扱いされる。 使い道としては、Javaでいうところのstatic initializerに相当するもの。 たとえば、ライブラリ内コレクションを貯める、みたいなインタフェースがあったとして

package some-library

var hogeCollection map[string]Hoge

func init() {
  hogeCollection = map[string]Hoge{}
}

のように書いておくと安全に初期化できる。 これを利用してコマンドライン引数の初期化などを書いておくとコードがすっきりしそう。 コマンドライン引数の扱いは、flagパッケージを駆使すると便利にパースできる。

import (
  "flag"
)

var (
  a int
  b string
)

func init() {
  flag.IntVar(&a, "a", 0, "args a")
  flag.StringVar(&b, "b", "", "args b")
  flag.Parse()
}

func main() {
  println(a)
  println(b)
}

グローバルに変数を宣言することになるけど、mainに書くよりかは読みやすいかなぁと。

ISUCON3 - 予選から本戦を終えて

TwitterでISUCONに参加したいとぼやいていたら、@f440 さんが一緒にどうかと誘ってくれたので、ISUCON3に参加してきました。

オシャレ怪盗スワロウテイルとして参加し、本戦2位という結果で幕を閉じました。 参加した動機として、自分がWebエンジニアとしてどれくらい通用するのかという腕試しでもあり、また優勝を狙っていたので、あとからじわじわ悔しさが溢れだしながらこのまとめを書いています。

僕も考えていた全体的な心構えとか、方針とかは相方の @f440 さんのエントリ ISUCON3 の参加記録 に読みやすくまとめらているので、合わせて読んでもらえると僕達がどう考えてISUCON3に取り組んだのかが深まると思います。

予選前

今回がISUCON初参加、ということもあり勝手があまりわかっていない状態だったので、@f440 さんにざっと解説してもらい過去の問題(ISUCON2KAYACさんの社内ISUCONRubyに書き直して)をチューニングする、みたいなことをしていました。

予選でやったこと

予選の問題は、メモスニペットアプリ(投稿するテキストがMarkdownで編集できる)でした。 セッションを伴うWebアプリは出題されそうだよね(KAYACさんの社内ISUCONがまさに)という話は事前にしていて、出題されたときに予想どおりでテンションが上がったのを覚えています。 また、公開されたamiからAWSに自前で用意して挑戦するというのも、AWSに慣れた身としては気持ちも楽でした。

やったこととしてそんなに多くなく、

  • クエリのチューニング
  • DBのチューニング
  • Varnishによるページキャッシュ
  • テーブルのインメモリ化

という感じでした。 クエリのチューニング、DBのチューニングは早々に潰せ、 さらに、Varnishをページキャッシュとして導入したことにより一気にスコアを上げることができたまではよかった。 のだけれども、アプリが軽くなってくると、Markdownで変換しているところが重たくなってくる、のは目に見えてわかってきていたのに、どうでもよいチューニングを繰り返して、結局1位を取れず、2日目2位、総合5位で予選通過という結果になりました。

予選が終わってからの反省会で、「なんで巨大なテキスト取ってきてsplitしてるんだよ」「普通Redcarpet使うよね」「カウントしてるところとかRedisでincrするでよいのでは」と、冷静になると効果的なチューニングができることに気づいて少し凹んだりもしました。

予選を終えて

予選の結果があまりにも不甲斐ない終わり方だったので、 メンバーとチャットで予選の反省会しつつ、本戦に向けての準備を各自する、ということを続けていました。

  • 「今までずっと、コンテンツはテキストベースだったよね」
  • Flickrみたいな画像のアップローダーとか来そう?」
  • 「INSERTばかりでなくて、UPDATEやDELETEが大量にあるのでは」
  • SNS系だよなぁ。タイムラインは複雑になると破綻するし」
  • Twitterもどきはそろそろ出題されそう」
  • 「本戦でも多くてもテーブルは3つか4つくらいだよね」

などなどお互い予想しつつ、僕はもっぱらこういったアプリのデータ構造をRDBで持つアプリにしてチューニングする、ということを繰り返していました。 たとえばRDBじゃなくてKVSにする場合はどういう持ち方にするだろうか、キャッシュとの親和性を高めるにはどうすればいいか、等を考えていました。 具体的には、MySQLからRedisへデータ構造に落としこんだり、キャッシュとアプリの親和性を高めるために、ワーカーでアプリの断片を生成しつつ、nginxのSSIで組み立てるみたいなことも特訓していました。

また、KAYACさんが予選で使用したGo製のベンチマークツールも公開してくれていたので、手を加えて予選の問題を改造して、削除や更新も追加して負荷をかけたりして、ベンチマーク作る側からすればどういう問題が出そうか、みたいなことも考えたりしました。

どうでもいいですが、Go、大変良いですね。最近Rubyより書いています。

本戦でやったこと

本戦のお題は、公開レベルを設定できる画像のみ投稿可能なTwitterでした

構成がシングルページアプリケーションと今となっては珍しくないですが、しっかりした作りでチーム内で「シンプルに作られてて良い」みたいな話もしつつ、兎に角プロファイルです。 予選のときに、いろいろ手当たり次第な感じにチューニングを進めてしまったことを反省して、本戦では、アプリを読み込む、プロファイルをとることに注力しようとう話を事前にしていました。

まずは、コードを読みつつ画面を見ながらアプリを操作しつつ静的解析してからデータ構造を把握したところで、 素の状態で、以下のプロファイルを取ります。

  • Apacheのアクセスログ
  • MySQLのクエリログ(long_query_tme=0)
  • Sinatraのアクションログ(enable :logging)

これらのログから、「画像配信」がボトルネックだということが、すぐにわかりました。 DBにはほぼ負荷がないことに若干戸惑いもしました(mysqldumpslowすると一撃でわかります)。

そこで、画像配信をどうすれば速くできるか。午前中はそればかりを話し合っていました。アクションログや、アプリのコードを読みつつ、

  • 初期配置された画像はすべて事前にすべてのサイズで変換しておく
  • アプリで変換済みのものがあれば変換しない
  • 変換したら、保存して2度変換しない
  • アイコンはすべてpublicだよね? これはすべて変換してしまって静的ファイルとして返す
  • 画像も公開範囲は途中で変更できないので、publicなものはアイコンと同様に静的ファイルとして返す
  • 投稿されたときに非同期でワーカーで画像を変換する

という一連の案を素早く閃けたのは、なかなか良い感じでした。 あとは、作業分担して淡々とチューニングをすすめるだけなのですが、

  • 画像のバックアップ先がMacBook Airだった
  • そのままMacBookAirで、画像変換スクリプトを走らせてしまった(MacBook Proあるのに!)
  • ワーカーを作成したけど、思ったほど効果が出ない
    • 最初Watchrで書いたのですが思う通り動かず、Guardにしたのですが、ファイル検知が遅すぎて使い物になりませんでした。あとから、フロントからResqueなりSidekiqに投げればよかったとか思いつき悶絶したり
  • 構成として、1台のみアプリを動かして、4台にnginxを配置してフロントにしたのだけど全台をフロントにしてそれぞれでアプリを稼働させればスケールしてた

と、予選と同じくやはり本番中ではなぜか気づけず、タイムアップ後やチャット上で冷静になった頭で考えるとあれこれアイデアが浮かんでくるというなんともやり切れなかった感が残りました。 ただ、アグレッシブな方法を取らず、基本を忠実に、Failせずに完走できたのは良かったと思います。

まとめ

とにかくISUCONでは、

  • 熱くなっても冷静になる
  • ふつうのWeb技術力
  • アプリケーションを短時間で把握する力
  • Web技術の引き出しを増やして、問題解決のために使えるものを選択できる力
    • 知ってるとかちょっと使えるだけではだめで、これをこう使うと問題をこう解決できる、というレベルにシフトしておく

が、大切だなーと実感しました。 仕事と同じで、問題解決にはまずどこが問題なのかを把握するところから始めます。 ISUCONでは、それを7時間という短い時間内に的確に見極めて、手を入れていくのはとても面白いものがありました。

調度良い難易度の問題を作成してくださったKAYACさん、また、インフラを提供してくださった、データホテルさん、イベントを開催していただいたLINEさん、本当にありがとうございました。 オシャレ怪盗スワロウテイルはここで解散となりますが、また来年リベンジしたいと思っています。

SO_REUSEPORT ソケットオプション

Linux Kernel3.9(BSDでは割と以前から実装されていたけど)で取り込まれたSO_REUSEPORTソケットオプションは、異なるプロセスで同一のポートにbindできるようになるので、いろいろおもしろいことができそう。

https://lwn.net/Articles/542629/

これがmerge commit

https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/commit/?id=c617f398edd4db2b8567a28e899a88f8f574798d

試しにRubySO_REUSEPORTを使うサーバを書いてみた。 もちろんKernelを3.9以降にアップデートしないと動かない。 Ubuntuだと、script書いてくれてる人がいるので楽にUbuntu Kernelをアップデートできる。

http://dl.dropboxusercontent.com/u/47950494/upubuntu/kernel-3.10.9

require 'socket'

SO_REUSEPORT = 15

s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
s.setsockopt(Socket::SOL_SOCKET, SO_REUSEPORT, 1)
s.bind(Addrinfo.tcp("127.0.0.1", 10080))
s.listen(5)

while true do
  conn, addr = s.accept
  puts "Connected to #{Process.pid}"
  data = conn.recv(1024)
  conn.send("HTTP/1.1 200 OK", 0)
  conn.close
end

で、これを10プロセスほど起動して、apache benchでつないでみる。

$ for i in `seq 10`
do
  ruby so_reuseport_opt.rb &
done
$ ab -n 10000 -c 10 localhost:10080/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)...Connected to 3476
Connected to 3334
Connected to 4875
Connected to 3531
Connected to 3531
Connected to 4879
Connected to 4869
Connected to 2891
Connected to 4870
Connected to 4868
Connected to 4866
...

カーネル側でバランシングしてくれていることがわかる。 使いどころとして、

  • 単純なpreforkモデルでパフォーマンスを出したいアプリケーション
  • システム管理系のアプリケーションで、プロセスを落とせないけど、新しいversionを使いたくて、同時に新しいものも起動させてから、古いversionのプロセスを落とす(hotswapしたい)

などが作れそう。

guard文のあれこれ

あるメソッドで、渡されたパラメータによっては処理をまったくしないということがある。 その場合guard文を書くことでネストが深くならなくて、読みやすくなることはイディオムとしてよく知られている。

しかし、guard文を書くと逆に読みにくくなってしまう場合があるんじゃないかと最近仕事のコードを書いていて思った。とくに、必ず特定の値を返すメソッドの場合。 「必ず特定の値を返すメソッド」はどんな時に書くのかと考えると、例えば、Interceptorパターンを実装していて、パラメータは降ってきて特定の条件下では処理はするけど、必ず後続のInterceptorに処理を継続させたくてtrueを返す場合。

public boolean before(AObject o, AMap param) {
  if (!cond(param)) {
    return true;
  }

  // brabrabra

  return true;
}

二回もreturn文が出てきて気持ち悪いのでここは、guard文を書かないで素直に書くほうが読みやすいんじゃないかと。

public boolean before(AObject o, AMap param) {
  if (cond(param)) {
    // brabrabra
  }
  return true;
}

Mac OS -> Ubuntu

教授がMac OSをメインで使っていた影響で、学生時代からメインマシンにMacを使ってきたけれども、転職を期にUbuntuへ移行しました。

  • 職場の開発マシンがUbuntuなので、普段からプログラミングスタイルや開発環境のノウハウを貯める
  • xmonadの完成度が高くてプログラミング環境として集中できる(職場にxmonad使い多くて吹いた)
  • 別にMac OSが必要な仕事をするわけでもないし、プライベートでも興味がない。結局Xcodeもcommand line toolのインストール時等、数える程しか起動しなかった

など、よくよく考えるとMacでなければならない理由はなくて、手軽にUnixライクな環境が使えればよかった。 とはいえ、純粋なUnixではなくUnixの皮をかぶったDarwinなので、たまに不都合(ビルドがすんなりできないとか)が出たりしたときはつらいなーとか思って諦めてそのまま使ったり、頑張って解決したりしてきたけど、そんな悩みもなくなった(はず)。 切り替えてから1ヶ月ほど経過してるけどいまのところ問題もなく、むしろ快適に感じている。

nodeでRedisを使ってセッション管理

フレームワークにexpressを使う場合、connectが提供するsessionモジュールを使うと楽に実装できる。 さらに、Redisにセッションデータを保存する場合、connect-redisを使うと楽だ。

https://github.com/visionmedia/connect-redis

他にも、express-session-redisというモジュールがあるけれども、コードを読んでセンスがあると思ったのは、connect-redisの方。 connectがセッションストア用インタフェースを作ってくれているので、それを継承して作ったほうがスマート。

Twitterでログインするやつをconnect-redisを使って書いてみた。

https://gist.github.com/yoppi/5604149