mizdra's blog

ぽよぐらみんぐ

ISUCON 12 予選に出場した

id:odan3240 さんと「情報処理部」というチームで出場してきました。

id:odan3240 さんの参加記もあるので合わせてどうぞ:

odan3240.hatenablog.com

結果

  • 最終スコア: 3270

何もできなかった...

時系列

結構前の話なのでうろ覚えだけど…大体こういう流れだったと思う。

  • 10:00 競技開始
  • 10:30 インスタント立てたりマニュアル読み合わせしたり
  • 〜11:30 ソースコード取り出したり、デプロイスクリプト書いたり、Datadog 入れたり
  • 11:30〜
    • id:odan3240 N+1 修正、インデックス貼る
    • id:mizdra sqlite から mysql に移行し始める
  • 14:00
  • 15:00
  • 17:30
    • Redis 導入も mysql 移行も間に合わない!となって、監視設定切ったりパラメータチューニングしてベンチマーク回し始める
    • 3270 点出たところで終了

やったこと

github.com

  • sqlite のスキーマに index を貼った
    • ...のだけど、途中でやっぱり sqlite 脱出しよう!となって中断して、実際に貼ったのは 1 つだけ。
  • sqlite のデータを mysql に移行する (失敗)
    • 最初 sqlite3-to-sql を使って移行を始めたけど、40分くらい? (よく覚えていない) 掛かる上に、完走した後に player_score テーブルのレコード数を数えたら、20万件くらい欠けていることが判明した
      • 虚無へと消えるレコード...
      • 警告とかエラーとかも出てなくて、何が起きていたのかさっぱり分からなかった
      • 「なんで〜」となって試行錯誤しているうちに 1 時間くらい溶けていた
    • そもそも 40 分掛かるのおかしいよね、ということで CSV に変換してから import する方法に切り替え始めた
      • for file in `ls ../../initial_data/*.db`; do
          sqlite3 -csv $file "select * from competition;" >> competition.csv
          sqlite3 -csv $file "select * from player;" >> player.csv
          sqlite3 -csv $file "select * from player_score;" >> player_score.csv
        done
        mysql -uisucon -pisucon -Disuports --enable-local-infile -e"load data local infile 'competition.csv' into table competition fields terminated by ',' OPTIONALLY ENCLOSED BY '\"';";
        mysql -uisucon -pisucon -Disuports --enable-local-infile -e"load data local infile 'player.csv' into table player fields terminated by ',' OPTIONALLY ENCLOSED BY '\"';";
        mysql -uisucon -pisucon -Disuports --enable-local-infile -e"load data local infile 'player_score.csv' into table player_score fields terminated by ',' OPTIONALLY ENCLOSED BY '\"';";
        
      • これで 20 分くらいに短縮された
      • けどレコードが虚無へと消えていくのは直らない...
    • mysql のバッファに余裕がなくてレコードが欠けてしまっているのでは? と思って、mysql のパラメータチューニングをし始める
      • [mysqld]
        innodb_buffer_pool_size=4G
        innodb_buffer_pool_instances=4
        innodb_log_buffer_size=200MB
        innodb_log_file_size=400M
        innodb_flush_method=O_DIRECT
        max_allowed_packet    = 64M
        
      • これで 3 分くらいに短縮された
      • レコードが虚無へと消えることもなくなった!
    • しかしベンチマークを回してみると POST /initialize がタイムアウトしてしまう!
      • webapp/sql/init.sh にこういう初期化用の sql を書いていたのだけど、created_at レコードに index がなくて、そもそも webapp/sql/init.sh に時間がかかりすぎるらしい
      • index 貼った
    • まだタイムアウトする!
      • GET /api/admin/tenants/billing が遅すぎてタイムアウトしてた
      • N+1 直してなかったのが原因っぽかった
      • 直そう、と思ったけど残り 30 分ほどしかなくて、ここであえなく時間切れ
      • https://github.com/odanado/isucon12-qualifier/pull/9
  • その他 import 中の待ち時間に nginx 周りでなにかできることないか探したり、シュッとできそうだったので /api/me をゲストユーザの時は nginx から返すようにしたりしたけど、ほとんど点数は変わらなかった

感想

sqlite to mysql に手を出したのが良くなかった… 危険な匂いを嗅ぎ分けて、先に他のボトルネック (それこそ最後に詰まっていた N+1 とか) から 1 つずつ潰していけばよかった。あとデータ量が多い時の mysql のパラメータチューニングの方法とかも事前に練習しておけばもっとスムーズだったかも。というよりは、業務でデータ量多い DB を扱う経験を積んだりとか、普段から色々なことに手を伸ばしていきましょうという話っぽい。もっと挑戦していきたい。

来年もがんばります。

趣味と仕事と技術検証

id:mizdra はプログラミングが趣味で、趣味と仕事が一致している。そのため、趣味でプログラミングする時は常に仕事を見据えた技術検証をしている。

趣味のついでに仕事にも役立てられたら最高だよね、という意識がある。

ところで、ちょっと別の話題だけど、趣味プログラミングで他に心がけていることとして、「常に何かしら新しいことに挑戦する」というものがある。

普段趣味プログラミングで何か作る時、何かしら新しいことに挑戦するということを意識している ... 折角何か創作活動をするので、ついでに新たなことに挑戦し、新たな学びやスキルの向上へと繋げようという狙い

from: 趣味で創作する時は常に何かしら新しいことに挑戦する - mizdra's blog

折角趣味でやるんだから、お得度が最大になるような時間の使い方をしよう、という意識がここにも現れている。面白い。

遠い未来の話をする理由、それは自信を持って前に進むため

id:mizdra は時々仕事で「今の時点で結論を出す必要はない、遠い未来の話」をすることがあります。1年後に考えれば良いことを、今考える、とかです。それをするのは何故か、という話を今日はします。

長期プロジェクトを段階的に進めていくには、不安が付き物

現在 id:mizdra はレガシーな Web アプリケーションへの Next.js の導入を仕事でやっています。新規実装部分から少しずつ Next.js を導入していて、段階的に Next.js 化を進めています。全部一気に Next.js 化するとなると、「レガシーな技術スタックでやってたアレは Next.js/React でどうやるの?」という問いが絶え間なく発生して全く前に進めなくなります。しかしスコープを小さくすれば、少しずつ問いに答えていって、進行できます。途中で方針転換もしやすいし、最初の機能をリリースするまでの時間も短くなります。プロジェクト全体のリスクを下げるためにも、段階的に進めていくことはとても重要です。

一方で段階的に進める場合、最初から全ての方針が決まりきっている訳ではないので、不安に駆られることがあります。最初のフェーズで先送りにした問題は、結局後で向き合うことになります。スコープを小さくするためには必要なことなので、それ自体は悪くないです。ただ、こうした疑問も抱くと思います。

『先送りにして、ちゃんと後から解決できるのか?』

結局後から解決できないと、全部イチからやり直す必要があるのではないか。今やっている作業が全部無駄になるのではないか。そうした不安に駆られます。その結果、日々の作業や意思決定に迷いが生じたり、手が止まってしまったり、議論が発散して決着が着かなくなってしまいます。不安は、目に見えない形でプロジェクトの進行によくない影響を与えていくことになります。だから「そうした不安を感じることもあるよね」で済ませるのではなく、ケアしていくことが重要です。

見通しを良くすることが大事

この問題をケアするには、いくつかの方法があります。例えば「技術ロードマップを敷く」とかです。Next.js 化で言えば、この順で問題を解決していって、古いページを Next.js 化していけばスムーズに進めるだろう、みたいな長期的な計画を立てます。ロードマップを描くと、課題に取り組むタイミングを皆がイメージできるようになって、安心感が生まれます。

また、まだ結論が出し切れていない細部の設計を紹介することも有効です。「先送りにして、ちゃんと後から解決できる保証は当然ないのだけど、何も考えていない訳ではなくて、多分できますよ、それなりに自信がありますよ。」という意思表明でもあります。たとえ荒削りのものであっても、これがあるのと無いのとでは、安心感が全然違います。

まとめると、先行きに対する見通しを良くすることが大事です。

見通しが良いと、自信を持って前に進める

こうして見通しが良くなると、日々の作業や意思決定に迷いが生じることもなくなり、結論も出しやすくなります。なにより自信を持って長期プロジェクトを進行できるところが大きいと思います。長期に渡ってずっと不安に駆られるのは大変ですからね。

まとめ

  • 長期プロジェクトを段階的に進めていくには、不安が付き物
  • 未来への見通しを良くすると、不安を軽減できる
    • 技術ロードマップを敷く
    • 結論が出し切れていない細部の設計をあえて紹介する
  • 見通しが良ければ、自信を持って前に進める
  • 段階的に進めることと、未来への見通しを良くすることは直交する概念ではない
    • 一気に全部やるわけではないけど、未来も見据える
    • バランスを取ることが大事

おまけ

この記事で書いたことと全く同じ話が、以下の記事でも軽く触れられていて、良いなと思ったので紹介します。Rust の実験的機能「GATs (generic associated types)」という機能があるのですが、その一部をそろそろ安定化させますよ、という内容の記事です。既存の全ての言語機能と一気に結合するのが難しく、段階的に結合と安定化をしようとしていて、それに対してコミュニティから「安定化を後回しにした部分は、後から問題が出て安定化できなくならないか?」「安定化させるなら、全部一気に安定化できるまで待つべきでは?」という疑問が出ていて、それに対するアンサーが書いてあります。

The goal of this blog post is to try to imagine a “shiny future” where we have GATs. It’s to consider a couple of the patterns that people want GATs for, how we might want to integrate GATs into standard library traits, and a few issues that don’t work today that we want to work in the future. We want this so that we have a future to strive for after stabilization. There are known shortcomings with GATs right now, but that’s okay. They’re already powerful and, as you’ll see, we have some thoughts on how to make them more powerful and ergonomic in the future.

DeepL 翻訳:

このブログ記事の目的は、GATがある「輝やかしい未来」を想像してみることです。人々がGATを欲しがっているいくつかのパターン、>GATを標準ライブラリのtraitに統合する方法、そして現在機能していない問題のうち、将来的に機能するようにしたいいくつかの問題について考えることです。これは、安定化した後に目指すべき未来があるようにしたいのです。今、GATには既知の欠点がありますが、それでもいいんです。すでにパワフルですし、ご覧のように、将来的にもっとパワフルで人間工学に基づいたものにするための考えもあります。

「try to imagine a “shiny future” (輝かしい未来を想像してみる)」「We want this so that we have a future to strive (目指すべき未来があるようにしたい)」という言葉がとっても良いと思います。

リリースノート自動生成テクニック

普段からいくつか趣味で作ったツールやライブラリを npm パッケージとして publish しています。ちょっと工夫していることとして、「できるだけ簡単に npm publish できるようにしておく」というものがあります。npm publish が心理的に、手順的に難しいと、すでに main ブランチに新機能や修正が入っているのに、npm publish されていない、という状況が発生しがちです。新機能や修正をすぐにユーザに送り届けられるよう、npm publish は無思考でできるようになっていると嬉しいです。

その一環として、リリースノート (CHANGELOG) の自動生成というのをやっているので、その紹介をしてみます。本当は 6 月にやっていた Maintainer Month 期間 に間に合わせたかったのですが、とろとろしていたら 7 月になってしまった! まあ遅れたから公開しないのも勿体ないので気にせず公開します。

GitHub のリリースノートの自動生成機能を使う

github-changelog-generator とか、git-chglog とか、自動生成するツールはすでに色々あるのだけど、id:mizdra は最近 GitHub の「Automatically generated release notes」という機能を使っている。

まず事前準備として、.github/release.yml というファイルをリポジトリに commit しておきます。この label がついていたらこのグループに振り分ける、みたいな設定を書きます。

# .github/release.yml
# ref: https://github.com/mizdra/eslint-interactive/blob/ae140c92081f8fb5c6d2cc3368ed5b186529981f/.github/release.yml

changelog:
  exclude:
    labels:
      - 'ignore for release'

  categories:
    - title: Breaking Change
      labels: ['Type: Breaking Change']

    - title: Bug
      labels: ['Type: Bug']
    - title: Documentation
      labels: ['Type: Documentation']
    - title: Feature
      labels: ['Type: Feature']
    - title: Refactoring
      labels: ['Type: Refactoring']
    - title: Testing
      labels: ['Type: Testing']
    - title: Maintenance
      labels: ['Type: Maintenance']
    - title: CI
      labels: ['Type: CI']
    - title: Question
      labels: ['Type: Question']
    - title: Security
      labels: ['Type: Security']
    - title: Dependencies
      labels: ['Type: Dependencies']

    - title: Other Changes
      labels: ['*']

GitHub Release で新しい release のリリースノートを書くところにあるボタンを押すだけ。

youtu.be

これだけです。簡単!

細かい編集をしてから公開も可能

自動生成されたリリースノートはそのまま textarea に挿入されるので、textarea 上で手直しして、それから公開する、ということができるます。結構細かい調整をしたいことがあるので、こういうのができるのはありがたいですね。

youtu.be

グルーピング用のラベルは @azu/github-label-setup を使って用意すると楽

「Type: Dependencies」とか「Type: Security」とかは GitHub がデフォルトで用意しているラベルリストの中に入っていないので、自分で作ってやる必要があります。手で作ると結構面倒なので、id:mizdra@azu/github-label-setup というツールを利用して1コマンドで作ってます。

efcl.info

$ # 事前に @azu/github-label-setup をインストールしたり、envchain に GITHUB_ACCESS_TOKEN を保存しておく
$ npm i -g @azu/github-label-setup
$ ehvchain --set github GITHUB_ACCESS_TOKEN

$ # リポジトリルートへ移動
$ cd mizdra/eslint-interactive
$ # https://github.com/azu/github-label-setup#default-labels にあるラベルリストに変更
$ envchain github github-label-setup -A

オススメです。

最後に

Maintainer Month に合わせて色々な人がオープンソースのメンテナンスについて記事を書かれているので、是非読んでみてください。また普段オープンソースのメンテナンスをしている人は、なにか記事を書いてみて、普段の活動を振り返ったりすると、良い機会になると思います。

efcl.info

sosukesuzuki.dev

極端なパターンを考えて、技術的な落とし所を探っていく

「React コンポーネントってどこまで細かく分割していいの?」とか、「Jest のスナップショットテストテストってどんなテストにも使っていいの?」とか、技術的な落とし所をどうするか悩むことは結構多いと思う。

コンポーネントを分割すると、責務が小さくなったり、再利用しやすくなったり、変更に強くなったり、各種ツールから利用しやすくなったりと、色々なメリットが得られる。けど、だからといってコンポーネント分割のしすぎは良くないと思う。コンポーネント分割するとなると、(そのプロジェクトの規約によるけど) コンポーネントファイルを新しく作らないといけないし、CSS Modules 用の *.module.css ファイルも作らないといけない。ファイルが増えることで、開発中に色んなファイルを行ったり来たりすることになって、作業効率が落ちる場合もある。コンポーネント分割だってタダじゃないし、デメリットもある。だからこそ、どこまでコンポーネント分割していくのか、その落とし所で悩むことになる。

こういう時に落とし所を探す方法はいくつかあるのだけど、id:mizdra はよく「極端なパターンを考える」というのをやっている。例えば「1タグごとにコンポーネント分割していったらどうなるか」とか。

function H2(props) {
  return <h2 {...props} />
}
function Img(props) {
  return <img {...props} />
}
function UserProfile({ useName, avatarUrl }) {
  return (
    <H2>{userName}<H2/>
    <Img src={avatarUrl} />
  );
}

見ての通り、コンポーネント分割したのに責務は小さくなっていないし、再利用しやすくなっていないし、変更にも強くない。コンポーネント分割のメリットが全く享受できていない。けどデメリットは被っている。「ああ、多分細かく分割しすぎたのだな」「メリットが享受できないパターンは NG なのだな」「ということは、責務が小さくなったり、再利用しやすくなると嬉しい場面で使っていくのが良いのかな?」「ロジックが沢山あるようなコンポーネントだったら責務小さくなって嬉しそうだし、色々なページで何回も出てくるようなパーツだったら再利用の効果が効いて良さそうだな」。そういった気づきが得られるはず。

あまりにも愚直なテクニックなのだけど、意外なことに、そこから芋づるのように技術的な落とし所のヒントが見つかってくる。色々な場面で使えるし、バカっぽいアイデアを考えるのが (そして信じられないことに、そのバカっぽいアイデアから気づきが得られるのが) 何より楽しい。オススメです。

実践例

以下の記事も今回紹介したテクニックを使って書きました。

www.mizdra.net

  • 出力の差分が巨大になる場合、スナップショットテストは向いてません
    • 例: 2 京行の差分が出る時
    • 例: Visual Regression Test で、5GB の画像が出力される時
    • テストフレームワークが処理するのに時間が掛かるし、最悪の場合クラッシュします
  • 出力の差分を見て、その妥当性を判断するのが困難な場合、スナップショットテストは向いていません
    • 例: 5 万行の差分が出るテスト
    • 1 分で 50 行見たとしても見終わるのに 1000 分掛かります
      • 9 時に出社して差分を見始めて、お昼過ぎてもまだまだで、定時過ぎてもまだまだで、終電が無くなった頃にようやく見終わる、みたいなスケール
      • 機械より人間が頑張るスタイル
    • ただし差分の内容を見ずに変わったことだけ分かれば良い場合や、最初の 100 行だけ見て妥当ですねと判断できる場合であればこの限りではないです
  • 出力の変化が激しい場合
    • 例: 現在時刻を表示するクロックコンポーネントのテスト
      • テストを実行する時刻によって出力が変わって毎回テストが fail します
      • 毎回人の目でチェックしないといけません
      • 機械より人間が頑張るスタイル 2
      • ただし何らかの手段で出力を固定できれば、問題を回避できる場合があります
        • クロックコンポーネントの例では時間を司る API をモックして時刻を固定すれば良い
        • Visual Regression Test なら変化する部分を黒塗りしてから比較すれば良い
        • このような出力の固定化が困難な場合はスナップショットテスト以外のテスト手法を検討しましょう
    • 例: アプリケーションのソースコードそのもののスナップショットテスト
      • ソースコードを 1 文字書き換えたらテストが fail する、みたいな
      • 誰もそんなテスト書かないと思いますが…
      • 良い具体例が思いつかなかった!本当はちょっとどこかを弄ったら毎回 fail するのは良くないよね、みたいなことを言いたかった。

チームに提案をする時は傾聴することが大事

「こういう取り組みをチームで始めてみたい」だとか、「新しいコーディング規約を考えてきたので、これを導入してみたい」だとか、「こういうツールを導入してみたい」だとか、チームに対して提案をする場面というのはよくあると思います。実際に提案する際には、以下のような 4 つのフェーズからなるフローで進めることが多いと思います。

  1. 提案の概要をまとめた資料 (提案書) を作成する
    • 何故それをやりたいのか (背景・動機)
    • どんなことをやりたいのか、やってみてどんなメリットがあるのか
    • 懸念やデメリット、代替案などはあるか
  2. 1の提案書を誰かにレビューしてもらう
    • 意見をもらって、提案書を適時修正するフェーズ
  3. 再度レビューに出して、チームから承諾をもらう
  4. 提案を実行する

で、この 2 についてなのですが、id:mizdra はこのフェーズで傾聴を大事にしています。レビューに出したら、色々な人からフィードバックコメントを貰えると思うのですが、それを傾聴するようにします。どんな些細なことでも、相手の気持ちや考えに寄り添って、丁寧な解決策を提示してみる。レビューする人のスキルは大抵バラバラで、時にはちょっとずれた意見をもらうこともあると思うのですが、そういう意見にも突き返すことなく、真摯に対応することを大事にしています。

意見をくれた人を突き返すようでは、あきれられたり、意見を言うことを恐れるようになってしまって、逆に意見が貰えなくなってしまいます。意見を貰うために提案をしているのだから、意見を気軽に言ってもらえる状態を目指すべきです。些細な意見でも、コミュニケーションを繰り返すうちに思わぬ発見があることもよくあります。しかもそれが思いの外重要なことだったり… どんな意見も大切にすることが重要です。

そもそも意見を無視して提案を押し通そうとするのなら、意見を聞く機会を設ける必要もないので、提案なんかせずに勝手に進めれば良いですからね。何が言いたいかというと、意見を突き返すようであれば、それは提案とは言えないということです。

傾聴は提案以外でも色々な場面で重要なスキルなので、提案以外の場面でも意識していけると良いですね。

見つけた GitHub の Issue を片っ端から subscribe している

あるライブラリを使っていてバグっぽい挙動に遭遇した時、ほぼ必ず当該ライブラリの Issue を検索するようにしている。加えて、見つけた Issue の subscribe ボタンを押して、https://github.com/notifications に通知がいくようにしている。バグ遭遇時以外にも、何らかの理由で Issue に到達した時にその Issue を subscribe してる。

たったこれだけのことだけど、これを地道に続けていると https://github.com/notifications を見れば無限に最新お得情報が流れてくる、面白ニュースフィードにできる。バグ修正されたらすぐ修正されたバージョンを取り込みにいけるし、欲しい機能が実装されたらすぐ触りにいける。id:mizdra は /notifications を見ては、趣味や業務で困っていたバグの修正情報を即座にキャッチして、その修正を即取り入れる、という活動に役立てている。

裏話が自然と頭に入ってくる

バグ修正されるまで/機能が実装されるまでの議論も逐次追っていけるので、たまに面白い裏話とか、実装の背景とか、設計の意図とか裏話的なものが自然と頭に入ってくるのも嬉しい。あんまり興味ないバグや機能の裏話は中々頭に入ってこないけど、興味のあるものを選んで購読するので、そんなに苦なく続けられると思う。id:mizdra はこの subscribe 活動を初めて 3 年くらい経ったけど、今の所楽しくやれている。

躊躇せずどんどん subscribe していく

沢山 subscribe していくと通知で溢れかえるんじゃないか、と思うかもしれないけど、今の所 id:mizdra はそんなことになっていないので、躊躇せず subscribe している。毎日 3〜5 issues くらいしか更新ないので、普通に数日おきに見ていれば取りこぼすこともない。むしろにいろんな issue を subscribe することで、毎日色々な面白情報がやってきて楽しいのでどんどん subscribe している。

/notifications には watch しているリポジトリの情報も流れてくるので、watch しているリポジトリが多い人だと、爆速で情報が流れていくので使いづらい、とかはあるかもしれない。僕は watch しているリポジトリほとんどないので困っていない。watch しまくっている人は /notifications 以外で見る、とか工夫が必要かも。

おまけ (2022/06/29 追記)

subscribe した Issue は以下の URL から一覧できる。「以前 subscribe したあの Issue どれだっけ...」という時に便利。

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.