私が歌川です

@utgwkk が書いている

お酒を飲むと皮膚がただれる

お酒を飲むと皮膚がただれてしまい、2日かけて治している。ということは毎日飲酒してると一生皮膚がただれたままになると思う。なぜ皮膚がただれるのか、皮膚が弱いことは知ってるけどお酒とどう関係があるのか、そもそもお酒が原因なのかはまだ特定できていない。

もう3日ぐらいずっとこの調子なのでちゃんと寝られてない。助けてくれ!!!!!!!

体調 - 私が歌川です

皮膚の調子が悪かったころの記事を読むと、睡眠が取れていないことがよく分かる。あまりに悲惨でかわいそう。今はここまでひどい状態ではない。

1年ぐらい皮膚科に行ってなくて、保湿クリームを塗って飲酒せずに寝たら基本的になんとかなっている。

友だちの内定を定期ツイートで祝う風習

風習があって、毎週2回ツイートしていたのだけれど、自分の内定祝いだと勘違いされてしまった(そう思うのが自然だと思う)ので、これ以上誤解を生まないために止めます。

1年半ぐらいずっと止めずに続けていたらしくておもしろい。内定は欲しい。

twilog.org

いつも「時間がない」あなたに 欠乏の行動経済学

この本は id:Pasta-K さんに誕生日プレゼントとしていただきました。


お金や時間が足りない、ダイエットのようにやりたいことを我慢していてできない状態、をリソースが足りない状態、欠乏であるとしている。欠乏状態では、喫緊の課題を解決するための集中力は上がる(集中ボーナスと呼ぶ)が、少し先の重要だが緊急でない課題は後回しにされる(トンネリングと呼ぶ)。借金の返済が終わらない、給付金の申し込みができない、常に予定が埋まっている、というのをこの本では欠乏の罠と呼んでいる。

荷造りをしているときに、余裕のあるかばんに対して何を入れて何を後回しにするか考えることはない。欠乏の罠に陥らないためには、このような余裕(スラックと呼ぶ)が常にあることが重要であるとしている。突然の出費に対して貯金が足りないと借金をするしかなくなり、借金返済のことしか考えられなくなってしまう。常に欠乏状態にあるわけでもない人が欠乏状態に陥ってしまうのは、豊かな時期にスラックを作らないためである、というのも印象的だった。

欠乏状態にあるとき、目先の課題を解決するための集中力はすさまじいが、将来のことはまったく考えられなくなる、というのは身に覚えがあって、さまざまな実験や事例によってこれが普遍的な現象であることが示されていた。怠慢のためにやらないとか、方法を知らないとかではなく、処理能力に多大な負荷がかかっているので考えが及ばない、といったほうが近い。スケジュールでジェンガをやるようになったらもう追い込まれている、ということ。

この本のあとがきや解説のセクションも集中ボーナスによって書かれている、というのが伏線回収しててよかった。学部時代はよく生きていたなと思うけど、いま考えるとだいぶトンネルの中にいたと思う。

scrapbox.io

バーチャルYouTuberの配信を見ていない

最近はぜんぜんバーチャルYouTuberの動画や配信を見ていない。生配信を見つづける時間や根気がないというのが大きくて、アーカイブを見るとしても何時間もある配信を見て、みどころを探して楽しむ、というのはなかなか大変。野生の切り抜き動画を見ても本人の動画の再生回数が増えるわけではないけれども、その方が分かりやすくて楽しみやすい。

バーチャルYouTuberが嫌いになったというよりは、飽きたとか、ついて行けなくなったというほうが正しそう。もともと長い生配信を見まくってたというわけでもない気がするし、みんなが配信をしまくるようになった頃からちょっとずつついて行けなくなっていたと思う。バーチャル存在が歌う動画ばかり見ている。歌ってみたは何時間もないので気楽に見れる。

サークルの自分の分報チャンネルにVTuber情報が集まってくるようにPurposeを設定していたのだけれど、ついにやめた。最近はもう見ていない情報のほうが多く貼られるようになったし、別のチャンネルがあるのでそっちでやったほうがチャンネル名と目的が合っていると思う。

blog.utgw.net

むかしはバーチャルYouTuber全員が魔王魂の楽曲に合わせて謎の応援ソングを歌っていた、という記事を書いてたのを思い出したので記念に貼っておきます。大昔のことのように感じられるけど2年前の話なのでめっちゃ昔というわけでもない。

【追記あり】echoでNew Relicのエージェントを使えるようにするミドルウェアを書いた

追記

nrechoというライブラリが公式から提供されているのでこっちを使ったほうがよいと思います。わざわざ自前実装をしてから見つけてしまったけど見つけられたのはよかったということでここはひとつ……。

そういうわけなので、以下の文章は読まなくてもよいことになりましたが、記録のために残しておきます。

(追記ここまで)


あらすじ

練習でISUCON9予選のコードにNew Relicエージェントを導入したときは、gojiを使っていて、 mux.HandleFunc でhandlerを登録する形式だったのでちょっと書き換えればすぐ導入できたのですが、echoだとひと工夫必要でした。

そもそも公式のGoエージェントではどうやっているのか、gojiの場合は

newrelic.WrapHandleFuncといった関数を使うことでGoエージェントを有効にできます(使用例のコードを引用)。

   http.HandleFunc(newrelic.WrapHandleFunc(app, "/users", func(w http.ResponseWriter, req *http.Request) {
        txn := newrelic.FromContext(req.Context())
        txn.AddAttribute("customerLevel", "gold")
        io.WriteString(w, "users page")
    }))

gojiの場合はこんな感じで導入できます。goji.io/patを使っていたので、軽いwrapperを噛ませて使っていました。

func wrapHandleFuncGet(path string, f func(w http.ResponseWriter, r *http.Request)) (*pat.Pattern, func(w http.ResponseWriter, r *http.Request)) {
    pathStr, wrapped := newrelic.WrapHandleFunc(newRelicApp, path, f)
    return pat.Get(pathStr), wrapped
}

func wrapHandleFuncPost(path string, f func(w http.ResponseWriter, r *http.Request)) (*pat.Pattern, func(w http.ResponseWriter, r *http.Request)) {
    pathStr, wrapped := newrelic.WrapHandleFunc(newRelicApp, path, f)
    return pat.Post(pathStr), wrapped
}

func main() {
    // (snip)
    mux.HandleFunc(wrapHandleFuncGet("/users/:user_id.json", getUserItems))
    mux.HandleFunc(wrapHandleFuncPost("/buy", postBuy))
    // (snip)
}

echoミドルウェア選定

GitHubecho newrelic というクエリでGo言語のリポジトリに絞って検索したところ、2020/09/13 時点で以下の5つのリポジトリがヒットしました。

エンドポイントごとにレスポンスを返すのにかかった時間を計測するのはどれでもできそうですが、Transactionオブジェクトをcontextで引き回さないと、DBアクセスにかかった時間までは測定できません。github.com/jessie-codes/echo-relic 以外は そのような実装になっています。

github.com/dafiti/echo-middleware は、ライブラリ側でNewRelicエージェントを初期化しており、また初期化に失敗したらpanicするので、エージェントを無効にしてアプリケーションを動かしたいというユースケースには合わなさそうです。

github.com/notyim/echo-middleware-newrelic は、importしたさいに環境変数の情報をもとにNew Relicエージェントを初期化しています。またエージェント初期化処理のエラーハンドリングがライブラリに委ねられています。ログを吐く処理とか環境変数をどうするか、みたいなのは自分でコントロールしたいので使わなさそうです。 runable 変数はどこで使っているのだろう……。

github.com/phacops/echorelic はNew Relicエージェントを渡してミドルウェアを作るかたちになっていてよさそうですが、c.Request().URL.Path をもとにトランザクション名を決めているのはよくなさそうでした。GET /api/chair/:id へのリクエストが :id ごとに散らばることになってしまいます。

github.com/golang-common-packages/monitoring (名前の範囲が広い!!) は、これもライブラリ側でNewRelicエージェントを初期化してpanicしているので、github.com/dafiti/echo-middleware と同様に採用できなさそうです。

いろいろ見てきましたが、どのライブラリも自分のユースケースには合わなさそうです。ISUCON10予選本番では、echoとNew Relicを組み合わせる例をその場で探して出てきたものを試した結果、 github.com/jessie-codes/echo-relic を採用してしまったのでなかなか大変でした。

エンドポイントにかかった時間は計測できたけど、分散トレーシングとか、SQLにかかった時間が取れてない!!! なんでなの!! と言いながらechorelicのコードを読みに行きました。どうもcontextが渡ってない予感がする? echoのミドルウェアを書くのか? と思いつつ echo.WrapMiddleware っていうのを使うとなんか書けそう、ということでガッと試しました。

ISUCON10 予選突破した #isucon - 私が歌川です

作った

  • e.Use(なんとか(app)) ってやるだけでエージェントが有効になってほしい
    • 全てのエンドポイントを計測したいので、エンドポイントごとにwrapするよりミドルウェアにしたほうが早い
  • エージェントの初期化処理はライブラリを使う側で行いたい
    • エージェントの設定を自由に行いたい
    • エラーハンドリングはこっちでやりたい (panicしないでほしい)
  • トランザクションの名前は c.Path() をもとにしたい
    • GET /api/chair/1 ではなく GET /api/chair/:id にしたい
  • できるだけNew Relicの公式ライブラリが提供する仕組みに乗っかりたい

以上の要件を満たすようなechoミドルウェアはなさそう、そしてちょっと実装すれば欲しいものはできそう、ISUCON10本戦でもNew Relicを使うことになるだろう、ということで作りました。go get github.com/utgwkk/echo-newrelic/v3 して今すぐご利用いただけます。ISUCON10予選の感想戦がてらちょっと試してみて、New Relicにデータが送信されていることは確かめました。

github.com

ISUCON10 予選突破した #isucon

😇😇😇 (:innocent::innocent::innocent:) というチームで、 id:nonylene id:wass80 と参加して、24位でめでたく予選突破できました。学生枠を使わずに予選突破できたのは初めてです。チームメンバーのブログも読んでください。

nonylene.hatenablog.jp

wass80.hateblo.jp

記録に残ってる最終スコアは2204点でした。

やったこと

New Relic導入

まずNew Relicを入れて計測できるようにしよう、というのをやりました。echo向けにはechorelicっていうのがいいのか、と調べて導入しました。

github.com

エンドポイントにかかった時間は計測できたけど、分散トレーシングとか、SQLにかかった時間が取れてない!!! なんでなの!! と言いながらechorelicのコードを読みに行きました。どうもcontextが渡ってない予感がする? echoのミドルウェアを書くのか? と思いつつ echo.WrapMiddleware っていうのを使うとなんか書けそう、ということでガッと試しました。

github.com

最終的にこれでSQLの計測もできるようになりました。これを書いてたときがいちばん輝いてた気がする。

後述するように、 /api/.+/search が重たいけどどういうクエリで重くなるのだろうか、と思ってNew Relicのtrace IDとリクエストの対応をログから取れるようにしたりもしていました。結局分析には使わなかったですが……。

github.com

インデックス貼る

明らかに足りないインデックスがあるよね、ということで貼りまくりました。

椅子におすすめの物件ロジックちょっとよくする

椅子を直方体に見たててドアを通せるか書いてあるけど、短い2辺だけ見ればよいよね、ということになって実装しました。Goでのsortのやり方を調べるより先に、3つのint64のうち小さい2つを返す関数を自前で実装してて野蛮な感じがあります。sort.Ints()を使うと楽に書けるよね、というのを聞いて、たしかに、と思いました。

github.com

うまくインデックスを効かせられないので、せめてスキャンする行数は減らしたい、ということで、EXPLAINを見ながらFORCE INDEXするインデックスを決める、というのもやってました。

github.com

自前アクセスログ解析

/api/.+/search が重いのでインデックスを貼りたいけどなんかめっちゃクエリあるね、どうしようかな、と id:wass80 と会話してて、searchはGETリクエストなのでクエリパラメータの種類でどんな検索が多いか傾向がつかめそう、という話をしました。分析スクリプトを即興で用意して回した結果、大半のリクエストはクエリ1個なのでひとまずそこにインデックスを貼るとよさそう?? とか、 features はLIKE検索やばそうだけどそんなにリクエストされてないから後でいいか、という判断ができたのはよかったです。

use strict;
use warnings;
use feature 'say';
use URI;

while (my $line = <STDIN>) {
  my ($req) = $line =~ qr{"GET (.+) HTTP/1\.1"};
  my $uri = URI->new("http://example.com/$req");
  my %query = $uri->query_form;
  my @keys = grep { $_ ne 'page' && $_ ne 'perPage' } sort keys %query;
  say join ',', @keys;
}

perl nukidasu.pl < search.access.log | sort | uniq -c | sort -n のようにして使うとよい情報が得られました。

(snip)
     11 color,priceRangeId
     11 heightRangeId,priceRangeId
     17 features
    441 widthRangeId
    449 color
    491 kind
    521 depthRangeId
   1027 heightRangeId
   1461 priceRangeId

あとになって、alpとかkataribeとかを使うとこういう解析はすぐできるのかなと思いつつも、小さなスクリプトで大きな効果が得られたな、と思います。

やったけど導入しなかったこと

/api/.+/search のSELECTを1つにする

SELECT COUNT(*) したあとに同じ条件でSELECTしてるのを1つにまとめられないか、ということで実装してみましたが、アプリケーションの互換性チェックに落ちたのでマージしませんでした。

github.com

MaxOpenConnsを大きくする

なんか小さいので大きくしたい、という話をしたのですが、大きくしたらFAILするようになったので結局もとに戻しました。

所感

Ruby → Python → Goと利用言語が変化していきました。ついにGoで参加したのですが、コンパイルが通るからひとまずマージできるでしょう、というふうに進められたのはスムーズでよかったです。VSCodeのGo拡張がだいぶ体験がリッチというのもありました。

New Relicを初手で導入して、このエンドポイントが明らかに重いし、分散トレーシングを見るとN+1なので直しましょう、というのがコマンドなしで分析できたのは便利でした。

今回はDBの負荷がどうしても高くなるようなアプリケーションだった、と思っていて、ひととおり自分ができることをこなしたあとは、id:wass80id:nonylene の2人にMySQL複数台構成の実装をやってもらっていて、後半はずっと手があいてました。たとえばもうちょっと実装をちゃんと見て、 priceRangeId とかをなんとかする、とかやったほうがよかったかもしれません。

去年の反省を活かして、VSCode Remoteは重いので手元で開発しましょう、という方向にシフトしたのは、結果的に各自が並列で作業できるようになって効率が上がったのでよかったと思います。

今回のダッシュボードは、ときどき高負荷で見られなくなることがあったとはいえ、体験がかなりよかったと思います。ベンチマークを走らせてると、どんどん点数が上がってる様子が見れるのはおもしろかったです。とてもおもしろい問題だったと思います。SUUMOはときどき引っ越しする予定が無でもブラウジングしていて、なぞって検索機能なんかあった気がする!! と言っていました。

今年こそ優勝したいですね。

追記

旅行っていうタグを間違えてつけたのでアイキャッチ画像がおもしろい感じになりました。