データベース
データベースが、アプリケーションが依存する永続データをどのように保存し、整理し、そして確実に取得するか。
データベースが存在する理由
アプリケーションのメモリは一時的です。実際のシステムには、クラッシュ、再起動、デプロイ後も残る永続的な保存先が必要です。
- サーバーのメモリ(RAM)は揮発性であり、プロセスが停止すると消去されます。
- 重要なデータ — ユーザー、注文、メッセージ、ログ — は実行を超えて保持されなければなりません。
- データベースは、ディスクに支えられた永続的で構造化された保存を提供します。
詳細
バックエンドサーバーは、オペレーティングシステムの制御下で動作する単なるプロセスです。メモリ上の変数は、プロセスの再起動、クラッシュ、または再デプロイによって消えてしまいます。そのため、メモリは計算には最適ですが、長期保存にはまったく信頼できません。
本番システムではデータの永続性が必要です。ユーザーアカウントは再起動後も存在していなければなりません。注文はデプロイ後も記録されたままでなければなりません。ログはデバッグや監査のために引き続き参照できる必要があります。
データベースは、構造化データを永続ストレージ(通常は SSD またはディスク)に書き込むことでこれを解決します。メモリが速度に最適化されているのに対し、ストレージは永続性に最適化されています。データベースエンジンは、データの書き込み、インデックス作成、安全な復旧の方法を管理します。
データベースがなければ、システムに長期記憶はありません。最悪の形でステートレスになり、障害を乗り越えられなくなります。
データベースとは何か?
データベースは、データを永続的に保存し、構造化して整理し、制御された取得を可能にする管理されたシステムです。
- プロセスの寿命を超えて、データをディスク上に確実に永続化します。
- データを構造化されたモデル(テーブル、ドキュメント、キー・バリュー)に整理します。
- クエリエンジンを提供し、整合性のための制約を強制します。
詳細
データベースは単なる保存場所ではなく、データのための制御された実行エンジンです。
リレーショナルモデルでは、データは行と列で構成されたテーブルに保存されます。各行はレコードを表し、各列は属性を表します。スキーマは構造、データ型、制約を定義し、無効または不整合なデータが挿入されるのを防ぎます。
データベースはクエリエンジンも提供します。アプリケーションは生のファイルを読む代わりに、構造化されたクエリを発行して、特定のデータの部分集合を効率的に取得または変更します。
システムの観点では、データベースは信頼できる唯一の情報源です。永続性を保証し、構造の整合性を維持し、予測可能な状態管理を可能にします。
SQL と NoSQL
SQL と NoSQL のデータベースは、似たような保存の問題を異なる構造とスケーリングのトレードオフで解決します。どちらか一方が常に優れているわけではありません。
- SQL: 構造化されたスキーマ、テーブルベースのモデル、強い整合性保証。
- NoSQL: 柔軟なスキーマ、主にドキュメント型またはキー・バリュー型、水平スケーリング向けに設計。
詳細
SQL データベース(リレーショナルシステム)は、厳密なスキーマを持つ事前定義されたテーブルにデータを整理します。テーブル間の関係は明示的にモデル化され、強いトランザクション保証が標準です。そのため、整合性や複雑なクエリが必要なシステムに適しています。
NoSQL データベースは、厳格なスキーマの制約を緩和します。多くはドキュメントベースまたはキー・バリュー構造を採用しており、レコードごとにフィールドを変えられます。この柔軟性により、急速に変化するデータモデルの開発を簡単にでき、分散クラスター全体での水平スケーリングもより自然にサポートできます。
本質的な違いはアーキテクチャ上の重点です。SQL は構造と強い整合性を優先します。NoSQL は柔軟性と分散スケーラビリティを優先します。本番システムでは、ワークロードに応じて両方を組み合わせることがよくあります。
サーバーがデータベースをクエリすると何が起こるか?
多くの実際のシステムでは、アプリケーションロジックではなくデータベース呼び出しがリクエストのレイテンシを支配します。
- リクエストの流れは拡張されます: Client → Load Balancer → Server → Database → Server → Client。
- サーバーはデータベースがクエリを処理している間、ブロックされる(または待機する)。
- データベースの往復通信が、全体の応答時間を決めることがよくあります。
詳細
クライアントが HTTP リクエストを送ると、最終的にそれはサーバープロセスに届きます。ハンドラーはアプリケーションロジックを実行しますが、意味のある操作の多くは永続データの読み書きを必要とします。
サーバーはデータベースエンジンにクエリを送ります。そのクエリには、SQL の解析、権限の確認、関連するデータページの特定、インデックスの利用、ディスクからの読み取り、結果セットの組み立てなどが含まれる場合があります。
この間、サーバースレッドは通常待機しています。同期モデルではブロックされます。非同期モデルでは、イベントループが結果を待ちます。どちらの場合でも、進行はデータベースが処理を終えるかどうかに依存します。
ここが重要です。データベースとのやり取りは、バックエンドシステムにおけるレイテンシの最大要因であることがよくあります。クエリの不備、インデックスの不足、ネットワークの往復、ディスク I/O の遅延は、アプリケーションの計算時間を簡単に上回ります。
システムが遅く感じる場合、まず調べるべき場所はデータベース層であることが多いです。
インデックス
インデックスは、データベースがすべての行を確認しなくても、データをすばやく見つけられるようにします。
- インデックスなし → データベースは行を1つずつ確認します。
- インデックスあり → データベースは一致するデータに直接移動します。
- インデックスは読み取りを高速化しますが、書き込み時には追加の処理が必要になります。
詳細
100万件のユーザーが入ったテーブルがあり、特定のメールアドレスを検索するとします。
インデックスがない場合、データベースは一致する行が見つかるまで各行を確認する必要があるかもしれません。これは遅く、特にテーブルが大きくなるほど顕著です。
email 列にインデックスがある場合、データベースは別の整理された構造(本の索引のようなもの)を保持します。すべてを走査する代わりに、その構造を使って正しい行をすばやく見つけます。
インデックスは読み取りを大幅に高速化しますが、無料ではありません。データを挿入、更新、削除するたびに、インデックスも更新する必要があります。
クエリが予想外に遅い場合、最初に確認すべきことの1つは「検索対象の列にインデックスがあるか?」です。
トランザクションと整合性
トランザクションは、関連する一連の操作がすべて成功するか、すべて失敗するかのどちらかになることを保証します。
ステップは順番に実行されます。1つでも失敗すると、それ以前の変更はすべて元に戻されるため、データベースが中途半端に更新された状態で終わることはありません。
- 複数のデータベース操作を1つの論理的な単位としてまとめられます。
- 1つのステップが失敗すると、それ以前の変更はすべてロールバックされます。
- これにより、データが部分的に更新された状態で残るのを防げます。
詳細
口座 A から口座 B にお金を送金することを考えてみましょう。
ステップ 1: 口座 A から $100 を引く。
ステップ 2: 口座 B に $100 を加える。
もしシステムが ステップ 1 の後、ステップ 2 の前にクラッシュすると、お金は消えてしまいます。これはデータ破損です。
トランザクションはこれを防ぎます。両方の操作はトランザクション境界の内側にまとめられます。データベースは次のどちらかを保証します。
• 両方のステップが正常に完了する、または
• どちらのステップも永続的には適用されない。
この「すべてか、何もないか」という動作は atomicity と呼ばれます。これにより、障害が発生してもシステムは論理的に整合した状態を保てます。
トランザクションは、データの正確性が重要なあらゆる場面で不可欠です。たとえば、金融、在庫管理、認証などです。
ACID
ACID は、データベースのトランザクションを正しく、かつクラッシュに強く保つ 4 つの保証を定義します。
- Atomicity: トランザクションは完全に成功するか、完全にロールバックされます。
- Consistency: コミットされたトランザクションの後もデータは有効な状態のままです。
- Isolation: 同時実行されるトランザクションは互いに破壊し合いません。
- Durability: コミットされたデータはクラッシュや再起動後も失われません。
詳細
ACID は理論ではなく、実用的な信頼性の契約です。
Atomicity は部分的な更新を防ぎます。1 つの手順が失敗した場合、すべてがロールバックされます。
Consistency は、トランザクション完了後も制約、関係、ルールが維持されることを保証します。
Isolation は、複数のユーザーが同時に操作しても、トランザクション同士が互いを壊さないように保護します。
Durability は、データベースが成功を確認した時点で、そのデータが永続ストレージに書き込まれ、システム障害が起きても残ることを保証します。
これらの特性により、データベースは実世界のシステムにとって信頼できる基盤になります。
データベースのスケーリング
トラフィックとデータが増えると、単一のデータベースインスタンスがボトルネックになることがよくあります。スケーリング戦略は、負荷を分散するか、容量を増やします。
- 垂直スケーリングは、単一のデータベースマシンの性能を向上させます。
- Read replicas は、読み取りトラフィックをデータの複数コピーに分散します。
- Sharding は、データを複数のデータベースに分割して、全体の負荷を分散します。
詳細
利用が増えると、クエリ数も増加します。やがて、単一のデータベースサーバーでは、入ってくるリクエストに対応しきれなくなります。
1つの方法は垂直スケーリングです。つまり、既存のマシンをより多くの CPU、メモリ、またはより高速なストレージで強化します。アーキテクチャはそのままなので、これが最もシンプルな解決策です。ただし、ハードウェアのアップグレードには限界があり、コストもすぐに増えます。
別の戦略は read replicas を追加することです。このモデルでは、primary database が書き込みを処理し、複製されたコピーが読み取り専用クエリを処理します。これにより primary server への負荷が軽減されますが、ノード間でデータが伝播する間に小さな遅延が発生することがあります。
さらに大きな成長に対応するために、システムは sharding を使うことがあります。すべてのデータを1台のマシンに保存する代わりに、データセットを複数の database instances に分割します。たとえば、1つの shard にユーザー A〜M を保存し、別の shard に N〜Z を保存する、といった形です。これにより総容量は増えますが、適切なルーティングロジックと運用管理が必要になります。
これらの方法はいずれもスケーラビリティを向上させますが、コスト、複雑さ、一貫性の面でトレードオフも生じます。
データベースの障害とボトルネック
データベースが遅くなったり障害を起こしたりすると、アプリケーション全体にその影響がすぐに現れます。
アプリケーション
DBを待機中のリクエスト
データベースの状態
- データベースが利用できなくなると、リクエストはすぐに失敗し始めます。
- 遅いクエリやロックの競合によって、アプリケーション全体のレイテンシが増加します。
- 接続数、ディスク、レプリケーション遅延などのリソース枯渇は、不安定さを引き起こします。
詳細
データベースは、バックエンドシステムにおいて最も重要な依存先であることがよくあります。データベースが遅くなったり障害を起こしたりすると、その影響はアプリケーションにすぐ反映されます。
データベースに到達できなくなると、リクエストハンドラはクエリを完了できず、アプリケーションは 500 系のエラーを返し始めます。
データベースが稼働していても、遅いクエリ、インデックスの不足、ロックの競合によってレイテンシが増加することがあります。外から見るとサーバーが遅く見えますが、実際にはデータベースの応答を待っているだけです。
高負荷時には、接続枯渇、レプリケーション遅延、ディスクの飽和などのリソース制限が、不安定さや一貫しない動作を引き起こすことがあります。
多くの「アプリケーションの問題」は永続化層に起因しています。この依存関係を理解することは、本番環境の問題を診断するうえで不可欠です。
質問セクション
1 / 5