仕組みから学ぶ Web

データ / DB 設計

正しく保ち、速く引く

テーブル設計は「同じ事実を2か所に書かない」ことから始まり、インデックスは「読む行を減らす」ためにある。そしてトランザクションの分離レベルは、同時に動く処理がお互いをどこまで見てよいかの取り決め。遅いクエリの原因は、たいていこの3つのどれか。

読み終わると、これに答えられるようになります

  • ?複合インデックスの列の順番はなぜ重要なのか
  • ?READ COMMITTED で起きて REPEATABLE READ で起きない異常は何か
  • ?件数が増えたら急に遅くなったクエリの、最初に見る場所はどこか
基礎14分

正規化 — 同じ事実を2か所に書かない

正規化は理論ではなく「更新時に矛盾が起きない形」を作る作業。どこまで正規化し、どこで意図的に崩すか。

商品名を注文テーブルにも持たせた結果、同じ商品なのに表記が3種類に分裂した

応用16分

インデックス — 読む行を減らすための索引

B-tree が何をしているかと、複合インデックスの列順がなぜ効くのか。張れば速くなるものではない。

遅いクエリに片っ端からインデックスを張り、書き込みが3倍遅くなった

発展16分

トランザクションと分離レベル — 同時に動く処理が互いをどこまで見てよいか

在庫が二重に引かれる、残高がずれる。同時実行の事故は、分離レベルの選択と設計の両方で決まる。

在庫チェックと在庫更新の間に別の注文が入り、在庫1個の商品が2人に売れた

応用13分

N+1 問題 — 1回で済むはずの問い合わせが100回になる

ORM の便利さが隠してしまう問い合わせ回数。発見の仕方と、対処の選択肢を使い分ける。

1件あたり2msのクエリが200回走り、一覧ページの表示に4秒かかっていた

発展15分

実行計画 — DB が何をしようとしているかを読む

推測でチューニングしない。EXPLAIN の読み方と、見積もりと実測のズレが何を意味するか。

統計情報が古く、インデックスがあるのにフルスキャンが選ばれ続けていた

応用15分

RDB と NoSQL — 問い合わせの形を、いつ決めるか

性能の比較ではなく「アクセスパターンがもう固まっているか、まだ動くか」で選ぶ。後から取り方を増やしたくなったとき、何が起きるかの違い。

取り方が固まる前に NoSQL を選び、後から来た検索要件が全件走査になった

応用15分

マイグレーション — 流した瞬間に、書き込みが全部待つ

列を1つ足すだけの変更が、本番で11分サイトを止める。行数ではなく、同時に走っているトランザクションの長さが効く。

列を1つ足すだけの変更で、書き込みが11分間詰まってサイトが止まった

応用14分

論理削除と物理削除 — 「消した」を、すべてのクエリが覚えていられるか

退会した会員にキャンペーンメールが届いた。論理削除は、削除を DB の操作から、すべてのクエリが守る約束に変える。

退会したはずの会員4,000人に、キャンペーンメールが届いた

応用15分

コネクションプール — 増やしたら、もっと詰まった

DB の接続が足りないと言われてプールを増やしたら、さらに詰まった。接続の総数は「1台あたり × 台数」で、サーバーレスでは台数が勝手に増える。

キャンペーン開始直後に DB の接続が上限に達し、プールを倍にしたらさらに悪化した

応用15分

ID の設計 — 連番を外に出した日から、数えられてしまう

注文番号を連番にしていた。競合が毎日1件注文して番号の差を見れば、1日の注文数が分かる。URL に出す ID は、内部の事情を外に漏らす。

注文番号が連番だったため、競合に1日の注文件数を数えられていた

応用15分

全文検索 — LIKE '%...%' が効かなくなる日

商品が5万件を超えたあたりから、検索が3秒かかるようになった。インデックスは張ってある。しかし前方一致でない検索には、効かない。

商品が5万件を超えたら検索が3秒かかるようになった。インデックスは張ってあった