さて今年も残すところあと3週間。11月末にISUCON13がありまして、その直後からシンガポールに5泊6日で遊びにいってた関係でISUCONのブログを書くのがすっかり遅くなってしまいました。今年のチームメイトは… いません! ISUCON9以来の一人参加です。チーム名はinit Sでした。昔からLinuxをこねくり回して壊していた人にはおなじみのシングルユーザーモードに入るコマンドから取った名前です。acidlemon is entering single user mode. ということです。
ISUCON9のときは「いやー一人でやるのは明らかに手が足りなすぎてしんどすぎる。もう一人ではやらない」って思ってた気がするんですが(実際点数も全然伸びた記憶がない)、人は喉元過ぎれば熱さを忘れる生き物ということでまたもや一人で出てしまいました。
結果から言うと、最終スコアは51172ということで上位30位ラインのボーダー付近だったのですが、最後のこのスコアを出すためにいれたtagsのキャッシュがちょっとミスっており再起動後の試験(目視確認)でNGになる感じで失格という感じでした。まぁ、目視確認やってもらえるレベルのスコアが出てたという点ではそれなりに健闘できたかなというところです。
一人で出るとなにが大変かというと、とにかくチームメイトに組長がいないというのに尽きます。組長いればアカウントつくったりhosts設定したりデプロイ設定したり細かいところキャッシュしたり全部やってもらえる(もちろん自分でやるよりも手が早い)のでデカイボトルネックに集中できるんだよな~ というところなんですが、今回はいつの間にかアドバイザーで運営側に入ってたようなのでしかたない。まこぴーさんもそーだいさんと組んでるし、ということで、初動なにするかとか組長が普段やってくれていることを自分で出来るようにする訓練だけやっておくことにしました。
さくらのクラウドクーポンを作ってISUCON11予選の環境をつくり、ちゃんとインスタンス3台+ベンチ1台を用意して、そこから初動の訓練をちょろっとだけ数日前からやりました。
apt install
で色々入れる(dstat, netdata, percona-toolkit, net-toolsなど)アプリをどう高速かするかとかは練習してもしかたがないので、ひとまずお決まりの頻出インフラ系タスクを当日悩まないように復習したという感じですね。
あとはisucon13リポジトリを作ってお手元にcloneしておいて、当日を迎えました。
まずは初動…ということで、事前に訓練したサーバのセットアップなどなどから作業開始。上に書いたいろいろの他に
sudo dd if=/dev/nvme0n1 of=/dev/null bs=1M
/initialize
を1回isu03に向けたりしたが、後にinit.shみたらちゃんとホスト指定で初期化できることが分かったので戻した10_schema.sql
を流し込んでいなかったので、DROP TABLE IF EXISTSしつつスキーマを毎回initializeで流し込み直すようにするなどをやりつつ、初期のpt-query-digestとかalpを眺めてサクッと張れそうなINDEXを張りました。
最初から3台構成でデプロイできるようにする前提で下準備をしたのでまぁまぁ手間取りつつ、さっそく高速化していきます。
まずは、DBにアイコンをバイナリで突っ込んでいる postIconHandler
がみるからにヤバそうなのでファイルに書くように対処していきます。
rm -rf ../img/user*.jpg
を仕込んでちゃんと消す 02ebcbbdここで、sha256毎回出してるのも無駄じゃろということで、ファイル名にsha256を含めて保存する 171732e8 という小技を使いました。アイコン周りは1時間くらいかけて一旦どうにかなりました。
この手の画像がDBに入る問題はISUCONだと頻出問題な感じですが、今回優しいなあとおもったのは初期データの時点では icons
テーブルがなんと空っぽということで、初期状態のDBから画像データを吸い出してstatic fileのディレクトリに置き直す…というちょっと面倒な作業が発生しなかったというところです。
アイコンが片付いたところで10000点を超えました。
アイコン周りが片付いたところで、DBのヤバそうなクエリが時間使ってるクエリの上位に出てきたので、ちょっとずつそれに対処していきます。まずはstatsから。
このstatsは明らかにループクエリなんですが、usersのJOINをやめるだけでちょっと負荷がさがるので、次にいきます。次に手をつけたのはダークモード関連です。13:30くらいからベンチマーカーが不調になりましたが、ひとまずこれくらいはサクッといけるはずということで動作確認なしで投入していきました。
update users JOIN themes t on t.user_id = users.id SET users.dark_mode = t.dark_mode;
を入れて初期データのthemesのデータをusersテーブルに反映 4337869dおにぎりを食べながらレギュレーションを読んでいると、アイコンについてはGETするときにConditional GETしていて別のAPIで返しておいたicon_hash
と変わってなければ304返すことが出来る というのがあるのでそれに対応しました dc1a2e88。で、これでOKかなとおもってnginxのアクセスログを確認しましたが304を返している形跡がなく、なんでやねんとおもったらダブルクォーテーションで囲まれているのでそれは取り除く必要がある、というやつでした 30354bcf 。ETag的なやつだから囲まれていた…ということですかね。
sqlxは普段ほとんど使わないのでstructに詰めるところを書くのが苦手なんですが、そうも言っていられないということでインラインにstructを定義してN+1クエリを解消してゴリッと1クエリで全部とるようにし、ランキングは元のロジックの集計するように修正しました 0ebf65ba。ハマることなくこれが一発でうごいたので結構自分で自分を褒めています。
この辺まで来たところで、PowerDNSの負荷もだいぶ上がってきたので、いろいろ手を打ちました。
/etc/powerdns/pdns.d/gmysql-host.conf
に設定があるので gmysql-host=192.168.0.12
に書き換えalter table records add index idx_records_name (name);
PowerDNSを1台目のままにしてあった理由はユーザ追加時に pdnsutil add-record
というコマンドを打っているからなので、これもAPIアクセスに変更しました 323293e1。これで理論上はいい感じにプロセスを別サーバに分離できそうでしたが、pdnsのAPIがlocalhostからしかいけなさそうなのでbindするアドレス変更とかまで調べる時間が惜しいのでDBだけ分離したまま1台目にプロセスを置きました。
このほかにもPowerDNSの設定で cache-ttl=90
と query-cache-ttl=90
あたりをいれたり、レコード追加するときのTTLも0じゃなくしたりなどの指定を入れてました。この辺をやっていくと、
という感じになり、WebAppとpdnsが同居している弊害が出始めます。
明らかにまだDB側にヤバイクエリ飛んでいるのにWebApp側のCPUがボトルネックになっているのはマズい、ということで、色々小手先の変更を入れて負荷を下げていきます。
ここまでやってもあまり有効打にならないということで、あえてDB側に負荷が寄るように画像のsha256のキャッシュメカニズムをファイル名からusersテーブルの追加カラムに変更 13c2b2b7というのをやったところ、WebApp側の負荷がガッと下がってまた3台目のisupipe MySQLにボトルネックが移りました。ここでスコアがゴリっと上がったので、「Globでファイル名リスティングするの重かったか~~」となりました。これで17000点くらいから30000点くらいまであがりました。
これで、DNSとアイコンがきちんと片付いた状態になったので、ここからまたisupipe DBに飛んでいるクエリをpt-query-digestで見ていって、足りないINDEXをポチポチ追加していきました。
で、ここまでやったところで17時半、38000点くらい。もうちょっと一押ししたいけどslowlog的にホットになっているもう1個のstatsのN+1クエリを潰している時間はない…ということで、この後に及んで更新がまったくない事に気付いたtagsをオンメモリキャッシュすることにしました。
なぜか何回かFailして、一部キャッシュするのを諦めるとなんとかベンチが通り、50000点に乗りました。今回は競技時間中の最終計測スコアが最終スコアになるというレギュレーションなので、netdataをアンインストールしたり各種ログを全部止めたりして2~3回回すと51172点が出たので、ここで作業終了。再起動して動かなくなる要素はないはずなので再起動試験はなし!で18時を迎えました。
で、結果発表のあとにインスタンスのログをみると、なんか計測してそうな時間に502エラーが出ている…。panicしているところのログをみていくと、「EC2が再起動されたあとに /initialize を呼ばずにtagsを読むとキャッシュに何ものってないのでOut Of Boundsなエラーがでる」というバグを最後に仕込んでしまったことがわかり、上位チームに対する事後整合性チェックでFail扱いになってしまいました。
51,172 init S (再起動後のデータチェックで不整合があったため)
競技が終わった後に、感想戦でtagsはそもそもDBから読むまでもなくGoのファイルにsliceでハードコードしてもいけてしまう、ということがわかり、いやーこれはもったいないことしたな~~ と思いました。そこちゃんと出来ていればギリギリ30位以内に入れたスコアなので、だいぶ惜しかったという感じです。まぁこの辺レビューとかで担保できなかったのは、一人でやっていることのつらさですよね。
Failで終わったわけですが、それにしても一人で特別賞ラインの5万点くらいいくのはまぁがんばったほうなんじゃないか…と思いました。ベンチマークが50分くらい止まったり、その後もキューイングの待たされ方にムラがあったりはしましたが、自分はそれによって大きく時間を無駄にしたりはしなかったのでその点はよかったなというところです。
あとはアレですね、アイコンのハッシュをファイル名に入れるっていうのはちょっとよくばりすぎましたね。最初からシュッとusersテーブルにいれておけばよかったです。その辺最初からusersにいれとけばstatsのN+1バラしはもうちょっと進んだはずなので、あと2~3万点くらいは上乗せできたのではないか…と思いました。そのほか色々思ったのは
ということで、今年のISUCONはおわり! それではまたお会いしましょう~
]]>ごく稀に書くお役立ち情報シリーズです。
というお話です。
最近うちの会社にもSSOできるIdPが導入されたのでAWSもSSOできるようにする流れが来ています。それに先立ち技術調査…というわけでもないですけど、せっかくだし自分の個人のAWSアカウントもSSO出来るようにするか〜ということで、個人のGoogle Workspace(無償版G Suiteで使ってたやつ)をIdPにしてAWS SSOを設定しました。ちなみにAWS SSOは最近名前が変わってAWS IAM Identity Centerになりましたが、この記事ではとりあえず略して(馴染みのある名前で)AWS SSOと呼ぶことにします。
さて、AWS SSOを設定してGoogleアカウントでログインできるようにしたのはいいとして、そもそも元々自分の個人のAWSアカウントとGoogle WorkspaceはSAML連携の設定をしていたので単純にAWS SSOでAWS Console入れるようにしただけだとむしろクリック数が増えてて旨味がありません。
じゃあなんでAWS SSOを設定したかというと、AWS CLIでSSOログインできるようになって、そうするとお手元にIAM Userのアクセスキーなどを持たなくてよくなるというところが主な狙いです。セキュリティの向上ですね。必要に応じて aws sso login
したときに初めて有効期限つきのクレデンシャルがふってくるので、万が一なにかがどうにかなっちゃったとしても、クレデンシャルに有効期限がついていれば被害の軽減に役立ちます。
AWS CLIのSSO設定は、 aws configure sso
とかをやればいいのでドキュメントを読みながら設定すればサクッといけます。一通り設定したあとは、SSOセッションの有効期限を調整して使い始めればOKです。私はあまり短すぎてもイライラするので、18時間に設定して aws sso login
したらその日いっぱいは再ログインしなくても使えるみたいな感じにしています。
これで必要に応じて aws sso login
すればブラウザが開いてIdPで認証をし、ブラウザでAllowをポチっとおせばクレデンシャルが降ってくる感じになりました。便利ですね。これでもうIAM Userのアクセスキーはいらん! ということで私はこれの設定をしたら速効でアクセスキー無効にしました。
ところで、私の環境依存の話ではあるのですが、このフローにはちょっと不満があります。AWS CLIが開くブラウザはデフォルトブラウザで、デフォルトブラウザに複数のGoogleアカウントでログインしていると、毎回Googleアカウントを選択しないといけないんですよねぇ。クリック数が1回増えるし、手が滑ってIdP設定してないアカウントをクリックしてしまうと403エラーになるしでいいことがありません。起動するブラウザを指定できれば、1個しかGoogleアカウントを使ってないブラウザを起動してAllowを1回クリックするだけでいいようにできるのですが… というのが今日の本題です。
その辺ググってみると、AWS CLIのGithubリポジトリにそれっぽいissueがあったりもするのですが、GUIセッションがない端末用に --no-browser
フラグを追加するのは対応されましたが自由にブラウザを指定するフラグなどは用意されませんでした。
そのissueには「WSLだと環境変数BROWSERを設定したら指定できるぜ」みたいな事が出てきたりして一体それはどういう仕組みなんだみたいな感じになったのでAWS CLIのソースをたくさん読んでみたところ、AWS CLIがブラウザを起動するのは最終的にPython3のwebbrowserモジュールに任せていることがわかりました。で、このドキュメントを読んでいくと「環境変数 BROWSER が存在する場合はデフォルトのブラウザリストに先立ちこっちを試す」みたいなことが書かれています。そういうことね!
環境変数で起動したいブラウザのパスを設定すればいいのは分かりましたが、わたしがブラウザ起動したかったのはmacOSなので、ちょっと一手間必要です。macOSのコマンドラインからGUIのアプリを起動するにはopenコマンドを使います。Safariを起動するなら open -a Safari
とかになります。で、これをそのまま環境変数BROWSERに設定してもうまく行かないので、結論としてはシェルスクリプトにして、環境変数BROWSERにはそのシェルスクリプトへのパスを設定しましょう、という感じになります。
#!/bin/sh
open -a "Safari" $@
こんな感じのシンプルなシェルスクリプトを作って run_browser.sh
とかのファイル名でどこかに保存しておき、+x
パーミッションを付けておきます。これを環境変数BROWSERに設定してやればOKです。BROWSER=$HOME/run_browser.sh aws sso login
を実行すれば、デフォルトブラウザではなく、ちゃんと run_browser.sh で設定したブラウザでIdPにアクセスしにいくはずです。
個人のAWSアカウントに立ってるEC2にsshするのをssm-agent経由で出来るようにして、EC2を0.0.0.0/0に対して22番ポートあけるのをそもそも辞めたいみたいなことを考えていたので、その辺も叶える~/.ssh/config
をこんな感じで書いています。これは、AWS CLIのpowawaというプロファイルを利用し、SSOセッションが期限切れだったらaws sso loginする、そうじゃなければ普通に aws ssm start-session
する、というようなコマンドラインです。
host my-ec2
user XXXX
hostname i-00XXXXXXXXXX
ProxyCommand sh -c "(aws sts get-caller-identity --profile powawa || BROWSER=$HOME/run_browser.sh aws sso login --profile powawa) && aws ssm start-session target %h --document-name AWS-Start-SSHSession --parameters 'portNumber=%p' --profile powawa"
この設定を書いておいて ssh my-ec2
とやると、
aws sso login
してaws ssm start-session
してssm-agentをつかってProxyCommandにポート転送するという感じでSSH接続が確立できます。体感としてはsshコマンドを実行するとブラウザが立ち上がってAllowするとSSH接続が確立する、という感じになります。~/.ssh/config
の hostname
は %h
に展開されるので、hostname
にEC2のインスタンスIDを書いておくというのがこの設定のチャームポイントです。
今回は本編前日の京都に来ました記事を書いておわりかな~とも思ったのですが、意外とブログ書く時間があったので参加後の記事も手短にお届けします。
4年ぶりのYAPC、ということでオフラインの技術カンファレンス筋が鈍っているというのもありますが、諸事情がいろいろあり今回は交流とかのところはバサッと捨てて時間がとれる限りトークを聞く、というところに集中していくことにしました。
個人的には昔から一貫してマルチトラックのカンファレンスでは「聴き終わったあとにコード書きたくなりそうなやつを聴く」というポリシーのため今回もあんまりエモなトークにはいかずに実践的なテックトークをいろいろ聴きました。
一番最後のやつだけは別にコード書きたくなりそうなやつではないんですが、いまは本業が経営企画兼エンジニアになっているので法律系もカバーしたいということでそちらに行きました。
YAPCはもともとPerlのイベントだけど、もはやPerl「中心」なのでPerlじゃなくてもよき、というところもあって普通に聴いてるだけで手を動かしたくなる話、今後のことを考えたくなる話がいろいろ聴けるのがいいですね。
今回は懇親会がないので人と話してトピックを収集するというよりは本当に自分の周辺知識とか世間の潮流とかをアップデートするための時間として過ごしたという感じで、久々に昔のYAPCに行ったときに似た感じで過ごした感じがします(他の人とほとんど話さず一人で黙々といろいろ聴いたという意味で)。ホントに、自社のブース以外だと会場でお久しぶりな元社員2〜3人と話した以外はほとんど話してないので逆に新鮮という感じになりました。
そういえば、私はいつもメガネかけてるところですが今回はコンタクトで行ったのでマスクしてるとそもそも気付かれないみたいな説もありそうだな、と思いました(逆にぼくもジョーに話しかけられたときに2秒くらい気付かなかった)。単純にオフラインのイベントが数年ぶりというところに加えてみんなマスクつけてるからよく見る人じゃないと全然気付かないみたいなの、結構あったかとおもいます。
京都自体が3年ぶりなのですが、よく行くエリアはそんなに変わらず、いつもの京都だな〜という感じでした。もちろん四条の通りを歩いているといろいろとお店が変わってるなと気付くところもありましたが、もっと全体的な感じでみていくと8割くらいは変わってない感じがしたので、そういう意味では安心してブラブラ歩けました。
京都に行くと2回に1回くらいは八坂神社にいくようにしているので今回も八坂神社に寄りまして、そのまま丸山公園に行ったらしだれ桜がいい感じになっていました。これまで、大体京都に行くのはイベントの関係で5月か12月になることが多いので春の京都はたぶん初だった気がしており、春の京都は混んでそうだからいかない、となりがちな性格なのでこういう機会で春の京都に来られたのはよかったな〜と思いました。
]]>ブログを書くまでがYAPC! ということで、これを書いている午前3時時点ではまだ始まってないんですけども、YAPC::Kyoto 2023のために久々に京都に来ています。
京都には会社の採用関係の仕事で来ることもそれなりに多かったので、新型コロナウィルスが流行するまでは何かしらの理由で毎年のように京都に来ていました。調べてみると最後に京都に来たのは2019年12月だったので、なんと3年ぶりの京都です。久々に四条通とかを歩いて「そういえばこんな感じだった!」と懐かしんでおります。
次のYAPCは京都らしい、という話が出たのはかれこれ遡って2019年だったかと思いますが、新型コロナウィルス流行の影響で2020年3月の開催が中止…いや、延期になって早3年。その間にオンライン開催のYAPCもありましたがついにオフライン開催のYAPCが復活して京都で開催されるということで大変おめでたい気持ちになっております。
わたしは気がつくともう面白法人カヤックに所属して12年目になるんですけども、今回のYAPCはだいぶお久しぶりということで(カヤックにしては珍しく?)手間のかからないスポンサーメニューに留まらず、ブース出したりなどもしております。ざっくりいうとおみくじが引けますのでオフライン参加の方は是非お立ち寄りください。わたしもたまにブースにいる…かもしれません。
わたし自身は諸事情ありましてYAPC本編以外のイベントは前日祭ふくめ何も参加できなさそうな感じになので懇親会的なやつや非公式イベントなどで他の参加者の人たちと話せなさそうのが残念なのですが、本編は朝から夕方くらいまでいる予定ですのでお久しぶりなみなさまの顔を見られるのを楽しみにしております。仕事でPerl書かなくなって久しいのですが、
…ということで、会場でお会いしましょう~
]]>Twitterに書くには文字数が足りないけど公式サイトのブログに書くほどでもない小ネタを供養する記事です。うろ覚えなところもあるのでオフィシャルな記事よりはだいぶ信頼性低めです。
今回の問題って完全にバックエンドはAPIしか提供してなくて、参加者がどんなサービスか知るために提供していたフロントエンドはSPAになってて最初からnginxで配信していました。例年だと静的ファイルはバックエンドが頑張って配信するみたいになってることが多いですが、そもそもベンチマーカーはHTML/CSS/JSには最初の整合性チェックフェーズでしかアクセスしないので、そこに力入れてもらう必要ないから最初からnginxで適切な状態にしてしまおう! という感じで出しました。
SPAなフロントエンドが提供されたことは過去にもありまして、大体Reactが使われていました。今年は私が実装するということで、私が一番手慣れてるVue.js(v3)でやりました。私は普段からNuxt使わずに筋力でルーティング書いちゃうことが多いので、Nuxtはつかってません。
あと、今回の問題はマルチテナントSaaSということで admin.t.isucon.dev はSaaS管理者画面、それ以外のサブドメインはテナント用の画面をだす必要があって、Vueのエントリポイントである main.ts でサブドメイン見てロードするAppのVueコンポーネントとルーター切り分けるという、だいぶパワーのある実装になっています。
if (host.split('.')[0] === 'admin') {
createApp(AdminApp).use(adminRouter).mount("#app")
} else {
createApp(TenantApp).use(tenantRouter).mount("#app")
}
importじゃなくてrequireで動的ロードしろよ説ありますが、まぁこれ動作確認用で提供してるだけなのでパフォーマンスとかはそこまで気にしなくていいから、エイヤーでimportしてしまいました。
これは小ネタオブ小ネタですが、ISUCONのお題アプリって最初はレスポンス返ってくるまでめちゃ遅いので、いくつかのエンドポイントを呼ぶときはLoadingっぽいのを伝えるぐるぐるを入れる必要があります。
実務だとBootstrap Iconsでなんかカワイイローディングアイコンを入れてCSSアニメーションで回すことが多いのですが、今回はフロントエンドの依存モジュール増やしたくない(あまりそこに時間をかけたくない)ということで、まんまるなひらがなを1文字くるくる回すという感じでグルグルを実装しました。
SaaS管理者の画面だとひらがなの「の」が回って、テナント側の画面だとひらがなの「め」が回るようになっています。これはあとでちゃんとしたアイコンに差し替えようと思ってたのですが、出題チーム内でも「の」が回るの新しい、カワイイ、などと好評だったのでそのまま残しました。
最初の参考実装はGoで書いていたのですが、他の言語に移植するときにいろいろと「そのまま移植できない!」が出てきてその言語用にちょっとずつ実装方法が異なっている部分があって、それを簡単にご紹介します。
出題チームが各言語の移植者にお願いしていたこととして、SQLiteの書き込み排他制御には必ずflock(に相当するsyscall)を呼ぶというのがあります。
flockを呼ぶ際、Goではブロッキングモードで呼び出して、ロックが獲得できるまでずっと待つという仕様になっており、ほとんどの言語はブロッキングモードで実装していたのですが、Node.jsだけはシングルプロセスシングルスレッドで動く以上ブロッキングで待ってしまうと全部のリクエスト処理が止まってしまってベンチマークが完走しません。
ということで、Node.jsはflockをノンブロッキングモードで使うようにして、EAGAIN
のときは10msごとにリトライして取れるまで無限にflock取るのを試す、といった実装になっています。
Javaはもっと大変でした。最初はnioパッケージにあるFileChannelを利用してFileLockを取る実装を試してもらっていました。しかしJavadocにも書いてあるとおり別プロセスとの排他制御にはこれが使えるのですがJVM内のマルチスレッドにおける排他制御にはこれが使えません。実際にFileLockで実装したものはベンチが安定せず、Javaだけflockを使うのを諦めました。
代わりにどうしたかというと、普通のJavaのお作法通り、Lockオブジェクトを作ってsynchronized
で保護する、という感じでやっています。
ちなみに「JNIでsyscallでflock(1)
呼ぶ」とか「外部プロセス起動でflock(2)
呼ぶ」とか「古のテクニックとしてmkdirがアトミックになるのを利用してロックをとるという20世紀の技があるんですけど…」のアイディアも出ましたが、まぁそこまではしなくていいんじゃない、ということでsynchronized
に落ち着きました。
Rustではsqlxというcrate(であってるのかな)を使っていたのですが、これはMySQLとSQLiteを両方サポートしていて、これを使ってクエリログを取るときに取れたデータにデータベースエンジンなどを区別する方法がない、ということでGoの参考実装通りの移植ができないということがわかりました。
出題チームで検討した結果、環境変数の名前をちょっと変えつつ、MySQLとSQLite両方でることは許容する、ログの形式も他の言語と揃えられないのでデフォルト設定の状態で出す、という形で落ち着きました。
ちなみにPerlのDBIx::Tracerも似たような感じでDBIのexecuteなどをフックしてコールバックを呼ぶみたいな仕組みのためMySQLとSQLiteが混ざるのですが、こちらはコールバックに$dbh
が来るのでDriverの名前を調べてSQLiteのときだけ出す、が出来ています。
テナント追加APIに、謎のコメントが残されています。
// NOTE: 先にadminDBに書き込まれることでこのAPIの処理中に
// /api/admin/tenants/billingにアクセスされるとエラーになりそう
// ロックなどで対処したほうが良さそう
これは読んで字のごとくで、最初はベンチマークが全然回らないので顕在化しないんですが、APIの回転が良くなってくると、AdminのBilling APIのアクセスがたくさんくるようになりこれで引っかかるようになるというやつがありました。
どういうことかというと、Adminのテナント追加APIでは、テナント情報をMySQLにINSERTして、その後SQLiteのDBファイルを作るという順番で処理していました。Admin Billingを叩くワーカーはテナント追加APIとは別のワーカーとして動いており、並列にアクセスが来る可能性があるため「テナント追加APIでMySQLにINSERTした直後〜SQLiteのDBファイルを作るまでの間」というごくわずかな時間の間にAdmin Billingが叩かれるとSQLiteのファイルを開けなくて500エラーになる、ということです。
…ということで高速化すると謎の500エラーに悩まされる…という状態になることが分かったので、ひとまずコメントを入れてお知らせしていた、という話でした。どうすればよかったかというと、簡単なのはINSERTをトランザクションでちゃんと囲んでSQLiteのDBファイルを作ってからMySQLにコミットするとかですかね。もう1個の方法としては予選の解説に書いた「そもそもDBファイルを作るタイミングを変更する」という手もありました。
…さて、いかがでしたでしょうか。小ネタでした。
]]>