「失敗から学ぶRDBの正しい歩き方」を読みました
勉強記録
今日は「失敗から学ぶRDBの正しい歩き方」という本を読みました。
昨日まで「SQLアンチパターン」を読んでいたのもあって、理解しやすかったです。
なにより、内容がとてもわかりやすい!実際の現場でのやりとりの会話例もあったので、アンチパターンが理解しやすかったです。
<第1章>データベースの迷宮
不適切な名前では、データベースのテーブルの関連性や意図が理解できない。
今後を想定した命名にしないと、後々わかりづらいし他の人も困惑する。
<第2章>失われた事実
履歴の保存をしていないと、過去の事実が失われてしまう。
例えば、商品単価を更新したら、過去の商品単価まで更新されてしまった→過去の売り上げ(事実)が変わってしまう
また、ステータスを持たせてステータスだけを更新するのも、どのような過程があったか失われる。
例えば、商品の発送に関して配送済みになるまでどのような過程があったのか(キャンセルや再発注など)また、それがいつ発生したのかが分からなくなる。
逆に、過程の記録を残さなくてもいい場合には、ステータスを更新しても良い。
・履歴の保存はUPDATEとDELETEしない
常にINSERTまたはSELECT
履歴を残したいので更新や削除は行わない
・履歴の保存はトレードオフである
履歴の保存をすることで、レコードの保存量が増加。→テーブルサイズが増える
検索速度が落ちる
・必要なものだけ履歴を残すことが大事
<第3章>やり過ぎたJOIN
RDBはちゃんと設計して正規化することが大事。正しいデータを取り出すのに必ずJOINが必要になる。
しかし、正規化を正しく利用した設計になってないと、ボトルネックになってしまう。
JOINは必要最低限にして、INDEXを適切に活用する→重い処理だからJOINを使わない!と安易に考えないように
<第4章>効かないINDEX
INDEXにか効くときと効かないときがあるので、正しく理解してINDEXを貼る。
・設定したINDEXが効かない(使われない)ケース(重要)
-検索結果が多い、全体の件数が少ない場合
-条件にその列を使っていない
-カーディナリティの低い列に対する検索
-あいまいな検索
-統計情報と実際のテーブルで乖離がある場合
※カーディナリティとは、別に格納されているデータの値にどれくらいの種類があるのかいうこと。
例えば、男/女という性別のデータはカーディナリティは低い。逆に誕生日のデータはカーディナリティ高い。
<第5章>フラグの闇
「とりあえず削除フラグ」はよくあるアンチパターン。
クエリが複雑化するし、UNIQUE制約が使えないのでデータの重複を防ぐことができない。
カーディナリティも低く、第4章でもみたように、検索のときにINDEXが効かない
・削除済みの事実のみを保存するテーブルを作成する
元のテーブルからは物理削除して、削除済みのためのテーブルに移す
・「状態」を持たせるのは必ずしもだめなのか
絶対にダメというわけではない。
対象のテーブルが小さくてINDEXがいらない場合や、頻繁にJOINの対象になることがないときなどはテーブルに状態を持たせてもよい。
<第6章>ソートの依存
ORDER BYはパフォーマンス面でRDBの苦手な分野。処理が遅くて障害につながるケースも。
・WHEREとORDER BY で指定するカラムにはINDEXを貼るべき
INDEXを貼ることでパフォーマンスが劇的に向上
ORDER BY の前にWHEREでデータを絞りこむことも大事
<第7章>隠された状態
RDBに状態を持たせることは大変危険。
意味を含んだIDは使ってはいけない(「論理ID」「スマートカラム」ともいう)
例えば、IDの先頭が9なら管理者で1なら一般ユーザーだとすること。
意味を含んだIDは、コードやデータから見えない状態だし、制約が失われる。
・似たようなアンチパターン
<第8章>JSONの甘い罠
リレーショナルデータモデルはスキーマレスな設計と相性が悪い。
JSONデータ型をむやみに使ってはいけない→ORMが使えない、データの整合性が保てない
・JSONデータ型を使ってもいいとき
非正規化でどうしても使わないといけなくなった場合
<第9章>強過ぎる制約
RDBMSを使う理由はACIDを担保するこが最大のメリット。
しかし、制約は使い過ぎるとデメリットにもなる。
・外部キー制約が生み出すデッドロック
デッドロックとはお互いの処理が終わるのを待っていて、どっちも先に進めない状態になっていること(お互いにロックがかかっているからロックが終わらない)
<第10章>転んだ後のバックアップ
バックアップは必ずきちんと取るべき。よくあるのは1日1回深夜にバックアップをとっていたりする。
例えばWHEREをつけずにUPDATEしてしまった場合など、バックアップは絶対必要!
バックアップにもルールを設けてバックアップ設計をちゃんとする
<第11章>見られないエラーログ
エラーログの重要性を理解して、ちゃんと見る。
エラーログが見れないまたは見づらい場合は、見やすく加工されていないから。
エラーログの設定をちゃんとみやすくして監視、エラーがあれば通知されるようにする。
<第12章>監視されないデータベース
DBをちゃんと監視しよう、というシステム監視に関する話。
サーバーがどんな状態なのか、24時間監視しなければならない。エラーを検知して通知するようにする。
・何がいけないのかモニタリングする
(OS側)
・ディスクI/O
・ネットワークトラフィック
・CPU利用率
・メモリ利用率
(RDBMS側)
・SQLの実行量
・読み込まれているレコードの量
・インデックスヒット率
・デッドロックの可否
・テンポラリファイルの作成の有無
・ロックの量と時間
※テンポラリファイルとは、一時的に作られた(作った)ファイルのこと
<第13章>知らないロック
<第14章>ロックの功罪
トランザクション分離レベルについて。
安全性とパフォーマンスはトレードオフの関係である。
ACIDのIsolationは分離性、独立性といった意味。Isolationを完全に担保するには、直列処理で1件ずつの処理がいいが、それだと処理が遅くなる。
・トランザクション分離レベルには4種類ある(重要)
(1) read uncommitted
(2) read committed
(3) repeatable read
(4) serializable
下に行くほど並列度が低くなる。serializableは完全な直列処理(直列処理であれば安全だが遅い、並列処理であれば安全性落ちるが速い)
MySQLでは、デフォルトで(3)repeatable read になっている。
トランザクション分離レベルによって、安全性を高めることもできるし、逆にパフォーマンスを高めることもできる。
<第15章>簡単過ぎる不整合
最も簡単にデータを壊す方法は非正規化である。
パフォーマンスを向上や、デッドロック回避のために、非正規化したくなるときはある。
安易に非正規化するのではなく、RDBMSだけで解決しようとせずNoSQLなど他の方法も視野にいれて検討することが大事。(正規化がめんどくさい、難しいからといって非正規化するのは論外!)
<第16章>キャッシュ中毒
キャッシュは一度利用したデータを保存しておき、再度同じリクエストがあったときに保存しておいたデータを再利用して計算処理を省略するしくみ。
パフォーマンスの向上が期待できるし、RDBMSの苦手な部分を結果整合性によってカバー。
(RDBMSのロックは悲観ロック。ACIDに準拠してガッチリとしたロックだが、その分遅い。結果整合性はその逆。結果的に整合性が保たれればよいという考え)
しかし、キャッシュにもトラブルはある。
キャッシュの消失や、見えてはいけないデータが見えてしまうなどのトラブルがあるので、キャッシュの特徴を理解して正しく使うことが大事。
・キャッシュの生存期間と更新方法を決める
基本的には時間経過で消えるように、更新/削除される期限を必ず決める
<第17章>複雑なクエリ
複雑なクエリはバグが発生しやすくなるし、メンテしにくい。他の人が読みにくい。
<第18章>ノーチェンジ・コンフィグ
コンフィグファイルをちゃんと知って、設定、管理しようということ。
コンフィグファイルは設定ファイルである。初期設定のままにしておかないこと。
AWSのRDSというデータベースのサービスは、コンフィグファイルが最初からおすすめの設定にしてある。
<第19章>塩漬けのバージョン
いつまでも古いバージョンのRDBMSは使わないようにということ。
サポート中のバージョンならまだいいが、セキュリティサポートが切れたバージョンを使うのは危険。
バージョンアップするには、データベースの停止を伴うし、サービスのメンテ時間が必要とされるので、難しい。
でもセキュリティだけでなく、新しい機能やパフォーマンスの向上などバージョンアップにはたくさんのメリットがある。
<第20章>フレームワーク依存症
フレームワークにあるORM=モデルではないよということ。
読み終えた感想
アンチパターンの内容が非常にわかりやすく書かれているなと感じました。私は実務経験がほぼないですが、そんな私が読んでもわかりやすかったです。
さらに「ロック」や「トランザクション分離レベル」などについては、今日初めて知ったし、この本にも詳しく書いてあるので、読み返して理解を深めようと思います。
次回はNoSQLの基礎について学びます!