isucon6本戦に出場して7位でした
@Konboi、 @tkuchikiとisucon6本戦に出場して7位でした。
振り返りとして、やったことと反省点をまとめておきます。
お題
Docker上にReact(兼Nodeサーバ) + APIサーバー(各言語実装)で構築されたリアルタイムお絵かき掲示板
やったこと
事前準備
『 みんなのGo言語』や『 プログラミング言語Go』を読んだり
gorilla/sessions のコードを読んだり
予選でGoのスキル不足を痛感したので、過去のisucon実装を参考に掲示板を作ってみたり
普段のギョームではruby(rails)を書いているので、帰宅後にコツコツGoを書く日々を過ごしていました。
掲示板を一通り作ってみた感想としては、GoでWebアプリケーションを作るのは大変だなぁというのが正直なところです。
自分が普段いかにレールの上を乗っていたかを思い知らされました。
当日
前半
早めに家を出て会場に到着
コンビニで朝食とお菓子を調達(今回はレッドブルには頼らなかった)
開始直後サーバが立ち上がらないというトラブルがあったものの、運営の方のスピード対応で復旧
インフラ周りはtkuchiki、アプリ周りはKonboiと自分といういつものパターンで進めること
サーバが立ち上がるまではレギュレーションを読む
サーバが立ち上がったら、ブラウザでアプリケーションがどんなものかを試しつつ、sshでサーバに入ってコードを読んでみる
コードを読んでる間にtkuchikiがコードをリポジトリ管理するようにしたり土台を整えてくれた
Konboiと二人で「Reactなるほど分からん」となりつつ、自分がNodeのコードを、KonboiがGoのコードを重点的に見てみることに
この間にtkuchikiはDockerを剥がすという対応を進めてくれていた
/img/:id が中でAPIを呼んでSVG生成をしていたので、Go側で直接SVGを返すようにしようという話になり、自分はここをやることに
Konboiは閲覧者数の部分をredisに置き換える対応をやってくれていた
中盤
ローカル環境は素早くつくることができた
node側で生成していたSVGのソースを読みながら、DBの内容と照らし合わせつつ以下のように愚直にxmlを書いていく
func getImg(ctx context.Context, w http.ResponseWriter, r *http.Request) {
idStr := pat.Param(ctx, "id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
outputError(w, err)
return
}
m := make([]string, 0)
m = append(m, `<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">`)
room, err := getRoom(id)
if err != nil {
outputError(w, err)
return
}
width := strconv.Itoa(room.CanvasWidth)
height := strconv.Itoa(room.CanvasHeight)
m = append(m, `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="`+width+`" height="`+height+`" style="width:`+width+`px;height:`+height+`px;background-color:white;" viewBox="0 0 `+width+` `+height+`">`)
strokes, err := getStrokes(room.ID, 0)
if err != nil {
outputError(w, err)
return
}
for _, s := range strokes {
strokeId := strconv.FormatInt(s.ID, 10)
strokeWidth := strconv.Itoa(s.Width)
red := strconv.Itoa(s.Red)
green := strconv.Itoa(s.Green)
blue := strconv.Itoa(s.Blue)
alpha := strconv.FormatFloat(s.Alpha, 'f', 1, 64)
points, err := getStrokePoints(s.ID)
if err != nil {
outputError(w, err)
return
}
var p = []string{}
for _, point := range points {
p = append(p, strconv.FormatFloat(point.X, 'f', 0, 64)+","+strconv.FormatFloat(point.Y, 'f', 4, 64))
}
m = append(m, `<polyline id="`+strokeId+`" stroke="rgba(`+red+`,`+green+`,`+blue+`,`+alpha+`)" stroke-width="`+strokeWidth+`" stroke-linecap="round" stroke-linejoin="round" fill="none" points="`+strings.Join(p, " ")+`"></polyline>`)
}
m = append(m, `</svg>`)
var res = make([]byte, 0)
for _, v := range m {
res = append(res, v...)
}
w.Header().Set("Content-Type", "image/svg+xml; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(res)
}
14時過ぎに一通りできて、tkuchikiにnginxの/imgの向き先をGoに変えてもらってベンチ実行
スコア0でFAILかつエラーメッセージもなしで焦る
ちょうどfujiwaraさんがidobataで同じ質問をされていて、SVGのパースが失敗しているということが判明
ここからひたすら原因の調査に時間が持って行かれる
適当なidで差分がないか比較してみようと思い、もともとnodeが出力している/img/900とGoで置き換えた/img/900のdiffを取ってみたものの差分なし
ブラウザのヘッダーを確認していたらContent-Lengthを付けていなかったことに気づく
「これだ!」と思ってContent-Lengthを付与して再デプロイ
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(res)))
が、またもやスコア0でFAILかつエラーメッセージなし
この間Konboiはstroke pointをredisにキャッシュする対応を進めてくれていて、tkuchikiは複数台構成の調整をしてくれていた
終盤
tkuchikiにも見てもらったところ、nodeとGoでContent-Lengthが異なるデータがあることが判明
該当のデータをdiffで見ても1行なのでどこが違うか分からないため、目grepでひたすら確認(これがキツかった)
すると、point.Xの部分で小数点以下の数値が返ってきていたことに気づく
point.Xは初期データが整数値だったので小数点以下を切り捨てるというやらかしをしてしまっていた。。。
- strconv.FormatFloat(point.X, 'f', 0, 64)
+ strconv.FormatFloat(point.X, 'f', 4, 64)
小数点以下も返すようにして再デプロイ
ついにベンチが通った
とはいえ点数はそんなに伸びず
原因の特定に時間がかかり過ぎてしまい気づいたら17時くらいになっていた
Konboiのキャッシュ対応を手伝いたかったがredis力不足でパッと分からなかったので、キャッシュ後もブラウザ上で正しく動作しているかの確認を手伝う
キャッシュ対応の方は線を書いた後に「ビュンッ」っと謎の線が出現してしまう現象に遭遇し時間的に断念
再起動試験はtkuchikiが早めに終わらせてくれていたので残り時間で最後のチューニング
アプリケーションとDBが同じサーバに乗っていたので、DB専用サーバとして分離してもらい最後のベンチ
最終スコアは17292点で7位という結果に
感想と反省点
今回/img/:idの対応でドはまりしてしまい、結局自分はこの対応しかできなかったのが反省点です。
また、途中Konboiが「ループクエリ発見」と言っていた時にループクエリの改善を手伝うことができたはずなのに自分の作業を止められず、ひたすら目の前のエラー修正のことしか頭が回ってなかったのも良くなかったと試合後に反省。
懇親会では問題出題者のedvakfさんや、高校生参加者のhideo54さんと色々お話できて楽しかったです。
今時の高校生はnginxとpythonでアプリケーション作ったりしててただただすごい。
高校生のお手本になれるように精進せねば。
isuconをきっかけにgoを学び始めましたが、せっかく学んだので引き続きgoで色々作っていきたいなぁと思いました。
次は LINE BOT AWARDSに向けて何か作ってみます!
isuconに出ると毎回まだまだ技術力が足りないなぁと痛感しますが3年前よりは少し強くなれた気がします。
来年もまた挑戦したいと思えたし、同じチームで次こそ勝ちたいという想いが強くなりました。
運営の皆様、予選から本戦まで準備/運営をありがとうございました!
非常に楽しい時間を過ごすことができました。
isuconカード大切にします。
Konboi、tkuchiki、二人と一緒のチームでずっと続けてこれて本当によかったなぁと思います。
いつも学ばせてもらってばかりなので自分も二人に負けないようにがんばらないと。
いつもありがとう。これからもよろしくです。