👌

RDBMSの負荷対策に悩むWebアプリ開発者へ

に公開

RDBMSの負荷対策に悩むWebアプリ開発者へ

Webアプリを作っていると、「DBの負荷が高くてページが遅い」「クエリがボトルネックでスケールできない」と悩むことは多いです。
多くの開発者が最初に選ぶのはRDBMS(MySQLやPostgreSQLなど)ですが、万能ではありません。保存や更新に強い一方で、検索や分析をすべて任せると限界にぶつかります。

本記事では、DB負荷を減らす設計思想と、実際に使えるテクニック集を整理しました。順番に試していけば、徐々にボトルネックを解消できます。


設計の基本思想

  • RDBMSは“保存と更新”の担当
  • 検索や分析は別の仕組みに任せる

つまり「なんでもDBでやる」のではなく、役割分担が重要です。
全文検索やレコメンドなど、大量データに横断的にアクセスする処理はRDBMSに向いていません。
そこで、インデックスサーバやキャッシュを組み合わせ、RDBMSの負荷を抑えます。


実践テクニック集

1. 即効性のある対策:キャッシュ

  • Redis / Memcached で頻出クエリ結果をキャッシュ
  • ページキャッシュ(HTMLごと)、クエリキャッシュ(SQL結果)でアクセス回数を削減
  • 「よく使うランキング一覧」「最新記事リスト」などに効果的

キャッシュが有効な理由

例:ブログ検索

  1. ユーザーが「Python」と検索
  2. DBやインデックスサーバで検索結果を返す
  3. 同じ検索は何回も発生 → キャッシュに保存すれば次回以降瞬時に応答可能

応用例

  • ランキングページや集計結果のキャッシュ
  • 部分一致検索のキャッシュ(例:「Python Web」「Python AI」)
  • 統計情報のキャッシュ(単語出現頻度やスコアリング結果)

導入のポイント

  • TTL(有効期限)を設定(1時間や数分など)
  • キャッシュの粒度を検討(検索単位、ページ単位、統計単位など)
  • インデックスサーバと併用し、最新データは検索専用サーバに任せる

👉 まずはキャッシュから始めるのがコストも低く、即効性があります。


2. 構造的な設計:マスタ/スレーブ構成

  • マスタDB:書き込み専用(INSERT/UPDATE/DELETE)
  • スレーブDB:読み取り専用(SELECT)
  • 読み取り負荷をスレーブに分散してスケールアウト可能

ポイント

  • レプリケーション遅延に注意
    • 最新データはマスタから取得
    • UIで「反映まで数秒かかります」と表示するなど通知する
  • スレーブ増設で読み取り分散
  • 書き込み集中時はマスタの性能改善やシャーディングを検討
  • キャッシュとの併用で、見た目上の遅延をほぼゼロに
                +------------------+
                |    Webアプリ      |
                +------------------+
                   | write   | read
                   v         v
+------------------+         +------------------+
|   プライマリDB    |==repl==>|   レプリカDB1     |  <-- SELECT
+------------------+         +------------------+
         ||==repl==>         +------------------+
         ||                  |   レプリカDB2     |  <-- SELECT
         ||==================>+------------------+

  ※ write(INSERT/UPDATE/DELETE) はプライマリへ
  ※ read(SELECT) はレプリカへ
  ※ repl はレプリケーション(プライマリ → レプリカ)

補足

  • UIで「最新データは反映まで数秒かかります」と表示することでユーザー体験を維持
  • スレーブDBを増設し、ロードバランサで分散すると大量アクセスにも対応可能
  • キャッシュと組み合わせることで、さらにレスポンスを高速化できます

👉 更新系と参照系を分けることで、同時アクセスが増えても安定します。


3. データを分割する:パーティショニング

  • 巨大テーブルをユーザーIDや日付で分割(シャーディング)
  • 複数の小さなDBに分散し、特定範囲のデータだけを効率的に扱える

実例:アクセスログの月次パーティション

CREATE TABLE access_log (
    id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    created_at DATE NOT NULL,
    action VARCHAR(255),
    PRIMARY KEY (id, created_at)
)
PARTITION BY RANGE (YEAR(created_at)*100 + MONTH(created_at)) (
    PARTITION p202301 VALUES LESS THAN (202302),
    PARTITION p202302 VALUES LESS THAN (202303),
    PARTITION p202303 VALUES LESS THAN (202304),
    PARTITION pMax VALUES LESS THAN MAXVALUE
);

特定月のパーティション活用

  • 特定月の検索はそのパーティションのみアクセス → 高速
  • 古いデータをまとめて削除 → 高速

応用例:ユーザーIDでの分割

  • 大量ユーザーのデータを複数テーブル/DBに分散
  • 特定ユーザーの検索は該当パーティションのみアクセス
  • スケーラビリティと検索性能が向上

👉 大規模化には必須の手法。RDBMSを水平スケールできます。


4. 高度な応用:インデックスサーバを利用

  • RDBMSから定期的にデータを抽出し、用途特化型のインデックスを構築
  • 例:Elasticsearch / MeiliSearch / Solr
  • Webアプリからは RDBMSではなくインデックスサーバに Web API や RPC 経由でアクセス
  • RDBMSは「更新担当」、インデックスサーバは「検索担当」

実例:全文検索エンジン

  1. バッチ処理でRDBMSから記事データを抽出
  2. インデックスサーバに転置インデックスを作成
  3. WebアプリからWeb APIで検索リクエスト
[DB] --(定期抽出)--> [インデックスサーバ] --(Web API)--> [Webアプリ]

実装例:Elasticsearch

インデックス作成

PUT /articles
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "body":  { "type": "text" },
      "created_at": { "type": "date" }
    }
  }
}

Webアプリから検索

from elasticsearch import Elasticsearch

es = Elasticsearch("http://localhost:9200")

res = es.search(
    index="articles",
    query={"match": {"body": "検索"}}
)

for hit in res['hits']['hits']:
    print(hit['_source']['title'])

検索結果はインデックスサーバから返され、DBにはアクセスしません。
ランキングやフィルタリングも柔軟に実装可能です。

ポイント

  • RDBMSは更新・永続化専用
  • 検索はインデックスサーバに任せる
  • バッチで定期更新し、最新データを保持
  • Web APIやRPCで検索アクセスを抽象化

👉 これにより、RDBMSの負荷を増やさずに大量データ検索を実現できます。


まとめ

  • RDBMSにすべてを任せない
  • キャッシュ → マスタ/スレーブ → パーティショニング → インデックスサーバ の順で段階的に導入
  • 適材適所のアーキテクチャが、大規模サービスを支える鍵

DBの負荷は成長とともに必ず問題になります。
小さな工夫から始め、サービスの規模に合わせて構造を進化させましょう。

Discussion