Skip to main content

概要

データベースとは、アプリケーションのデータを保持し、それについての問いに答えるシステムです。多くのプロダクトで登場するのは4種類で、そのどれを選ぶかがこのページの主題です。 この判断は中身を設計するより前に行うものであり、後続のどの判断よりも覆しにくいものです。変更するには、すべての行を移し、それらに触れていたすべてのクエリを書き直す必要があるからです。 この判断は製品の選択として読まれがちですが、アクセスの形の選択として読むほうが役に立ちます。アプリケーションがそのデータに何を尋ねるのか、その答えはどれだけ整合している必要があるのか、そして最終的にどれだけの量になるのかです。 このページでは、4種類それぞれが得意とすること、種類を横断する2つの軸、リレーショナルデータベースを既定の答えとする理由、2つ目を足す実例、そしてそのときの代償を解説します。

それぞれが得意とすること

種類が決めるのはアクセスの形までです。実際に使う製品は、その種類の中でさらに特定の方向へ特化しており、揮発性のような性質も製品ごとに決まります。

リレーショナルデータベース

データを行と列として保持し、表と表の関連を外部キーとして宣言します。結合を使えば複数の表にまたがる問いに1つのクエリで答えられるため、設計の時点で誰も想定していなかった問いを、後から尋ねることができます。 これに伴う2つの仕組みが、この種類を汎用たらしめています。トランザクションは複数の変更をひとまとまりとして成功または失敗させます。制約はデータが満たすべき条件を宣言し、それを破る書き込みをデータベースが拒否します。 MongoDBやDynamoDBもトランザクションを備えており、他の種類が持てないわけではありません。範囲や制約の表現力を含めて、これらを既定で広く備えているのがリレーショナルデータベースだということです。 想定されていないのは、1台で捌ける範囲を超える書き込み量と、数年分の履歴の集計です。いずれもそれ専用の形をしたデータベースが別にあります。

ドキュメントデータベース

データをJSONのような入れ子構造の文書として保持します。文書ごとに異なる項目を持てるため、スキーマを事前に固定しておく必要がありません。 リレーショナルデータベースとの最大の違いは、一緒に読むものを1つの文書に埋め込める点です。注文とその明細を1つの文書にまとめれば、結合を経ずに1回の読み取りで取得できます。 実装の傾向として、代表的な製品の多くは分散を前提に設計されています。これはモデルが要求するものではなく、単一ノードで動くドキュメントデータベースも、分散するリレーショナルデータベースも存在します。ただし分散する構成を採るなら、分け方はキーで決まるため、どのキーで引くのかを先に決めておく必要があります。 代償は、埋め込んだ値の更新が複数の文書に及ぶことです。商品名を各文書に埋め込めば、名前の変更はそれを含むすべての文書へ届かなければなりません。この形は、どう読むかが先に決まっている場合に力を発揮し、読み方が後から変わる場合には作り直しを要求します。 この種類は、他の3つと選ばれ方が異なります。キーバリューストアやデータウェアハウスがリレーショナルデータベースの隣に置かれるのに対し、ドキュメントデータベースは主データベースそのものの代替として選ばれます。判断はプロダクトの初期に下されることが多く、アクセスの仕方を事前に固定できるか、そして分散を要する規模かで決まります。

キーバリューストア

構造は可能な限り小さく、キーを渡すと値が返るだけです。データベースは値の中身を解釈しないため、その意味づけは完全にアプリケーションの責任になります。 RedisやMemcachedは値をメモリ上に保持するため、1件あたりの読み書きがディスクを介するデータベースより桁違いに速くなります。キーごとに有効期限を設定でき、期限切れの削除をアプリケーションで管理せずデータベース側に任せられます。 ただし、この構造がメモリ上の保持を要求するわけではありません。DynamoDBはキーバリューとドキュメントの両方の性格を持ち、ディスクへ保存して複数の場所へ複製します。ソートキーによる範囲検索も可能で、速度ではなく分散とマネージド運用を目的に選ばれます。上の製品の表が示すとおり、こうした性質は種類ではなく製品ごとに決まります。 適するのはキャッシュ、セッション、レート制限のカウンタ、単純なキューです。キー以外での検索には適しません。全キーを走査するしか手段がないためです。レコードどうしが互いを参照するデータにも適しません。

データウェアハウス

アプリケーションのトランザクション処理(OLTP)から分離され、履歴全体に対する分析処理(OLAP)に答えるためのデータベースです。分析担当者が数年分の履歴を走査しても、利用者が待っているリクエストと資源を奪い合いません。 これを成り立たせる実装として、多くの製品がデータを行ではなく列ごとにまとめて保持します。2つの列を集計するクエリは、表に何列あってもその2列しか読まないため、数億行の集計が現実的な時間で終わります。 同じ設計が、1件単位の読み書きと低いレイテンシを苦手にします。データウェアハウスが答えるのは事業についての問いであり、アプリケーションからのリクエストではありません。

種類を横断する2つの軸

それぞれが得意とすることとは別に、あるデータベースに何を置いてよいかを決める性質が2つあります。1つは置くデータによって決まり、もう1つは製品と構成によって決まります。どちらも種類の表からは読み取れません。

所有か、複製か

そのデータベースを失うことが、事業の記録そのものを失うことなのか、別の場所から作り直すことなのか。

永続的か、揮発的か

プロセスの再起動を越えてデータが残るのか、空の状態で戻ってくるのか。
2つは独立しています。本番のデータベースから同期しているデータウェアハウスは、永続的でありながら所有者ではありません。ディスク上に残り続けますが、失えば元のデータベースから作り直せます。キャッシュを載せたMemcachedは揮発的で、かつ複製を保持しています。 どちらか一方の軸だけでは、何をどこに置くかは決まりません。だからこそ、判断の前に両方を確かめる必要があります。

所有か、複製か

データを所有するデータベースには、耐久性、バックアップ、そして正しさを保つ制約が必要です。失えば、他のどこにも存在しない記録が失われるからです。複製を保持するデータベースは中身を別の場所から受け取るため、失うことは再構築の間の停止であって、損失ではありません。 この違いは種類ではなく、そこに置いたデータで決まります。本番のデータベースから同期しているデータウェアハウスは複製ですが、イベントログを直接送り込んでいるなら、そのデータの所有者はデータウェアハウス自身です。事故が起きやすいのは後者を前者と思い込んだときで、所有しているデータにバックアップも保持期間の設定も用意しないまま運用することになります。

永続的か、揮発的か

揮発性とは、データが再起動を越えて残るかどうかです。これは種類ではなく、製品と、その構成で決まります。 Memcachedは値をメモリ上にだけ保持するため、プロセスを再起動すると中身は空になります。Redisも保持はメモリ上ですが、既定でスナップショットをディスクへ書くため、再起動で失われるのは直近の書き込みに限られます。この差はどちらも同じキーバリューストアという種類の中にあります。 ただし、こうした永続化の仕組みは耐久性の保証というより、再構築にかかる時間を短くするための仕組みと捉えるほうが安全です。 揮発性が、そこに何を置いてよいかを決めます。セッションが失われても、利用者が一度ログインし直せばアプリケーションは自力で復帰します。一方、他のどこにも存在しないカウンタは、失われれば二度と再現できません。その置き場所はデータを所有するデータベースであり、キーバリューストアが持てるのはその複製までです。

既定はリレーショナルデータベース

リレーショナルデータベースから始めることは、保守的な選択という意味ではありません。次の3つの性質が、要件が固まりきる前に選べるデータベースにしています。 クエリはデータを保存したあとに書けます。 リレーショナルデータベースは、そのデータに対する問いではなくデータの構造を軸に整理されているため、誰も想定していなかった問いにも新しいクエリで答えられます。既知のアクセスパターンに最適化されたデータベースは、まずそのパターンが分かっていることを求め、新しい問いはデータの再編成を意味することがあります。 トランザクションが複数の変更をひとまとまりにします。 注文とその決済が両方成功するか両方失敗するかでなければならないとき、トランザクションはそれをそのまま宣言します。2つのデータベースにまたがってこれを再現するには、部分的な失敗をアプリケーション側で扱う必要があり、その作業量は見積もりを誤りやすく、正しさの検証も困難です。 制約が不正なデータを入れさせません。 外部キー、一意性、チェック制約は、バッチ処理や障害対応中に手で実行される文も含め、すべての書き込み経路で評価されます。 ここから導かれるのは、特定の問題がリレーショナルデータベースを除外しない限りはそれを選ぶ、ということです。他の種類は、それでは解けない問題が現れた時点で足します。

実例で見るデータベースの選び方

最も飛ばされやすいのが中央の問いです。張られなかったインデックス、インデックスを辿れるのに全走査しているクエリ、用意されなかったリードレプリカは、いずれも「データベースの種類が違う」ことのせいにされがちな問題を解きます。この問いが「解ける」になる場面と「解けない」になる場面を、2つの実例で見ます。

ダッシュボードが利用者と競合し始めたとき

GROUP BYで組んだ売上ダッシュボードは、数か月分のデータであれば問題なく動きます。履歴が数年分に育つと、その1本のクエリがデータベースの資源を占め、利用者が待っているリクエストが遅くなり始めます。 インデックスの追加やクエリの見直しで足りることもありますが、この問題の本質は本番のリクエストとの競合です。集計をリードレプリカへ送れば、重いクエリが本番の資源を奪わなくなり、多くの場合はここで解決します。 データウェアハウスが答えになるのは、分析がアドホックになったときです。分析担当者が事前に想定されていないクエリを書き、複数のテーブルを結合し、数億行を集計する。この負荷を現実的な時間で捌けるようにしているのが、列指向の保持と分析処理への特化です。

セッションの読み書きがテーブルを圧迫するとき

リレーショナルデータベースの表にセッションを置くと、リクエストのたびに読み書きが発生します。ログイン中の利用者が増えるほど、業務データとは無関係な書き込みがデータベース全体に占める割合を増やしていきます。 キーバリューストアへ移すと、その読み書きは桁違いに速くなり、有効期限の設定によって期限切れの削除も後片付けの処理なしで済みます。 そこに置いてよいかどうかの判定は、失われたときに何が起きるかです。セッションなら代償はログイン1回分で、アプリケーションは自力で復帰します。「本日の注文件数」のようなカウンタはそうはいきません。キーバリューストアだけに置けば次の再起動で失われ、前述の揮発性がそのまま置き場所を決めます。

2つ目のデータベースが要求する代償

データベースを足すことの代償は、それを動かす費用だけではありません。次の3つはアプリケーション側に降りかかり、以後ずっと残ります。

2本目の書き込み経路

参照元へのすべての変更が複製にも届かなければなりません。その経路は、きっかけとなった書き込みとは独立して失敗しえます。

ずれ

複製は参照元より遅れます。多くは一時的ですが、障害のあとに恒久的に残ることもあります。どこまでの古さを許容するかをアプリケーションが決める必要があります。

運用

監視、バックアップ、アップグレード、容量、そして深夜3時にそれを診断できる人です。
実務で判断を分けることが多いのは3つ目です。1人しか運用できないデータベースは、システムだけでなくチームにおいても単一障害点になります。 順序が重要なのもこのためです。1つのデータベースでできることを尽くし、そのうえで計測された問題に対して意図的に2つ目を足します。専門化されたデータベースに早く手を伸ばすことは、まだ必要でない能力を買い、その代金を以後のすべての書き込みで支払うことを意味します。

関連ページ

パフォーマンス

最適化の前に計測すること、そしてコードを速くすべき問題と仕事を減らすべき問題の違いを扱っています。