ぱいぱいにっき

Pythonが好きすぎるけれど、今からPerlを好きになりますにっき

ISUCON13に出場していました #isucon

こんにちは。「失敗から学ぶISUCONの正しい歩き方 - 葬送のPostgreSQL」チームとして、id:soudai@tetsuzawaと共に出ていました。言語はGoです。

とはいえ芳しくない結果に終わりました。その辺は id:soudai のブログにあります。

soudai.hatenablog.com

我々のアプローチ

過去にfujiwara組として優勝した時などは特に素振りなどはしていなかったわけですが、これはチームの3人とも普段から一緒に仕事しているから、お互いの動き方というのがわかっているというのがありました。ただ、今回組んだメンバーたちとは一緒に仕事をしていないので、月並みな言い方をすると仲良くなるところからのスタートでした。

というわけで、この1年間は大体毎月素振りをしていました。

結果的に我々の戦略というのは以下の3つに絞られます。

  1. どういう問題であってもPostgreSQLに移行する
  2. 事前準備で計測ツールやデプロイ方法を明確にする
  3. あとはその場で解く

1のPostgreSQL移行する、というのは id:soudai さんが得意だから、このチームの最大風速を目指すのであれば選びたい戦略という理由だと僕は考えています。

3回目とか4回目ぐらいまでの昔のISUCONというのは、俺の考えたロマン構成や、ISUCON決戦兵器をぶっ放す人も多かったように思えます。ユーザーランドで処理を行わずにカーネル上でアプリケーションを書いてどうにかするチームや、nginx module上のPerlを使う人など、尖ったアーキテクチャを採用するチームもありました。あとテストカバレッジを100%にするのを目指すチームもいましたね。

今回の我々のチームの戦略であるPostgreSQLに移行する、というのはロマンの塊ではあります。しかし、過去のロマン戦略を取ったチームと同じく、勝つことを目的としています。過去の素振りではPostgreSQLに移行した結果、同時にインデックスチューニングが既になされて高速化するということもありましたし、JSON型や配列型などPostgreSQLならではの手法も使えます。

自分たちの土俵に上げるために、この1年間はひたすらPostgreSQLに移行する訓練をしてきたと言えます。その結果、今回のISUCON13では開始から2時間後の12時台にはもうPostgreSQLに移行が完了していました。

2の事前準備に関しては@tetsuzawaが初期セットアップの自動化や手順書を色々書いてくれたのと、僕の方でVPS上にGrafanaとPrometheus、Tempo、Pyroscopeを用意して、アプリケーションから飛ばすようにしていました。tetsuzawaが用意してくれていたalpの結果を表示する君と合わせて、だいぶ解像度高くアプリケーションの実態を測ることができるようになっていました。また、この辺まで含めて12時台に入っているので、最初はまあまあ合格点かなと思っています。

なぜスコアが出なかったのか

なぜスコアが出なかったかといえば、3の当日やる部分が全くうまくいかなかったという点があります。僕がやった部分と言えば、

  • PowerDNSをMySQL backendからLMDB backendに切り替える => 名前解決数は増えたもののCPUはそこまで下がらず
  • user statisticsをフルRedis化 => 失敗
  • user statisticsをsingleflightによるオンメモリキャッシュ化 => スコア上がらず

結果的に言えば、これらは手をつける順番も間違っていたし、手法もやりきれてないということで、全然ダメダメだったなあという感じです。PowerDNSに関してはiptablesによるdropをやるべきでしたが、手法を知っていたのにも関わらずキャッシュに手をつけてしまって結果的に中途半端になってしまいました。フルRedis化も本来得意なところだったはずなのにバグらせて結局時間を溶かしただけなので、実力不足と言えます。また、その手前に他にやることがいろいろあったと思うので、この辺りは素振りで確認していきたいと思います。

振り返り

良かったところ

  • 過去の参戦ではできなかった計測について取り組むことができた
  • OpenTelemetry TraceやPyroscopeなど現代的な武器を活用できた
  • 全く知らないPowerDNSの設定変更について立ち向かってその場で対処できた

悪かったところ

  • 肝心の実装で精度を出せなかった
  • ベンチマーカーが行うシナリオの観測ができていなかった
  • シナリオの観測ができなかった結果、手をつける順番を間違えた

次へのトライ

  • 実装に集中してみる
    • いい結果が出ている時は実装に専念しているので、観測はそこそこに実装をやるのが一番バリューが出る
  • シナリオをちゃんと追いかける
    • OpenTelemetry Traceで追いかけることができないかやってみる
      • アプリケーションのコミットごとにトレースを分けることができていたのでもうちょっとこの辺サマれるようにして実用的にしたい
  • 仲間を信じる

おまけ: PostgreSQL移行 アプリ視点

ISUCONの参考実装はたいていMySQLなので、MySQLからPostgreSQLに移行することを考える。Goの場合、MySQLではgo-sql-driver/mysqlが使われるが、PostgreSQLのドライバーはいくつか選択肢がある。弊チームではjackc/pgx/stdlibを採用する。

MySQLPostgreSQLとではSQLにおいて主に以下の違いがある(これはライブラリの違いも含まれる)

  • プレースホルダ
  • カラム名を囲むクオート文字
  • AUTO INCREMENTのテーブルに対して挿入した行のIDを取得する方法
    • MySQLでは LastInsertID
    • PostgreSQLではINSERTの末尾に RETURNING id を付ける
  • Upsertのやり方の違い
    • MySQLでは INSERT ~ ON DUPLICATE KEY UPDATE ...
    • PostgreSQLでは INSERT ~ ON CONFLICT DO UPDATE ...

他にも型の厳密さが違ったり、標準でのトランザクション分離レベルが違ったり、いろいろあるが、頻出パターンは以上である。

前2項目についてはクエリを実行時に動的に書き換える以下のライブラリを制作し、実戦投入していた。

github.com

後者の2つに関しては、手で書き換えるようにしていた。今回のISUCON13ではLastInsertIDが5箇所ぐらいあったように思える。

github.com

また、行の衝突のエラーハンドリングも頻出パターンではある。MySQLのエラー番号とPostgreSQLのエラー番号は当然違うので対処が必要。これはあらかじめスニペットを用意していた。今回は出番はなかった。

DDLの変換や初期化スクリプト、データ移行に関しては id:soudai さんに完全にお任せしていた。DDLの変換はChatGPTに投げたやつを手直しして使っていたらしい。時代ですね。

というわけで次も(次があれば)PostgreSQLでやって、いいところまで行きたいですね。できればデータモデリングからやり直したり、ビューをうまいこと使うとかそういう感じでやりたい。なお仕事は完全にMySQL使っています。

また来月振り返りがあるので、反省していきます。ではまた。