2020年の年末から、フィヨルドブートキャンプ内で『達人に学ぶDB設計 徹底指南書』の輪読会をやった。
Slack で輪読会の話が出たときに、DB関係の本を読みたい!と言ったところ、フィヨルドブートキャンプのアドバイザーであるベテランエンジニアの方がメンターをやってくださることになり、開催に至った。
初学者で集まって読むのも楽しく一人で読むよりは知識も深まるが、その分野の知識や経験が豊富な方に参加してもらえたことで、さらに何倍も有意義な会になったと思う。
ものすごく勉強になったのを独り占めするのはもったいないのと、自分の復習のためにも特に印象に残ったところだけでも書き残しておくことにしたい。
学んだこと〜データベースを制する者はシステムを制す
データベースは事実を保存するもの
プログラムと比べ、データはあまり変化しない。というか、そう設計されるべきだ。データ設計(データベース設計)は、プログラム設計に先立って行う。
たとえば、ユーザーの氏名(事実)が変わることはあまりないが、名前の表示の仕方(プログラム)を変えたくなることはよくある。
名簿はフルネームだけど、担当者名のところには苗字だけ表示させたい、とか。
可能な限り NOT NULL 制約をつける
事実を保存する前提に立つと、基本的にNULL
(データが空っぽ)となることはない。
NULL
を許容したくなったときは、データ設計自体を見直した方がよい。
たとえば、以下のような社員テーブルを考える。
社員ID | 姓 | 名 | 年齢 | 部署 |
---|---|---|---|---|
200500123 | 山田 | 太郎 | 45 | 総務 |
201000111 | 高橋 | 花子 | 36 | 広報 |
202100567 | 鈴木 | 一郎 | 20 | ??? |
今年入社したばかりの鈴木さんは、研修後に配属が決定されるので、入社日時点では部署が決まっていない。
NULL
を許容しないとなると、部署が決まるまで鈴木さんを社員テーブルに追加することができない。
こういうときは、このテーブルの設計自体がまずい可能性が高い。
社員テーブルに「部署」を入れる必要があるのか?配属先が変更になったら?複数の部署に配属される可能性はゼロか?
ということを考慮すると、「配属」というイベントが存在していることに気づく。
部署テーブル
部署コード | 部署名 |
---|---|
010 | 総務 |
020 | 広報 |
以下略 | … |
配属テーブル
配属日 | 終了日 | 社員ID(外部キー) | 部署コード(外部キー) |
---|---|---|---|
2005-06-01 | 2011-3-31 | 200100123 | 070 |
2011-04-01 | 2018-03-31 | 200100123 | 050 |
2018-04-01 | 200100123 | 010 | |
2010-06-01 | 2015-03-31 | 20100111 | 030 |
2015-04-01 | 20100111 | 020 |
現在進行中のところの終了日はNULL
を入れたくなるからまた設計がおかしいのか…
と思うかもしれないが、こういうところには最大値を入れておくというテクニックがある。「事実」ではないが、つっこまないで…。
未来に起こる「かもしれない」ことを全て考慮するのは難しく、NULL
を許容することが絶対に悪いということではない。
何かを優先すれば何かが犠牲になる「トレードオフ」の世界なので、部署を未定
で登録しておくケースもあるかもしれない。
何を優先するかによって、考え方・正解がたくさんある世界だという点も大事。
可逆 / 不可逆を考える
上の社員テーブルでは、社員名の姓と名を別々のカラムにした。
データはできるだけ分解した方がいいというのはなんとなくわかるが、姓だけ、名だけでは誰かわからないので意味が失われてしまうのではないか?
カラムを分けるか、テーブルを分割するか考えるときは、可逆的か(元に戻せるか)どうか?を考えてみるとよい。
姓・名を別々のカラムにしておいたとして、フルネームが必要なときは、2つのカラムのデータを単純にくっつければいい(可逆的)。
逆に、氏名という1つのカラムに「山田太郎」と保存していた場合、社員の姓だけが必要になったときに、プログラムで姓だけを取得することは不可能。 絶対にフルネームでしか使わないということであればいいのかもしれない。
ちなみに、「年齢」はあまりよくなさそう。誕生日が来たらいちいち更新するのか?「生年月日」にしておけば、年齢はプログラムで求めることができる。
正規化は絶対ではない
データベースの勉強をすると必ず出てくる「正規化」という言葉。
ざっくりいうと、冗長性(重複)をなくしデータの不整合を防げるように整理すること。
正規化にはいくつか段階があり、正規化を進めれば進めるほどデータ整合性を高められるが、検索性能は劣化する。ここもトレードオフ。
正規化するのが絶対に正しいというわけではなく、パフォーマンスを優先してあえてデータを冗長に保持することもある。
たとえば、Twitter の「いいね!」数は、ある特定のツイートに対し誰が「いいね!」しているかをデータベースに問い合わせて計算すれば求めることができる。ただ、何万もの「いいね!」がつくことも珍しくない Twitter で、一つ一つのツイートに対してそんな処理をやっていると、結果が表示されるのに時間がかかったりデータベースに負荷がかかったりする。
こういう集計データ(サマリデータ)は、計算後の結果を別途保持しておくことによって、パフォーマンスを上げることができる。
トレードオフを理解した上での非正規化は、アンチパターンではない。
ER図をいきなり描くのは難しい
フィヨルドブートキャンプでは、Twitter の ER 図を描くという課題がある。苦戦した過去の自分へのアドバイスをいくつか。
とにかく、まずは具体的なデータを置いてみること。どういう形でもいいから、具体的な例を考えてデータの置き場所を作ってみること。
エクセルのような表を作って、セルに複数のデータが入ったりしてもいいから、とりあえず書いてみる。
その後、正規化したりリレーションシップを考えたりしていく。いきなり ER 図を描くのは難しい。
この本では、2種類の表記方法が紹介されているが、ググってみると、いろんな描き方をしている人がいて、どれが正しいの?となる。
ER図に関しては、厳密にどれが正しいというより、伝わることが一番大事。
主キーの考え方がRailsとは違う
この本を読むときに注意が必要なのが、主キーの考え方。
主キーの考え方には、ナチュラルキー(自然キー)とサロゲートキー(代理キー)というものがある。
参考:「ナチュラルキー」と「サロゲートキー」の違い|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
ナチュラルキーは、事実を(必要に応じ組み合わせて)主キーにするのに対し、サロゲートキーは、事実とは関係のない番号などを人工的に主キーとしてつけるもの。ナチュラルキーの例は、上の社員テーブルでいうと「社員ID」や「部署コード」。これは、プログラムのためにつけたものではなく、現実世界に存在するID(コード)。
この本は、ナチュラルキーを使うべきで、サロゲートキー(代理キー)は推奨しない立場で書かれている。詳しくは本の「8-2 代理キー〜主キーが役に立たないとき」を参照。
一方、Rails はデフォルトでサロゲートキーを使うようになっているので、この立場の違いを知らないと理解が難しいところが多々ある。 自分も1回目に一人で読んだときはそれを知らず、理解できないところが多かった。
現在、現場ではサロゲートキーが使われることも多いようだ。
サロゲートキーは連番か
ちょっと話は逸れるが、サロゲートキーを連番にするかランダムな数字にするかという話題で興味深いツイートを教えてもらったのではっておく。
ベースブレッドのシナモンとメープルがうまいらしいので、ベースブレッドの購入を再開してみる
— ところてん (@tokoroten) January 4, 2021
ベースブレッドのマイページを見たら、注文番号が連番くさいので、一日当たりの注文量を出してみたら、割とえげつない事業成長をしていて、サブスクビジネスのすごさが分かった、今は月商8000万くらいぽい pic.twitter.com/IOukckfiwt
たとえば、ユーザーIDが連番だと、登録した人が「全ユーザーが何人いるか」わかってしまうので、あえてランダムな数字にすることもよくある。
難しい用語にこだわらなくていい
この本では、部分関数従属とか推移的関数従属のように難しい用語がいくつか出てくる。
現場ではあまり使わないようなものもあるので、細かい用語を一つ一つ覚える必要はない。難しい言葉は、ざっくり理解して次出合ったら覚えるぐらいでよさそう。
輪読会の概要
毎週土曜日22時〜23時に Discord で開催し、全13回。
参加者は、多い日は20人弱、少ない日は3人ぐらい。
項目ごとに5〜10分の時間を取って各自読みながら疑問点などを HackMD に書いていき、時間が来たら意見交換を繰り返す流れで進行した。
第7〜8回は、参加者を2チームに分けてモブプロっぽく ER図の作成会をやった。
参加してくれた皆さん、bluerabbit さん本当にありがとうございました。