midnight in a perfect world

webエンジニアのメモ

 「失敗から学ぶRDBの正しい歩き方」を読む。

失敗から学ぶRDBの正しい歩き方 (Software Design plus)

失敗から学ぶRDBの正しい歩き方 (Software Design plus)

 

 テック系のポッドキャスト「ajitofm」で知った曽根さんという人の著書。話が面白かったので読んでみた。

ajito.fmタイトル通り、主にMySQLとPosgreSQLをメインに具体的なバージョンによる挙動の違いなどにも言及しつつ、様々なRDBの設計や実装のアンチパターンを紹介していく本。個人的には実務でOracleしか触ったことないんだけど、内容は十分理解出来るもので学び多かった。「データベースの寿命はアプリケーションより長い」という著者の持論と、彼が実際に遭遇した様々なリアルな現場での経験と痛みを少しでも自分に血肉化して、将来の負債になりにくい(かつ拡張性)DBを作っていけたらなと思いつつ。

章ごとにまとめ。

①「データベースの迷宮」不適切なカラム名ではデータの正当性や意図が理解できず、集計等の業務に生かすことが出来ない。→外部キー制約やCHECK制約などで防御する。または新しいカラムを用意してリファクタリングする。

②「失われた事実」レコードの状態を上書きすると、過去の状態や事実が分からなくなる。結果、払い戻し処理などの業務が出来なくなる。→履歴を保存するのがいいが、レコード量が増大するので、Elasticserchなどの分析サービスに渡すのが吉。

③「やりすぎたJOIN」JOINのアルゴリズムをきちんと理解してないと、インデックスのないテーブルなどを盛りこみまくると処理がむちゃくちゃ遅くなる。→基本はNLJ(Nested Loop Join)という一行ずつループする処理がメジャーだが、インデックスがないとキツいので注意。マテリアライズド・ビューなどを使うと効率的。

④「効かないINDEX」テーブルに設定していたINDEXは、レコード件数やカーディナリティの状態によってINDEXを使わなくなるため、検索が遅くなったりする。→検索結果が20%未満の検索や、数万行以上のレコードでないと効果がない。なので、ユーザの年齢分布などで検索するとタイミングによって分布が変わるため、INDEXを使ったり使わなかったりする。

⑤「フラグの闇」とりあえず状態を表すためにフラグつけとけ、という考えは危険。アプリ側のクエリが複雑になるし、UNIQUE制約が使えないなど、RDBの特性を失ってしまう。→状態を持たせたいなら、小さいテーブルに限る。

⑥「ソートの依存」ORDER BYは適切に使わないとこれまた遅くなる。→RDBMSSQLを評価してクエリを実行する際に評価する順番がある。大量のデータを取りだしてからソートすると処理が重くなるので、WHEREで対象を絞り込むなどしてからソートする方が効率が良い。それか、フロントエンドのデータをREST APIで受け取ってJava Scriptでソートさせて表示させるとか、アプリ側で実装してしまう手もあり。それか、NoSQLやRedisなども適材適所でこういう用途に使うのはあり。

⑦「隠された状態」ユーザIDとして一般を「1001」管理者を「9001」みたいに意味を持たせて登録すると、アプリ側でいちいち同じカラムの値を識別する必要があったりして分かり辛くバグも多くなる。似たような問題設計としてEAV(複数の目的に使われるカラム)と言ったりする。→JSON型などのカラムにつっこむやり方がある。

⑧「JSONの甘い罠」とりあえずJSON型カラムにJSONを突っ込むことはできるが、中身が予想できないし正規化もされないので扱いにくい。Web APIの戻り値など、構成が変わりやすいデータを入れる時などに限定して使う。

⑨「強すぎる制約」データベースを防御するために入れる制約によって、業務に支障が出てしまうことがある(メールアドレスをカラムに登録する際の制約など)。

⑩「転んだ後のバックアップ」ちゃんとリストア訓練しとかないといざという時復旧できないよねという話。→レプリケーションのデメリットを把握しておく。ヒューマンエラーで全テーブルの値を間違えて更新した、とかの場合はそのミスをレプリケートする。だから、きちんとバックアップを取って、さらにステージング環境でリストアするまでを自動化しておくと良い。

⑬「知らないロック」ロックのレベル(排他・共有)と粒度(表・行)を理解し、データを守るための基本的な仕組みを理解する。ギャップロックやネクスキーロックなど、RDBMSごとに振るまいも違うので注意する。

⑭「ロックの功罪」トランザクション分離レベルをきちんと理解する必要あり。データを守りながら並列処理をどのように実装するか考える。ロックを強くするとパフォーマンスが落ちるが、弱めると不整合が生じる。

⑯「キャッシュ中毒」キャッシュを使うとデータ参照は簡単に高速化出来るが、トラブルじにキャッシュデータが悪いのか元データが悪いのかなどの調査がしづらい。→キャッシュのヒット率を計測してちゃんとパフォーマンス向上に意味があるか確認しつつ、アプリ側でキャッシュする方法なども検討する。

⑱「ノーチェンジ・コンフィグ」コンフィグをデフォルトのままにしておくと、メモリの使用量やコネクション数などサーバの能力を活かしきれないのできちんとチューニングする。

⑲「塩漬けのバージョン」変化を恐れてRDBのバージョンアップを先延ばしすると、バージョンアップ時のサービス影響が大きくなるためバージョンアップ自体のハードルが上がっていくのでこまめにバージョンアップすべし。

⑳「フレームワーク依存症」アプリ側の開発フレームワークフレームワークのORMに依存するとテーブル設計がそっちに引っ張られるのでバランスを考えて設計する。PHPSymfonyリポジトリパターンは優秀なので覚えておく。