👌
RDBMSの負荷対策に悩むWebアプリ開発者へ
RDBMSの負荷対策に悩むWebアプリ開発者へ
Webアプリを作っていると、「DBの負荷が高くてページが遅い」「クエリがボトルネックでスケールできない」と悩むことは多いです。
多くの開発者が最初に選ぶのはRDBMS(MySQLやPostgreSQLなど)ですが、万能ではありません。保存や更新に強い一方で、検索や分析をすべて任せると限界にぶつかります。
本記事では、DB負荷を減らす設計思想と、実際に使えるテクニック集を整理しました。順番に試していけば、徐々にボトルネックを解消できます。
設計の基本思想
- RDBMSは“保存と更新”の担当
- 検索や分析は別の仕組みに任せる
つまり「なんでもDBでやる」のではなく、役割分担が重要です。
全文検索やレコメンドなど、大量データに横断的にアクセスする処理はRDBMSに向いていません。
そこで、インデックスサーバやキャッシュを組み合わせ、RDBMSの負荷を抑えます。
実践テクニック集
1. 即効性のある対策:キャッシュ
- Redis / Memcached で頻出クエリ結果をキャッシュ
- ページキャッシュ(HTMLごと)、クエリキャッシュ(SQL結果)でアクセス回数を削減
- 「よく使うランキング一覧」「最新記事リスト」などに効果的
キャッシュが有効な理由
例:ブログ検索
- ユーザーが「Python」と検索
- DBやインデックスサーバで検索結果を返す
- 同じ検索は何回も発生 → キャッシュに保存すれば次回以降瞬時に応答可能
応用例
- ランキングページや集計結果のキャッシュ
- 部分一致検索のキャッシュ(例:「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は「更新担当」、インデックスサーバは「検索担当」
実例:全文検索エンジン
- バッチ処理でRDBMSから記事データを抽出
- インデックスサーバに転置インデックスを作成
- 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