blog.syfm

徒然なるままに考えていることなどを書いていくブログ

ISUCON13 に参加した

毎年恒例の ISUCON に参加してきた。メンバーは毎年恒例 @acomagu & @natumn 今回はノベルティが欲しかったのもあり個人スポンサーになってみた。

結果としては最高スコアで 56180 点を出すことができたが、再起動試験に失敗し fail でした…。

事前にやったこと

今年はあらゆるものを自動化しようとしていて、具体的には以下のことをしてみた。

  • Ansible: もともとだいぶ前から Ansible で初期セットアップを自動化していたけど、今回はさらにいろんな設定を追加してみた
    • ツール群のインストール
    • Netdata のセットアップ
      • まったく使わなかったが…
    • Cloud Profiler/Cloud Trace で使うクレデンシャルまわりの設定
    • Slack 通知用の設定
    • MySQL のユーザ作成・権限追加
    • nginx のログフォーマットを LTSV に変更
  • コード自動修正ツール: ソースコードに対してこのツールを実行すると以下の設定や実装を自動的に追加・修正してくれる
    • 特定の関数やメソッドを context に対応しているものへ置き換える
    • request context を各関数・メソッドへ渡すようにする
    • サーバ起動時に Cloud Profiler/Cloud Trace 関連の設定、エージェントをスタートさせる
    • ベンチ実行後に自動的に slow query や alp の解析結果を Slack に通知させる

どれもやってよかったけど、特に Cloud Profiler/Cloud Trace や Slack 通知周りが便利で競技中はほとんど手動で計測を行うことはなかった。ただ、後述するように準備の時間が十分にとれていなくて競技時間中にツールを修正したりしていたのが痛かった…。

当日やったこと

当日は @ktr の自宅に集まり、猫に見守られながら参加していた。

競技開始直後

  • 各メンバーが ssh してサーバに入れるか確認
  • サーバで起動しているアプリケーションの設定確認
  • Ansible 実行
  • コード自動修正ツールの実行、メトリクスやトレースが取れているかの確認
  • ソースコードのバックアップ
  • 当日マニュアルおよびアプリケーションマニュアルを読む

このあたりを分担してやっていった。自分は Ansible 担当でほとんど自動化されていたのでその間にマニュアルを読んでいた。ただ、その後に実施したメトリクスやトレースの設定が一部うまく動いておらず修正にわりと時間を費やしてしまった。

昼ごろまで

  • アプリケーションのアクセスログを切る
  • アプリケーションのログレベルを上げる
  • MySQLinterpolateParams=true を設定
  • MySQL を2つめのインスタンスへ分離する
  • users テーブルに icon_hash を保持する
  • ボトルネックとなっているクエリに対してインデックスを貼る

今までは後半になってから複数台構成へ移っていたが、自分たちはインフラに対して比較的不慣れだったのでほぼほぼ初手で複数台構成へ変えることにした。とりあえずアプリと DB は間違いなく分けるだろう、ということで 2 台構成としており、今回はほとんどハマらず移行することができた。

あとは毎回やっている設定を行ったり、明らかなボトルネックを潰したりといった感じで午前中は終了。だいたい 6600 点くらいだった。

午後

  • 一通りコードに目を通して改善できそうな箇所にコメントをつける
  • Redis で user、livestream、ngword をキャッシュする
  • トランザクションが不要な箇所の修正
  • NG ワードチェックをクエリではなくアプリケーションだけで行う
  • タグを DB ではなくアプリ内のメモリに持つ
  • アイコンを nginx から配信する
  • PowerDNS をやめてオレオレ DNS に切り替える

とにかく N+1 が多かったのでそのあたりを順番にキャッシュしていった。また、すべての DB アクセスでトランザクションが貼られていたのでコードリーディングしつつ本当に必要なところだけに絞っていった。

アイコンについては If-None-Match の仕様があることが分かっていたのでそちらの仕様を利用するか、nginx から静的に配信するかで迷ったけど最終的に nginx からの配信とした。ただ、nginx の設定にドハマリし、1 時間くらい溶けたのでこの方針が良かったのかはわからない…。

また、このあたりの修正が終わったあたりで水責め攻撃による PowerDNS の負荷がボトルネックになってきていたので改善に着手し始めた。DNS の運用経験も PowerDNS の知識もまったくなかったので、チームで話した結果、Go で簡易的な DNS サーバを書くのが一番効率的かつ不確実性が低そうという結論になった。この方針はかなりうまくいき、30~40 分くらいで完全に置き換えることができた。

ここまでで 24000 点くらい。

ラスト 45 分

  • MySQL の binlog を止める
  • nginx のログを止める
  • アプリケーションのログを止める
  • メトリクスやトレースを取るのをやめる
  • 残っていたスロークエリ用にインデックスを貼る

基本的に最終ベンチに邪魔なものはすべて止めていった。早めに終わったのでアプリケーションコードに変更を加えないで済むスロークエリの改善を行っていた。支配的なボトルネックではないと思いこんでいたので、多少スコアが伸びればいいなーくらいの気持ちだったが、なんとこれがめちゃめちゃ効いてスコアが倍くらいになったw

一番最後のベンチで最高点を出して競技終了。

終わりに

最終的に上位 30 位に入れるスコアだったのに fail してしまったのはとても残念 😇 時間がギリギリになっていたのと、おそらく大丈夫だろうという憶測で再起動チェックをしなかったのは最大の反省点かなぁと思う。

他にもツールがうまく動かなかったことや nginx の設定でハマったことで無駄に時間をかけてしまっていたところや、結局 3 台目のインスタンスを使わなかったところも反省点。このあたりは次回までにちゃんと対策したり・戦略を考えておきたい。

とはいえ今回の ISUCON もめちゃめちゃ楽しかった!競技が終わってからもしばらく ISUCON のことを考えていたくらいなので来年こそは良い成績を残したいなー。毎年一緒に出てくれる友人たちにも感謝 🙏

運営に関わった皆様ありがとうございました。

追記

インスタンスを構築していた友人によると fail の直接的な原因は自作 DNS サーバを systemd に登録していたものの、スタートアップ時に自動起動する設定になっていなかったとのこと。こういう凡ミスを避けるためにも再起動試験は必須ですね 😇

一年を振り返り… (2022)

執筆をサボっていたのでこの振り返りは大晦日の電車の中でスマホで書いています 😇

仕事

今年もメルペイのコード決済チームにいた。今年からチームの TechLead となり、それと同時に一番長くチームに在籍しているメンバーとなった。

仕事内容に関しては大きな案件はあまりなく、どちらかといえばマイクロサービス統廃合や、お客さま体験の改善を中心に進めていた。加えて、TL としてチーム全体のエンジニアリングスキルの底上げを図ったり、インターン生のサポートといったソフトスキルが求められるシーンも多かったと思う。 また、期間限定で他の案件にも多少関わっており、特にメルカードの案件では普段と異なることができたので楽しかった。 メルカードでは負荷試験の担当もしており、ローンチ日が不安だったが無事に捌き切れたので一安心だった。

技術

仕事の技術スタックは去年と特に変わらず。 チームで開発しているマイクロサービスの分散トランザクションを管理するための仕組みを移行させており、そのあたりの知識がより深まったように思う。 また、to B のシステムだけでなく to C のシステム設計にも多少関わったのでそのあたりの設計の違いを久々に感じることができた。

プライベートでは趣味のプログラミングでフロントエンドをつくる機会があり、TypeScript や React、Next.js、GraphQL あたりをよく触っていた。特に React 18 の Concurrent Rendering や React Server Components の仕組みが面白かった。また、Next.js の app ディレクトリによるコロケーションや、GraphQL の fragment コロケーションのような思想は Go における API ドキュメントやテストファイルの仕組みと類似しており、そういった共通点が見られるのも興味深かった。

ときどき設計方針で悩む時があるが、他のプロジェクトや OSS、公式のベストプラクティスを見ながら試行錯誤している。

OSS

引き続き Evans と go-fuzzyfinder のメンテナンスを中心に行っている。とはいえ、最近は Evans の開発が停滞しがちになってきており、より活発に開発したい人がいたらメンテナへ迎えても良いかもなぁと感じている。そのあたりはゆっくり考えたい。

go-fuzzyfinder では go-ansisgr というライブラリをつくったことにより、ANSI Escape Sequence の Select Rendition Graphic をサポートした。去年からやりたいと思ってできていなかった機能なのでようやくという感じ。

GitHub Sponsors では 2022/12 現在で 8 名の方から、計 1000 円/月程度の支援をいただいた。本当にありがとうございます。

読んだ本

技術書

ビジネス書

  • 教養としての決済

漫画

  • かぐや様は告らせたい 25~28
  • ブルーピリオド 12~13
  • 正直不動産 14~15
  • 九条の大罪 5~6
  • チェンソーマン 4~12
  • さよなら絵梨
  • フツーに聞いてくれ
  • タコピーの原罪
  • HUNTER×HUNTER 1~37
  • 百木田家の古書暮らし 1~2
  • 好きな子がメガネを忘れた

ずっと読んでいたかぐや様が終わってしまい悲しい気持ちがあるが、今年読み始めた HUNTER×HUNTER は当分終わらなそうなので安心できる (?)

旅行

今年もいくつか旅行に行っていた。

湯本

自分の地元だが、パートナーが初めてこちらへ来たので近くの温泉旅館に泊まった。翌日には適当に小名浜に海を見に行ったりしていた。 湯本の旅館に泊まるのはなんだかんだ初めてで新鮮な気持ちだったが、温泉自体はハワイアンズやさはこの湯で何度も入っていたので懐かしさがあった。

伊東

親戚との旅行で行った。 久々にあった人も多く、(話は聞いていたが) いつの間にかはとこが誕生していてびっくりした。 伊東は鎌倉殿の 13 人でも舞台となるので聖地巡礼をしたかったけど、団体行動は難しく、断念せざるを得なかった 😇

軽井沢

ひさびさの ISUCON 兼旅行で行ったところ。 東京とくらべて気温が 7~8 度くらい低く、めちゃめちゃ涼しく快適だったので避暑地と言われる所以を体感できた。 人もそこまで多くなく、美味しいごはんも食べられるのであと数日くらい滞在していたかった。 また、ちょっと電車で移動するとあの夏で待ってるの聖地があり、このアニメにハマっていた自分からするとその点でも最高だった。

オフ会

旅行ではないけど、中学生くらいから付き合いのある友人らと 3 年ぶりくらいにオフ会をした。 久々だったにも関わらず、みんな全然変わっておらず安心した。 また、初めて会うメンバーもいたのでドキドキだったがすぐ思い出話で盛り上がった。 ゆるく続いているこの関係が今後も続いていけば良いなと思う。

観たアニメ

この中だとかぐや様が一番良かった。3期目なので安心感があったし、自分が一番見たかった回 (ラップ回) が見れたので良かった。この調子で YouTuber 白銀父もやってほしい。

やったゲーム

ひたすら龍が如くをやっていた。やはり 0 がストーリー的にもシステム的にも一番完成度が高くて面白かった。 また、アルセウスはダイパやプラチナ世代だったのでそのあたりをやっていると分かる演出が多々あってとても面白かった。 バイオレットも特に中盤から終盤にかけて結構アツくて良かった。

植物

家の日当たりが格段に良くなったので、基本的に屋外で育てている。枯らさない限り植物の数が減ることはないので今年は買う数を減らしていたものの、興味が出ていたアガベ ブラック&ブルーをふるさと納税で手に入れた。とてもかっこいい。 パキポディウムとかも面白いけど結構お高いのと管理が難しそうでまだしばらくは買わないかも。

今年から猫を 2 匹飼い始めた。どちらも同じ施設の保護猫で、引き取ったときは生後 3 ヶ月くらいの子猫だった。 家の中を走り回っていて大変なときもあるものの、とても可愛いのでなるべく長く幸せに生きてほしい。 今はもう 8 ヶ月くらいになり、すっかり大きくなった。特にキジトラのほうの猫 (まめ) の体重増加が著しい。

Catlog Board を一台導入しているが、それだけだとトイレをカバーしきれていないので追加導入したり、Pendant もいずれ使いたい。

なにか猫に関連するサービスなり仕組みを作れたらいいなーと思いつつ特に思いついていない…。

人生

今年の頭にノリでマンションのモデルルームをいくつか見に行ったら気に入ってしまい、気づいたら購入していた。ローンを組むのも当然初めてで、人生の中でもっとも高い買い物になった。 住んで早半年以上経つが、困るようなことは全くなく、むしろ賃貸のときよりすべての設備が大幅にグレードアップしているので快適に過ごすことができている。前の家が 2LDK+Nで、今の家は 3LDK+WIC+N なので部屋も広くなった。 月々の支払いに関しても、前の賃貸より多少上がった程度で、最悪自分一人でも支払えるくらいの額となっている。

また、10 月には結婚をした。もともとお互い事実婚で良いというスタンスだったが、家が資産となったことでその相続に関する問題が出てきたり、病院での面会をはじめとする諸問題など、事実婚はあらゆるところで考慮しなければいけないことがあり、最終的には両家の両親とも相談し、最大公約数を取るような選択として入籍した。選択的夫婦別姓があれば即その決断をしていたので今後の人々のためにも早く導入してほしい。 とはいえ、結婚により家族、親族、友人をはじめとするたくさんの方々からお祝いをいただいたのでそれは素直に嬉しかった。

終わりに

この振り返りを書く前は今年もなんもしなかったな…と思っていたけど実際はめちゃめちゃいろいろなことをしていた…。なんとなく始めたこの振り返りによってちゃんと一年を思い出せているのでこれからも引き続き書いていければなと思う。

今年は大きな事件・事故などなく本当に穏やかな一年であったように思う。来年も同じように穏やかに過ごせますように。

go-fuzzyfinder で色をつけられるようになった

個人的にほそぼそと開発をしていた ktr0731/go-fuzzyfinder を使うと fzf のような UI を持つ CLI ツールを非常に簡単につくることができます。go-fuzzyfinder では、Preview Window という、現在選択しているアイテムの詳細情報を知らせるための表示領域を持っています。これは非常に便利な機能ですが、一方で未実装の機能が一つありました。

それが Preview Window 内の文字列にスタイルをつけて表示する機能です。fzf の --preview フラグでは同様に Preview Window を表示できますが、こちらでは色をつけた表示も可能であるため、go-fuzzyfinder もこの機能を導入したいと考えていました。

そして最近になってようやくこの機能を入れることができました。この記事では、この機能をどのように使うことができるのか、そしてどのような設計・実装になっているのかを紹介します。

使い方

API に変更はありません。go-fuzzyfinder を v0.7.0 にアップデートするだけでこの機能を使えるようになります。 PreviewWindow に表示される文字列に ANSI エスケープシーケンス が含まれている場合、go-fuzzyfinder はそれを解釈し、パラメータに応じてスタイルが適用されます。

たとえば、ターミナルで以下のようなコマンドを実行してみます。Hello, の部分はボールドされており、World! の部分は赤色になっています。

$ echo '\x1b[1mHello, \x1b[0;31mWorld!'

go-fuzzyfinder の PreviewWindow でも同じ文字列を表示してみます。

ターミナルで表示したときとまったく同じような表示となりました。

同様に、ファイルを bat で開き、その出力を go-fuzzyfinder の PreviewWindow で表示してみた例です。bat は cat のような振る舞いをしますが、シンタックスハイライト付きのリッチな表示をします。PreviewWindow でも同様にカラフルな表示となりました。

このように、go-fuzzyfinder がエスケープシーケンスを解釈できるようになり、PreviewWindow の表示をよりリッチにできるようになりました。

Select Graphic Rendition

Terminal で色を付けるための機能は Select Graphic Rendition (SGR) と言います。これはANSI エスケープシーケンスの仕様の一つで、色だけでなくボールドやイタリックといった表現もできます。

たとえば先ほどの例では以下のような文字列でした。

$ echo '\x1b[1mHello, \x1b[0;31mWorld!'

\x1b[CSI (Control Sequence Introducer) と呼ばれるもので、これにより ANSI エスケープシーケンスの開始を示しています。あとに続くセミコロンで区切られた数値はパラメータで、このパラメータによりどのような表示にするかを指定できます。最後に m が付き、これによりパラメータの終了を示します。 今回の例では、先頭にさっそく CSI が現れており、パラメータとして 1 が指定されています。これはボールドを示しており、表示を見ると Hello, が太字になっているのが分かると思います。また、後続の CSI では 0 および 31 が指定されています。0 は既存のスタイルをすべてリセットし、31 は文字列を赤色にします。

色の指定にはいくつかの種類があり、それぞれ指定方法が異なっています。 もっとも基本的なものは 16 色のみの指定ができ、単一の値を取ります。

\x1b[31m

次に 256 色の指定方法があります。パラメータは文字色は 38;5、背景色は 48;5 から始まり、n の値によりどの色を選択するかを指定します。

\x1b[38;5;<n>m
\x1b[48;5;<n>m

次に RGB の指定方法があります。これは True Color とも呼ばれています。パラメータは文字色は 38;2、背景色は 48;2 から始まり、<r><g><b> の値によって色を指定します。 RGB で 16 色および 256 色のそれぞれの色も表現できますが、これらは区別できなければいけません。ターミナルにカスタムテーマが適用されている場合、RGB による指定ではこの設定が無視されます。

\x1b[38;2;<r>;<g>;<b>m
\x1b[48;2;<r>;<g>;<b>m

SGR と gdamore/tcell

go-fuzzyfinder はその描画に gdamore/tcell を利用しています。tcell でもスタイルを適用できますが、SGR をそのまま解釈できるわけではありません。そのため、入力となる文字列をなんらかの形でパースし、tcell の API に適合できるように落とし込んで上げる必要があります。 SGR のシーケンスを含んだ文字列をパースできるライブラリはいくつか存在していました。ただ、一部の機能が不十分だったり、go-fuzzyfinder の既存実装にうまく馴染むものがありませんでした。

そこで、1 からライブラリを実装することにしました。それが ktr0731/go-ansisgr です。

ktr0731/go-ansisgr

go-ansisgr では、NewIterator という関数があり、これが唯一のエントリポイントとなっています。この関数はイテレータを返し、このイテレータエスケープシーケンスを除いた文字と、その文字に適用されているスタイルを返します。

実際の例を見てみましょう。

in := "\x1b[1mHello, \x1b[0;31mWorld!"
iter := ansisgr.NewIterator(in)

for {
        r, style, ok := iter.Next()
        if !ok {
                break
        }

        // do nothing.
}

iter.Next() による 3 つの戻り値があります。

okイテレータの終端を表します。 r は次の文字を表す rune 型の値です。この例ではループごとに Hello …のようになります。

style はその文字に適用されているスタイルです。こちらは少し複雑になっています。ForegroundBackground というメソッドがあり、それぞれ文字色と文字の背景色を表しています。このメソッドの戻り値により、それぞれに色が設定されているかどうかがわかります。

color, ok := style.Foreground()

oktrue だった場合、色が設定されています。color を見ることでどんな色が設定されているかがわかります。 color.Mode() は、その色が 16 色、256 色、RGB のどの形式で指定されているかを表しています。また、color.Value() はその形式におけるパラメータの値です。たとえば \x1m[31m では 31、\x1m[38;5;120m では 120、\x1m[38;2;10;20;30m では 660510 (0x0a141e) となります。 RGB の場合、color.RGB() により R、G、B それぞれの値を取得することもできます。

また、style には Bold()Italic() のような bool を返すメソッドがあり、この値によってどのスタイルが適用されているかを表します。

API の一覧について、より詳しくは ドキュメント を参照してください。

go-fuzzyfinder への組み込み

こうしてできた go-ansisgr を go-fuzzyfinder へ組み込みました。gdamore/tcell は一文字ずつ描画していくため、go-ansisgr のイテレータのようなインターフェースとの組み合わせが良く、go-fuzzyfinder へ組み込んだときも大きなコードの変更なしに導入できました。*1

実際の diff は こちら から見ることができます。既存の描画のためのループ内で iter.Next() を呼び、描画対象文字とそのスタイルを順番に取得しています。

まとめ

go-fuzzyfinder で SGR がサポートされ、スタイルを適用した文字列を PreviewWindow で表示できるようになりました。 この機能の大部分は go-ansisgr によって実装・提供されており、このライブラリは入力となる SGR を含んだ文字列をイテレータ形式で扱うことができ、特に gdamore/tcell のような一文字ずつ描画していくようなライブラリとの相性が良いです。

go-fuzzyfinder も go-ansisgr もぜひお試しください!

*1:そうなるように API を設計したので当たり前といえば当たり前ですが…