NGW開発BLOG
← 記事一覧へ
·#069

速いマシンは、優しい嘘をつく非力なLinuxがあぶり出した遅さを、母艦のMacごと直す

新しく建てたLinuxで、ホーム画面の起動に6秒かかった。RAMの潤沢なMacでは、この遅さが一度も表に出たことはない。非力な1台があぶり出したのは、何年も全職員が少しずつ払い続けていた全表スキャンだった。Linuxで見つけて直した手を、そのままMacへ持ち帰る。速いマシンほど、遅さを上手に隠す。

速い1台が、ずっと隠していた

先日、NGW に初めての Linux の1台を建てた。次の10年に備えた土台だ。その上で本体を回し始めた。ログイン直後に出るホーム画面が、起動に数秒かかる。あるテスト用の ID で入ると、画面が出そろうまで5秒を超える。

奇妙だったのは、この遅さを、いつもの Mac では一度も見たことがなかった点だ。RAM の潤沢な機体である。同じコード、同じデータの写し。Mac では一瞬で出る画面が、非力な Linux の仮想マシンでは5秒待たされる。

「非力なマシンだから遅い」で片づけたくなる。実際そう見える。だがそれで納得すると、新しいハードを入れれば消える問題ということになる。本当にそうなのか。推測ではなく実測で詰めることにした。

最初の容疑者は、犯人ではなかった

画面が重いとき、目はまず派手なところに行く。読み込んでいる部品の量。通信の往復。いくつか疑って手当てもした。体感は変わらない。

変わらないという事実が、次の手がかりだった。遅さは画面の描画やダウンロードではなく、サーバの処理の中にある。 リクエスト1本ごとに「サーバ側で何秒かかったか」を記録する仕掛けを一時的に入れ、実際にブラウザからログインしてもらった。

数字ははっきりしていた。ホーム画面の初期化リクエストが、サーバの中だけで6秒。私が手元で同じ処理を叩くと1秒未満だ。本物のログインでは6倍かかる。この差の中に犯人がいる。

犯人は、関数に包まれた1行

追っていくと、ホーム画面の初期化が毎回投げているクエリに行き当たった。ざっくり書くとこうだ。

WHERE DATE_FORMAT(掲載日, '%Y-%m-%d') = '2026-07-19'

掲載日が今日のものを探しているだけ。素直な1行に見える。この1行が致命的だった。

列を関数で包むと、その列に索引が張ってあっても索引は使えない。 データベースは加工後の値で探せない。全行を1件ずつ計算し直して照合するしかない。この表は約26万行。毎回26万行を最初から最後まで舐めて、条件に合う7行を拾っていた。

このクエリは、ホーム画面の初期化と、回覧タブの切り替えのたびに走る。画面のあちこちで26万行の空回りが繰り返されていた。

直し方は2つで1組だ。まず列を関数で包むのをやめる。日付の列なら、そのまま等値で比べれば同じ意味になり、索引が効く形になる。そのうえで条件に使う列に索引を張る。両方そろって初めて効く。片方だけでは空回りは止まらない。

索引を張った瞬間、そのクエリは 0.96秒から0.001秒になった。26万行の全表スキャンが、7行だけを指す索引参照に変わった。

それでも、まだ遅かった

1つ目の犯人を捕まえた。初期化は6秒から4秒台に落ちた。劇的だ。まだ4秒ある。

実測を続けた。今度は回覧板の一覧を組み立てる処理が、1本あたり0.6秒食っていた。しかも初期化は、表示されるのは1枚だけなのに、隠れているタブの分まで先に5枚ぶん作っていた。先読みをやめ、開いたときに読む方式に寄せた。4秒台が2秒台へ。

残った2秒の芯は、根が深かった。回覧の一覧は、2つの巨大な表——掲示そのものと、その宛先が誰かを持つ表——を貼り合わせたビュー越しに引いていた。宛先の表は約470万行。ある人ぶんに絞ると数千〜1万数千行になる。それを投稿時刻の順に並べ替えてから先頭だけ取る。

ここに罠があった。並べ替えに使う投稿時刻の列は貼り合わせた片方の表にあり、絞り込みに使う列はもう片方の表にある。どう索引を足しても、ビューを経由する限り、並べ替えのたびに一時表を作って全行をソートし直す。 索引の効く並び順に、ビューの壁が届かない。

答えは、ビューをやめて2つの基本表を自分で直接つなぐことだった。並び順は投稿時刻から連番の ID に変える。連番の ID は投稿順とほぼ完全に一致するので、表示はほとんど変わらない。その列には索引が効く。ソートは索引を逆からたどるだけで済み、一時表もソートも消える。

一覧を引く時間は、1.1秒から0.008秒になった。並べ替えのために毎回1万行を積み直していたのが、索引の上を後ろから数十件なぞるだけになった。

ここで確かめておくことがあった。重いのは、そのテスト用 ID が特殊なだけではないのか。調べると逆だった。宛先の表を多く抱える人は珍しくない。平均で1人あたり9千行あまり。7割の職員が6千行を超えていた。 この2秒はテスト用 ID の事故ではない。全職員が毎朝のログインで静かに払い続けていた税だった。直す価値はそこにある。

最終的に、ホーム画面の初期化は6.2秒からおよそ1秒。仕上げにパネルの集計も同じ要領で基本表へ移して、約0.15秒になった。

そして、Mac へ持ち帰る

Linux で見つけて直した手を、次は Mac に入れる番だった。実際に団体で動いているのと同じ構成である。放っておけば、こちらにも同じ遅さが眠ったまま残る。

単純な貼り付けでは済まなかった。2つの環境は同じ根から育ちながら細部が食い違う。差分をそのまま当てるのではなく、同じ意図の場所を探して、その環境の書き方に合わせて直す。ある関数は Linux 側では素朴なのに、Mac 側では1枚で全部を組み立てていた。Linux 向けの整理はそのままでは当てはまらない。整理の形は変え、遅さの芯——ビュー越しの全表ソート——を潰す一点だけを同じにした。

今度は当てる前に手元で確かめられた。Mac には、いちばんデータの重い団体とほぼ同じ量の写しがある。書き換え前と後で返ってくる中身が1件残らず一致することを突き合わせ、実行計画を覗いて一時表とソートが消えていることを確認してから、コードを確定させた。推測で当てて祈るのではない。同じデータで先に見てから当てる。

道中、教科書どおりに従うと事故る場所が1つあった。移植の指示書には「重複した索引を2本消せ」とある。ところが動いている DB を見ると、片方は消すと困りそうに見える。念のため配布用の元ダンプ——手を入れる前の正典——を開いた。元の設計には、同じ列に索引が2本ずつ張ってあった。指示書が消せと言っていたのは、その重複の片割れだ。消しても、もう1本が残る。

動いている DB は、既に誰かが片方を整理した後の姿だった。それだけを見て「消すな」と早合点しかけていた。いま動いている状態ではなく、手を入れる前の正典を確かめる。それで判断が逆転した。

速いマシンは、優しい嘘をつく

この1件でいちばん腑に落ちたのは、ここだ。

RAM の潤沢なマシンは、悪いクエリを上手に隠す。 26万行の表がまるごとメモリに乗れば、全表スキャンでも一瞬で終わる。だから Mac では、この遅さは何年も表に出なかった。速いから気づけなかった。速いことが遅さを覆い隠していた。

非力な Linux の1台は、それを隠せなかった。表がキャッシュに乗り切らない。悪いクエリが悪いクエリのまま、6.2秒という正直な数字で出てきた。「非力なマシンだから遅い」と見えたものは、正直なマシンが、隠れていた欠陥を見せてくれただった。

新しいハードを入れれば消える問題ではなかった。欠陥はずっとそこにあり、全職員が毎朝ほんの少しずつ払っていた。

……しかし考えてみれば、私はこの数年、ホーム画面が出るまでの一瞬を「まあこんなものだろう」と思って眺めていたわけである。毎朝である。数百人ぶんである。速いマシンに嘘をつかれていたのは、誰よりも私だった。非力な1台は、足を引っ張る厄介者ではなく、嘘をつかない証人だった。おかげで、速いはずの Mac まで本当に速くなった。

AIとの協働作業での学び

  • 推測をやめて、実測する。 「非力だから遅い」「見た目が派手だから重い」。それらしい説明はいくつも立つ。実測はまったく別の1行を指した。もっともらしさは証拠ではない。
  • 列を関数で包むと、索引は目をつぶる。 DATE_FORMAT(列)=値 のような素直な1行が、静かに全表スキャンを呼ぶ。加工は値の側に寄せて、列は裸のまま比べる。
  • ビューは便利だが、並べ替えの索引までは通さないことがある。 別の表の列で並べ替えている限り、毎回ソートし直す。芯を速くしたいなら、ビューを降りて基本表を直につなぎ、索引の効く列で並べる。
  • 移植は、貼り付けではなく翻訳。 同じ根でも環境は食い違う。差分をそのまま当てず、同じ意図の場所を、その環境の言葉で書き直す。当てる前に、同じデータで結果と実行計画を確かめる。
  • いま動いている姿ではなく、正典を見る。 誰かが途中まで整理した後の状態だけで判断すると、逆の結論に転ぶ。手を入れる前の原本が本当の設計を教える。
  • 速いマシンは、優しい嘘をつく。 潤沢な資源は欠陥を覆い隠す。非力な1台の正直な数字は、厄介ごとではなく贈り物だった。