非正規化 - Webパフォーマンスのための実用的なトレードオフ
Olivia Novak
Dev Intern · Leapcell

はじめに
Webアプリケーション開発の世界では、パフォーマンスが最優先事項です。ユーザーは瞬時の応答を期待しており、読み込み時間が遅いとエンゲージメントや収益の損失につながる可能性があります。データベース正規化は、データの冗長性を削減し、データ整合性を向上させることを目的とした、優れたリレーショナルデータベース設計の基盤ですが、絶対的な正規化の追求はしばしばコストを伴います。それは、クエリの複雑さと実行時間の増加です。これは、正規化されたスキーマでは通常、完全なデータセットを取得するために多数のJOINが必要になるためです。高トラフィックのWebアプリケーションでは、これらの追加のJOINはすぐにボトルネックとなり、ユーザーエクスペリエンスに深刻な影響を与える可能性があります。ここで、非正規化が登場します。それは欠陥としてではなく、計算された戦略的な設計上の選択、つまり最新のWebアプリケーションが要求する応答性を達成するために必要な犠牲としてです。
環境を理解する
非正規化のニュアンスを掘り下げる前に、いくつかの基本的な概念を明確にしましょう。
正規化: データベース正規化は、データの冗長性を最小限に抑え、データ整合性を向上させるために、リレーショナルデータベースの列とテーブルを整理するプロセスです。通常、大きなテーブルをより小さく関連するテーブルに分割し、それらの間の関係を定義します。一般的な正規形には、1NF、2NF、3NFがあります。
非正規化: 非正規化は、クエリパフォーマンスを改善するために、データベーススキーマに意図的に冗長性を導入するプロセスです。これには、テーブルを結合したり、正規化された設計では別々になるであろうテーブルにデータのコピーを追加したりすることがよくあります。
JOIN操作: リレーショナルデータベースでは、JOIN句は、それらの間の関連する列値に基づいて、1つ以上のテーブルの列を結合します。正規化されたスキーマから完全なデータを取得するために不可欠ですが、特に大規模なデータセットを扱う場合、JOINは計算上コストのかかる操作です。
非正規化の原則
Webアプリケーションにおける非正規化の背後にある核心的な原則は単純です。頻繁にアクセスされるデータを取得するために必要なJOIN操作の数を減らすことです。データを事前結合または複製することで、データベースエンジンがクエリ時にこれらのコストのかかる操作を実行する必要がなくなります。この事前計算または事前格納された結合データは、Webアプリケーションで支配的な操作であることが多い読み取りアクセスを大幅に高速化します。
一般的なシナリオを考えてみましょう。ブログのホームページに、各記事の著者の名前とともに記事のリストを表示します。
正規化されたアプローチ:
通常、articlesとauthorsの2つのテーブルがあります。
-- albums table CREATE TABLE articles ( article_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, content TEXT, author_id INT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (author_id) REFERENCES authors(author_id) ); -- artists table CREATE TABLE authors ( author_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE );
記事のタイトルと著者の名前を表示するには、次のようなクエリを実行します。
SELECT a.title, au.name FROM articles a JOIN authors au ON a.author_id = au.author_id WHERE a.created_at >= CURDATE() - INTERVAL 7 DAY ORDER BY a.created_at DESC;
数記事であれば、このJOINは無視できます。しかし、何千もの記事と数百人の著者を持つ人気のブログで、頻繁にアクセスされるページのデータを取得することを想像してみてください。各リクエストにはJOINが含まれ、これはパフォーマンスのボトルネックになる可能性があります。
非正規化されたアプローチ:
articlesテーブルに直接author_nameを追加することで、冗長性を導入できます。
CREATE TABLE articles ( article_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, content TEXT, author_id INT NOT NULL, author_name VARCHAR(255) NOT NULL, -- Denormalized field created_at DATETIME DEFAULT CURRENT_TIMESTAMP -- author_id は整合性や将来の更新のためにまだ present されていますが、 -- 必要に応じて外部キー制約が still be desired。 );
これで、記事のタイトルと著者の名前の取得は、単一テーブルからの単純な読み取りになります。
SELECT title, author_name FROM articles WHERE created_at >= CURDATE() - INTERVAL 7 DAY ORDER BY created_at DESC;
この単一テーブルクエリは、特に負荷が高い場合、JOIN操作のオーバーヘッドを回避するWebアプリケーションにより、JOINよりも大幅に高速です。
アプリケーションシナリオと実装
非正規化は、次のようなシナリオで最も効果的です。
- 読み取り負荷の高いワークロード: Webアプリケーションでは、読み取りと書き込みの比率が読み取りに大きく偏っていることがよくあります。非正規化は、これらの frequent な読み取り操作を最適化します。
- 複雑なレポートまたは分析: 複数のテーブルからデータを集計することは、しばしば複雑で遅いJOINを伴います。集計を事前計算して保存することで非正規化すると、レポートが劇的に高速化する可能性があります。
- 複数のテーブルを必要とする頻繁にアクセスされるデータ: 特定のデータ組み合わせが常に要求される場合、非正規化はsignificant なブーストを提供できます。
一般的な非正規化戦略:
-
列の複製: 上記の
author_nameの例のように、これは最も単純な形式です。関連テーブルから列をアクセスのプライマリテーブルにコピーします。 -
サマリー/集計テーブル: レポートのために、計算済みの合計、平均、またはカウントを保存するテーブルを作成することで、コストのかかる
GROUP BYおよびJOIN操作を最小限に抑えることができます。たとえば、daily_sales_summaryテーブルは、個々のorder_itemsから毎回再計算するのを避けて、日ごとの製品あたりの総売上を保存できます。 -
**垂直パーティショニング(非正規化と組み合わされることが多い):**これは厳密には非正規化ではありませんが、しばしば組み合わせて使用されます。多数の列を持つ単一のテーブルを保持する代わりに、アクセスパターンに基づいてテーブルを垂直に分割します。頻繁にアクセスされる列は一緒に保持され、あまり頻繁にアクセスされない(またはより大きい)列は別のテーブルに配置されます。次に、頻繁に結合される列をメインの「ホット」テーブルに非正規化することができます。
データ整合性の管理:
非正規化の最大の課題は、データ整合性を維持することです。非正規化されたフィールドが元のテーブルで更新された場合、複製されたコピーも更新する必要があります。これは、さまざまなメカニズムを通じて処理できます。
-
アプリケーションロジック: アプリケーションコードは、元のデータが変更されたときにすべての一意のコピーを更新する責任があります。これには、注意深い規律ある開発が必要です。
-
データベーストリガー: データベーストリガーは、ソースデータが変更されたときに非正規化されたフィールドを自動的に更新できます。これにより、アプリケーションから一貫性ロジックをオフロードしますが、データベーススキーマに複雑さを追加します。
-- authors.name が変更されたときに articles.author_name を更新するトリガーの例 DELIMITER $$ CREATE TRIGGER update_article_author_name AFTER UPDATE ON authors FOR EACH ROW BEGIN IF OLD.name <> NEW.name THEN UPDATE articles SET author_name = NEW.name WHERE author_id = NEW.author_id; END IF; END$$ DELIMITER ; -
バッチ更新/スケジュールジョブ: あまりクリティカルでない、または頻繁に変更されないデータについては、非正規化されたフィールドへの更新は、定期的に(たとえば、毎晩)実行されるバッチジョブによって処理できます。
-
**マテリアライズドビュー(一部のデータベースシステム):**一部の高度なデータベースシステムは、物理的に保存されたクエリの事前計算結果であるマテリアライズドビューを提供します。これらは手動または自動でリフレッシュでき、強力な非正規化メカニズムを提供します。
結論
非正規化は、あらゆるコストを回避すべきアンチパターンではなく、慎重に適用された場合には強力な最適化手法です。読み取り速度が重要な高性能Webアプリケーションにとって、データ冗長性の制御された導入は、クエリ実行時間を劇的に削減し、ユーザーエクスペリエンスを向上させることができます。それは、一部の理論的なデータ整合性を具体的なパフォーマンスの利点と引き換える、実用的なトレードオフです。鍵は、戦略的な適用、整合性への影響の理解、および冗長データの効果的な管理のための堅牢なメカニズムの実装にあります。非正規化を慎重に採用することにより、開発者は機能的であるだけでなく、並外れて高速で応答性の高いWebアプリケーションを構築できます。