モヒカンメモ

髪色が定期的に変わることに定評のある(比較的)若者Webエンジニアの備忘録

【GitHub】 マージ済みのremote branchを一括削除するコマンド

GitHubへマージ済みブランチが貯まると git clone するのにかかる時間が増えてしまったり、ブランチ一覧からお目当てのブランチを探すのが面倒になるなど、地味に生産性を下げるのでこまめにお掃除するのがオススメ。

コマンド

$ git branch --remotes --merged | grep -v "origin/main" | sed -E 's/  origin\/(.*)/\1/' | xargs -I{} git push origin :{}

解説

(1) マージ済みのブランチ一覧を取ってくる

$ git branch --help
...
OPTIONS
...
       -r, --remotes
           List or delete (if used with -d) the remote-tracking branches. Combine with
           --list to match the optional pattern(s).
...
       --merged [<commit>]
           Only list branches whose tips are reachable from the specified commit (HEAD if
           not specified). Implies --list.
$ git branch --remotes --merged
  origin/some-merged-branch-1
  origin/some-merged-branch-2
  origin/some-merged-branch-3
  origin/main

(2) 消しちゃダメなブランチを取り除く

例えば main ブランチを残しておきたいなら

$ git branch --remotes --merged
  origin/some-merged-branch-1
  origin/some-merged-branch-2
  origin/some-merged-branch-3
  origin/main

$ git branch --remotes --merged | grep -v "origin/main"
  origin/some-merged-branch-1
  origin/some-merged-branch-2
  origin/some-merged-branch-3

(3) "origin/" 部分をtrimする

sedで origin/ 以降だけを正規表現の参照で取り出す

$ git branch --remotes --merged | grep -v "origin/main"
  origin/some-merged-branch-1
  origin/some-merged-branch-2
  origin/some-merged-branch-3

$ git branch --remotes --merged | grep -v "origin/main" | sed -E 's/  origin\/(.*)/\1/'
some-merged-branch-1
some-merged-branch-2
some-merged-branch-3

(4) remote branchを削除する

単体のブランチ削除コマンドとxargsを組み合わせて全部消していく

$ git push --help
...
       git push origin :experimental
           Find a ref that matches experimental in the origin repository (e.g.
           refs/heads/experimental), and delete it.
$ git branch --remotes --merged | grep -v "origin/main" | sed -E 's/  origin\/(.*)/\1/'
some-merged-branch-1
some-merged-branch-2
some-merged-branch-3

$ git branch --remotes --merged | grep -v "origin/main" | sed -E 's/  origin\/(.*)/\1/' | xargs -I{} git push origin :{}
remote:
To github.com:aaa/bbb.git
 - [deleted]         some-merged-branch-1
remote:
To github.com:aaa/bbb.git
 - [deleted]         some-merged-branch-2
remote:
To github.com:aaa/bbb.git
 - [deleted]         some-merged-branch-3

削除対象がたくさんあるときは、xargsに -P n を指定して並列度上げると短時間で終えられる。

Docker ComposeでDBコンテナの起動を待ってからAPIコンテナを起動させる

ざっくりまとめ

Docker Composeの depends_onhealthcheck を組み合わせると、DBコンテナが完全に起動してからAPIコンテナを起動させられる。

課題

DBとAPIからなるWebアプリケーションの実行環境をDocker Composeで用意しているとき、DBが完全に起動し終わる前にAPIが起動してリクエストを処理すると 500 (Internal Server Error) を返してしまう問題がある。

ローカル環境などでの開発時であれば大きな問題はないが、CI時にコンテナスタックの起動後即座にAPIテストが走る前提だとこの問題を踏んで「なぜか稀にAPIテストが500で落ちる」という現象に悩まされることになる。

毎回起こるわけではないため対応の優先度付けが難しく、またAPIテストが落ちるとなったら普通はAPIに不具合があることをを疑ってしまい本当の原因に気づきづらい。

解決方法

Docker Composeの depends_onhealthcheck を組み合わせて、DBコンテナの完全起動を待ってからAPIコンテナを起動させる。

(1) depends_on

depends_on は、あるコンテナが別のコンテナに依存していることを表明する設定。 docker compose up を叩いた際のコンテナ起動順に影響があり、この設定をしておくと依存先から順に起動してくれる。

例えばAPIコンテナがDBコンテナに依存しているとき、下記のように depends_ondb と記載しておくと db コンテナを起動後に api コンテナが起動されるようになる。

...
  services:
  
    api:
      ...
+      depends_on:
+        - db
  
    db:
      ...

ただし、ここでいう "起動" とは「CMDで指定されているコマンドを実行して即座に異常終了しなかった」という状態を指しているので、この設定だけではDBのウォームアップ中にAPIコンテナが起動して500を返し得てしまうのでもう少し工夫が必要。

(2) healthcheck

healthcheck は、正常状態かどうかの検証方法をDockerに検証させるための設定。コンテナのウォームアップが終わってリクエストを正常に受け入れられる状態かどうかの検証方法をここで設定する。

例えばDBコンテナが完全起動したら mysqladmin ping -u root とか mysql -u root -e "SELECT 1;" コマンドが通るはずなので、下記のように test に検証コマンドを記載する。

  services:
  
    db:
      ...
+      healthcheck:
+        test: ["CMD", "mysql", "-u", "root", "-e", "SELECT 1"]
+        interval: 6s
+        timeout: 1s
+        retries: 5        

(3) depends_onとhealthcheckを組み合わせる

依存関係を表明する depends_on と、完全起動状態を検証する healthcheck を組み合わせると、DBコンテナが完全起動してからAPIコンテナを起動させられる。

...
  services:
  
    api:
      ...
+     depends_on:
+       db:
+         condition: service_healthy

    db:
      ...
+     healthcheck:
+       test: ["CMD", "mysql", "-u", "root", "-e", "SELECT 1"]
+       interval: 6s
+       timeout: 1s
+       retries: 5

depends_on を書くときに condition: service_healthy と指定しないといけないのがややトラップ。

【 PHP 】 preg_match関数に渡せる正規表現の長さには上限がある

超長い正規表現をPHPのpreg_match関数に食わせたところエラーになった。

2022/9/30現在、公式ドキュメントにはこの振る舞いは記載されていないので調べたことを残しておく。

https://www.php.net/preg_match

エラーメッセージ

PHP Warning: preg_match(): Compilation failed: regular expression is too large at offset 32765 in php shell code on line 2

正規表現長すぎと怒られていることが読み取れる。

検証コード

# php -a
Interactive shell

php > echo PHP_VERSION;
8.1.9

php > $length = 32000;
php > while (true) {
php {     if (preg_match('/' . str_repeat('a', $length) . '/', 'x') === false) {
php {         echo "max:" . ($length - 1);
php {         break;
php {     }
php {     $length++;
php { }
PHP Warning:  preg_match(): Compilation failed: regular expression is too large at offset 32765 in php shell code on line 2
max:32764

※ 初期 $length を32,000としているのは、事前の調査で32,000ではpassして33,000ではエラーになることが分かっていたため

まとめ

preg_match関数の第一引数 ($pattern) として渡せる正規表現の長さは最大32,764文字。 それを超えると Compilation failed Warningが発生し、実行結果はfalseとなる。

ISUCON12 予選に参加して無残な敗退を喫した💀

恒例行事となっているISUCON予選に今年も参加し、無残な敗退を喫してきた💀

isucon.net

去年のブログ

blog.pinkumohikan.com

チーム紹介

チーム名は毎年見直していて今年は2時間にも及ぶ激論の末「 牡蠣食えば 金が無くなり リボ払い 」 (5-7-5) に、チームリーダーは怪しい野良抽選ツールによる厳正なる抽選(?)の結果 @nekodaisuki が務めることに。

nekodaisuki

  • リーダー
  • SQLの魔術師

cureseven

  • 歌手
  • 分析隊長

pinkumohikan

  • 肩もみがかり

振り返り

予選当日

記録: github.com

序盤は極めていい感じに動けており、遅いクエリのindexを調整したり、アプリ・DB分離を行ったり、データの使われ方に応じてテーブル設計を見直すなどして瞬間ランキングでは1位になったこともあった。「練習通りだ、今年は本戦行けるぞ!」このときは誰もがそう信じて疑わなかった。

瞬間ランキング 1位

中盤に差し掛かり、MySQLサーバの負荷が5%以下となった段階でSQLiteについて思いを馳せ始めた。 SQLiteは単体での性能は高いがスケーラビリティに欠けるのと単純に我々は慣れていないのでMySQLへ移行することにしたのだが、ここが難所だった。

不慣れなSQLiteとの対話、終わらないMySQLへのローディング...一進一退の攻防を繰り広げながら、最終的にはinitializeが時間内に終わらない問題を倒しきれずMySQL移行は失敗した。着手段階でMySQL移行が上手く行かない可能性はありそうだなと思いつつ、SQLite固有の施策を進めてMySQL移行完了後に2度手間になるのがやだなーと思ってSQLite周辺にあまり手を入れなかったのが良くなかった。さらに言えば、SQLiteで何ができるのかをしっかり調査せずに「SQLiteってtx使えなかったよね〜」などと勘違いしたまま進めてしまったのも良くなかった。

競技終了1.5時間ほど前の時点でMySQL移行が時間内に終わらない可能性の高まりを感じ、SQLiteのまま行くことことにやっと前向きになった。SQLiteで実行しているクエリのindexやクエリのチューニングをはじめたところすぐに4,000点ぐらい上がった。あと1時間あれば予選通過ラインには届いていそうな伸び率だっただけに、この判断をもう少しはやく出来ていれば...と思うなどした。

最終結果:

133位 / 698チーム中
from: https://isucon.net/archives/56838276.html

練習

練習には十分に取り組めた。 今年も10回近くの練習会を実施し、他チームとの合同練習会は背筋がピンとなるとても良い機会だった。 来年も同様の練習をしていきたい。

リーダー業のローテーション

いつチームが解散してもそれぞれがリーダーとして新チームを引っ張れる状態にしたいので、リーダー業をローテーションをしていきたいという思いがある。

これに関しては練習時はいい感じだったが、予選当日は (予選通過がかかっている事もあり) 進行や意思決定に少し口を出しすぎた感覚がある。 来年はもっとリーダーにリーダーらしい振る舞いをしてもらうための配慮をしたい。

課題

今回もめちゃめちゃ唸らされるおもしろい問題だった。 毎年毎年、そろそろ課題を出しつくたんじゃないか?って思うのに新たな唸りポイントを作れるのは本当にすごい。

技術的にもSQLiteはそれ自体は歴史があるが使われる箇所でいうと組み込みやネイティブアプリなどいわゆるWebエンジニアリングからはやや距離があるイメージだったが、最近ではCloudFlareがエッジコンピューティングに採用したりで注目度が上がっているので触れられてよかった。ファイルにすべての状態が閉じているので参照偏重なワークロードならアプリケーションとSQLiteと同梱して配布するなどすればコスパよくスケールさせられそう。

来年も対戦宜しくお願いします 🔥

指定したサイズ以上のファイルを見つける

どこにあるか分からないけどデカいファイルがあって悪さしていそうなので探したいときなどに、 find コマンドの size オプションが便利。

findコマンドにsizeオプションをつけるとファイルサイズで検索できる

findコマンド

ディレクトリやファイルを探せる便利なLinuxコマンド。

atmarkit.itmedia.co.jp

findコマンドのsizeオプション

容量の検索オプション。 例えば100MB以上のサイズのものを検索したければ -size +100M のような感じ。

$ man find
...
-size n[ckMGTP]
        True if the file's size, rounded up, in 512-byte blocks is n.  If n is followed by a c, then the primary is true if the file's size is n bytes (characters).  Similarly if n is followed by a scale indicator then the file's size is compared to n scaled as:

        k       kilobytes (1024 bytes)
        M       megabytes (1024 kilobytes)
        G       gigabytes (1024 megabytes)
        T       terabytes (1024 gigabytes)
        P       petabytes (1024 terabytes)

探してみる

前提:

$ ls -lh
total 614400
-rw-r--r--  1 pinkumohikan  staff   100M  8 15 06:18 100MB.txt
-rw-r--r--  1 pinkumohikan  staff   101M  8 15 06:18 101MB.txt
-rw-r--r--  1 pinkumohikan  staff    99M  8 15 06:18 99MB.txt

※ 各ファイルは dd if=/dev/zero of=100MB.txt bs=1M count=100 のようにカッチリ作ったもの

(1) 指定した容量のファイルを探す

$ find . -type f -size 100M
./100MB.txt

(2) 指定した容量よりも小さいファイルを探す

$ find . -type f -size -100M
./99MB.txt

(3) 指定した容量よりも大きいファイルを探す

$ find . -type f -size +100M
./101MB.txt

使われていない機能を積極的に消すべき理由

(プロダクト開発の文脈で) 使われていない機能は百害あって一理なしなので積極的に消すべきだと考えている。

使われていない機能は消すべき慈悲はない

とある日の某氏に降り掛かった悲しい出来事

消すべき理由1: プロダクトにおける "コア" の部分がハッキリする

たくさん機能があると何がコアなのか分かりづらくなる。「色々できることは分かったけど、何が強み(売り)なの?」という質問に答えにくくなる。

機能が減ればコアな部分が明確になる。コアが明確になると説明しやすくなる。説明しやすくなると営業しやすくなる。UIもシンプルになってお客さんにとっても使いやすいものになる。サポートにかかるコストも減る。開発者もどこを重点的に守る必要があるか分かる。

消すべき理由2: 機能の数は開発コストに直結する

売れることがバレれば競合はどんどん増えるし、時代が変われば求められているものが変わる。一度システムを作ればそれをずっと提供し続けられるわけではない。

機能が増えるとシステムの複雑度があがる。複雑度が上がるとシステム全体を理解しづらくなり、開発コスト(変更コスト)があがる。拡張をどんどん続けているサービスでは1年前は7日で作れたものが今では1ヶ月かかるとかはザラ。そのままにすると開発者はいなくなる。

余談としては設計を見直すことで複雑度を下げるというアプローチもある。それも大事だけど、使われていないものを消すほうがリスクが低いし手っ取り早い。

もったいない気持ちとの戦い

ある機能も一度はコストをかけて作られたもの。それを捨てようとしたら "もったいない" と思うのはごく自然なこと。

でも、使われていないということは価値を生んでいないということ。そして使われていない機能を消すべき理由に対して逆説的に、 機能は存在しているだけでコストが発生していると言える。

"価値を生んでいないのにコストが発生するもの"、捨てたくありませんか?

さくらのレンタルサーバ上で Node.js (npm) を使えるようにする

諸般の事情でさくらのレンタルサーバ上で Node.js (npm) を使いたくなったので、調べたことやインストール手順を残しておく。

前提:

  • 2022年5月時点の情報
  • さくらのレンタルサーバではNode.js (npm) は提供していない (ので使えるようにすることも含めてすべて自己責任)

選択肢

(1) パッケージマネージャでNode.jsをインストールする

さくらのレンタルサーバはFreeBSDというOSが使われていて、FreeBSDではパッケージマネージャとして packagesports が使えるらしい。

[pinkumohikan ~]$ uname -s
FreeBSD

https://docs.freebsd.org/ja/books/handbook/ports/

さくらのレンタルサーバはいわゆる "レンタルサーバ" なので、パッケージをグローバルインストールするのに必要なroot権限は (当然) 利用者には与えられていない。そのためパッケージマネージャを利用してNode.jsをインストールすることは出来ない。

(2) バイナリをダウンロードして使う

macOSやLinux等のメジャーOS用のバイナリは公開されているが、FreeBSD用の公式Node.jsバイナリは配布されてなかった。

https://nodejs.org/ja/download/

Download Node.js (npm) Binary

(3) バイナリをコンパイルして使う

Node.jsはOSSなのでソースコードが公開されている。

https://github.com/nodejs/node

コンパイルに必要なアレコレ (gccとか) はさくらのレンタルサーバへインストールされていたので、Node.jsのソースコードをダウンロードして自分でコンパイルすることでNode.js (npm) を使うことができそう。

Node.jsのインストール

今回は「(3) バイナリをコンパイルして使う」の方法でNode.jsを使えるようにしていく。

(1) OpenSSLをコンパイルする

標準で使えるOpenSSLはバージョンが古くてNode.jsのビルドに使えない (※) ため、新しめなOpenSSLをダウンロード & コンパイルする。

https://www.openssl.org/

[pinkumohikan ~/openssl]$ curl -sSf https://www.openssl.org/source/openssl-1.1.1o.tar.gz -O
[pinkumohikan ~/openssl]$ tar zxf openssl-1.1.1o.tar.gz

[pinkumohikan ~/openssl-1.1.1o]$ ./config --prefix=/home/pinkumohikan/openssl --openssldir=/home/pinkumohikan/local/openssl
[pinkumohikan ~/openssl-1.1.1o]$ make
[pinkumohikan ~/openssl-1.1.1o]$ make install

(2) Node.jsをコンパイルする

2022年5月時点の推奨LTSはNode.js 16系だが、Node.js 16からPython 3必須になる問題 (※) があって都合が悪いので今回はNode.js 14を使うことにする。

https://nodejs.org/ja/download/

[pinkumohikan ~]$ curl -sSf https://nodejs.org/dist/v14.9.0/node-v14.9.0.tar.gz -O
[pinkumohikan ~]$ tar zxf node-v14.9.0.tar.gz

[pinkumohikan ~/node-v14.9.0]$  ./configure --shared-openssl --shared-openssl-includes=/home/pinkumohikan/openssl/include/ --shared-openssl-libpath=/home/pinkumohikan/openssl/lib/
Package openssl was not found in the pkg-config search path.
Perhaps you should add the directory containing `openssl.pc'
to the PKG_CONFIG_PATH environment variable
Package 'openssl', required by 'virtual:world', not found
Node.js configure: Found Python 2.7.18...
INFO: configure completed successfully
[pinkumohikan ~/node-v14.9.0]$ export LD_LIBRARY_PATH=/home/pinkumohikan/openssl/lib
[pinkumohikan ~/node-v14.9.0]$ nohup make install DESTDIR=/home/pinkumohikan/local PREFIX=

make install には数十分かかるのでコネクション切断に備えてnohupつけておくのがオススメ。

(3) Pathを通す

モノができたらあとはパスを通すだけ。

[pinkumohikan ~/node-v14.9.0]$ echo "export PATH=$PATH:~/local/bin
export LD_LIBRARY_PATH=/home/pinkumohikan/openssl/lib" >> .bashrc

ログインし直すか source .bashrc して

[pinkumohikan ~]$ node -v
v14.9.0
[pinkumohikan ~]$ npm -v
6.14.8

完。

備考

OpenSSLが古くてNode.jsのコンパイルに使えない問題

標準のOpenSSLを利用してNode.jsをコンパイルしようとすると下記のようなエラーが出る。

[pinkumohikan ~/node-v14.9.0]$ make
...
../src/node_crypto.h:72:46: error: use of undeclared identifier
      'EVP_MD_CTX_free'; did you mean 'EVP_MD_CTX_create'?
using EVPMDPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
                                             ^~~~~~~~~~~~~~~
                                             EVP_MD_CTX_create
[pinkumohikan ~]$ openssl version
OpenSSL 1.0.2o-freebsd  27 Mar 2018

GitHub Issueを軽く読んだ感じOpenSSLが古いせいらしい。

https://github.com/nodejs/node/issues/22025

Node.js 16からPython 3必須になる問題

さくらのレンタルサーバではPython 2系までしか使えないので、Python 3系が必須となるNode.js 16系は入れられない。

[pinkumohikan ~/node-v16.15.0]$ ./configure
Node.js configure: Found Python 2.7.18...
Please use python3.10 or python3.9 or python3.8 or python3.7 or python3.6.

Node.js 14系までならPython 2系でいける。

ただ、Python 3を自前コンパイルすればNode.js 16系もいけそうな気がするので誰か試してみて欲しい。

https://www.python.org/downloads/

参考にした資料

PHPUnit実行時にメモリ使用量制限に引っかかる場合の対処方法

たくさんのテストケースを抱えるプロダクトでPHPUnitを実行した際、下記のようなエラーが表示されることがある。

エラーメッセージ:

$ ./vendor/bin/phpunit
...
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted

これはPHPのメモリ使用量制限 (memory_limit) を超えてメモリを確保しようとしたためPHPがプログラムの実行を中断したことを示すエラーメッセージ。

対処方法

メモリをたくさん使っているテストケースを見つけていい感じに直すという方法もあるが、元々PHPはメモリどのぐらい使うかとかは些細なこととしてあまり気にしない文化だと思うので (メモリ10GB食うみたいなアホみたいな状態でない限り) メモリ使用量制限を引き上げて凌ぐのが良いと思う。

(1) phpunit.xmlでメモリ使用量上限を引き上げる

PHPUnitの設定ファイル phpunit.xml に設定を書くことで、PHP設定を一時的に変更した状態でPHPUnitを実行することができる。

phpunit.readthedocs.io

デフォルト128MBから512MBへメモリ使用量上限を引き上げたければ下記のように変更する。

--- a/phpunit.xml
+++ b/phpunit.xml
@@ -2,5 +2,6 @@
 <phpunit>
     <php>
         <env name="APP_ENV" value="test"/>
+        <ini name="memory_limit" value="512M"/>
     </php>
 </phpunit>

個人的にはこの方法が一番妥当だと思うのでおすすめしたい。

(2) PHP実行時オプションでメモリ使用量上限を引き上げる

php コマンドにある -d オプションで一時的にPHP設定を変更することができる。

$ php --help
Usage: php [options] [-f] <file> [--] [args...]
...
  -d foo[=bar]     Define INI entry foo with value 'bar'
...

例:

$ php -i | grep memory_limit
memory_limit => 128M => 128M   # ← デフォルトでは上限128MB

$ php -d "memory_limit=512M" -i | grep memory_limit
memory_limit => 512M => 512M   # ← 一時的に上限512MBへ引き上がっている

これまで ./vendor/bin/phpunit ./tests のようにPHPUnitを実行していた場合、 php -d "memory_limit=512M" ./vendor/bin/phpunit ./tests のようにして実行時オプションを指定する。

(3) php.iniでメモリ使用量上限を引き上げる

PHPの設定ファイル php.ini でメモリ使用量上限を変更することができる。

www.php.net

f:id:pinkumohikan:20220115200027p:plain
リソース制限: memory_limit

ただし、php.iniの影響範囲はシステムグローバルとなるので、特定のツール (PHPUnit) のためだけにグローバルな設定を変えるというのはあまりお行儀が良くないようにも思う。

古めのLaravelアプリがComposer 2で動かない問題

少し古めのLaravelアプリケーションを保守していてComposer 2で composer install 出来ない問題にぶち当たったので、これから躓くひとのために記録を残しておく。

f:id:pinkumohikan:20211121151059p:plain
Error: @php artisan package:discover --ansi In PackageManifest.php line 131: Undefined index: name

ざっくりまとめ

  • Composer 2になってComposer 内部データファイルのデータ構造が変わった
  • LaravelにはComposer 内部データファイルを参照する処理があるため、パッチが当たる前のLaravelではComposer 2で動かない
  • Composer 2を使いたければ、その前にLaravel 6.18.7、7.6.0以上へバージョンアップが必要

エラーメッセージ

$ composer --version
Composer version 2.1.12 2021-11-09 16:02:04

$ composer install
...snip...
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi

In PackageManifest.php line 131:

  Undefined index: name


Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1

ざっくり解説

LaravelにはPackageManifestというクラスがあり、こいつはComposerの内部ファイルを参照する。

github.com

Composer 2になって内部ファイルのデータ構造が変更されたので、新データ構造に対応していない古いLaravelアプリケーションでは上述のエラーが起きる。

LaravelのIssueとしてはこれ。

github.com

上記Issueで作られたパッチがそれぞれ下記バージョンで取り込まれている。

github.com

github.com

ので、上記以上にLaravelをバージョンアップすることでComposer 2でも動くようになる。

検証

パッチが適用される前のlaravel/framework v7.5.2では動かない

bash-4.2# composer --version
Composer version 2.1.12 2021-11-09 16:02:04

bash-4.2# composer require laravel/framework:7.5.2
./composer.json has been updated
...snip...
> @php artisan package:discover --ansi

In PackageManifest.php line 131:

  Undefined index: name


Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1

パッチが適用された後のaravel/framework v7.6.0なら動く

bash-4.2# composer --version
Composer version 2.1.12 2021-11-09 16:02:04

bash-4.2# composer require laravel/framework:7.6.0
./composer.json has been updated
...snip...
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: fruitcake/laravel-cors
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
69 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

bash-4.2# echo $?
0

🎉

ISUCON11予選 31位で惜しくも本戦出場ならず〜〜〜

今年もISUCON 予選に参加し、527チーム中31位で惜しくも本戦出場なりませんでした、くやしい〜〜〜〜〜〜〜。

f:id:pinkumohikan:20210822220237p:plain

ISUCONとは

Iikanjini Speed Up CONtest。LINE社主催、お題となるWebアプリケーションをガツガツゴリゴリいじくり回して超たくさんの人が同時に使っても死なないチョッパヤ仕様にするソフトウェアエンジニア向けのエクストリームイベント。

isucon.net

今回のオンライン予選は598チーム、1421名の方にご参加いただきました。

チームメンバー紹介

今回はチーム 「牡蠣に当たるときの効果音→カキーン」 として、下記メンバーで参加しました。

cureseven

  • アプリの改修頑張った
  • 練習熱心
  • すぐ歌い出す

shiningcureseven.hatenablog.com

nekodaisuki

  • 猫がだいすき
  • インフラがちゃがちゃマン
  • SQLの魔術師

sinnderu.hatenablog.com

pinkumohikan

  • 色々指図するマン
  • やったこととスコアをIssueに書かないとキレる担当

主な施策

やったことの全容↓ github.com

1. index追加

Slow query logを見ながらindexが必要そうなところへペタペタ。 降順インデックスの罠は 進研ゼミ ISUUMOでやったやつ。

2. インフラ構成の変更

最初は1台のインスタンスに全盛りで、CPUをnginx, app, dbで食い合っていたのでDBサーバを別インスタンスへお引越し。 MariaDBには慣れてないからMySQL 5.7に変更、後に降順インデックス使いたかったのでMySQL 8へ。

最終的にはappサーバのCPUがフィーバーしていたのでappだけのサーバも用意して下記のような構成に。

サーバ 役割
1 nginx, varnish, app
2 app
3 DB

3. dropProbabilityの調整

チューニングが進み、リソースに余裕が出てきたタイミングでいじいじ。 5%刻みで設定変えてベンチ回して、最もスコアが良かった 0.95 に設定。

ただこれは13:30に設定して以降ずっとそのままだったのである程度チューニングが進んだ段階で見直したほうが良かったと反省。

4. lastなIsuConditionをメモリに持たせる改修

最新のIsuConditionを取得しているところやN+1があり、クエリで一括取得するようにしても良いけどDB負荷高めだったのと最新レコードだけならデータ量的にも行けそうだったのでアプリのメモリに持たせる方向で解決。

結構重要な仕事を無事成し遂げられたcureseven氏に拍手。ベースとなるデータを作る部分ではnekodaisuki氏のSQL力がいかんなく発揮された。

5. リバースプロキシでのコンテンツキャッシュ

最初はnginxのproxy cacheでキャッシュしてたけど最小キャッシュ時間が1sなのでタイミングによって鮮度古いよエラーが出ちゃう問題があり、途中からvarnishを挟んだ。

が、varnishは全然練習してなかったので手こずってた模様。自分も最後にvarnish触ったのは7年前とかなのでnekodaisuki氏に血を吐いてもらった。

振り返り

一言で表すと、去年と比べてチームとしても個人としても良い感じに進められ、チームビルディングや練習の甲斐あって成長が感じられる良い回でした。

競技中は終始良い雰囲気で進められたし、みんながしっかりログを見ながら「この辺が遅そう」みたいな当たりを付けながら進められていたので来年は誰がリーダーになってもやっていけそう。スコアも頻繁にリーダーボードに乗るぐらいには健闘していた。

去年本戦に出場できたのはまぐれじゃないぞと証明するため今年もチームで10回ぐらい練習会をしたものの、最終スコアは92336点 31位でわずかに25位に届かず予選敗退。 5万点ぐらい足りなかったら実力不足だと割り切れるけど予選通過ラインが106094点ぽく、競技終盤のスコアは10.7万ぐらいだったのでワンチャン届いていたなーと思えてしまって大変悔しい。

f:id:pinkumohikan:20210823010925p:plain

とは言え運も実力の内、むしろ運に左右されない強靭なスコアが必要。また来年がんばるぞ。

今回の問題も大変良い問題でした。 大きなボトルネックがドーンと構えている感じではなく、改善すべきポイントが各所にあって改善のたびに着実にスコアが伸びてコツコツとスコアを積み上げていけるタイプの問題。ベテランはもちろん、ISUCON初心者でも楽しめる課題だったと思います。ISUCON入門イベントとかに使えそう。

来年も対戦宜しくお願いします!