データベースレプリケーション入門|プライマリ/レプリカ構成・読み取り分散・遅延の扱い

kento_morota 12分で読めます
社内のAI・IT活用、技術がわかる相手に無料相談。 相談 →
目次

「DBが1台なのが不安なのでレプリカを追加したい」「読み取りが重いのでリードレプリカに分散したい」と考えて構築したものの、更新直後に古いデータが表示される、レプリカが落ちてもアプリが気づかない、いざ障害のときに切り替え方がわからない、といった状態になることは珍しくありません。レプリケーションは「作る」より「正しく使う」ほうが難しい仕組みです。

この記事では、同期と非同期、ストリーミングとロジカルという2つの軸でレプリケーションの種類を整理し、リードレプリカへ読み取りを分散するアプリ側の実装、避けられないレプリケーション遅延とのつき合い方、そしてフェイルオーバーの基本を解説します。RDSやCloud SQLでレプリカを作る操作自体はAWS RDSの実務ガイドGoogle Cloud SQLの導入ガイドにありますので、本記事は「作った後にどう設計するか」に集中します。

レプリケーションの仕組みと2つの分類軸

変更履歴(WAL / binlog)を転送して再生する

レプリケーションの基本は「プライマリで起きた変更を、レプリカでも同じ順序で再生する」ことです。PostgreSQLはWAL(Write-Ahead Log)、MySQLはbinlog(バイナリログ)という変更履歴を書き込みのたびに生成しており、これをネットワーク経由でレプリカへ送り、レプリカ側で適用し続けます。レプリカは常に「プライマリの少し過去の状態」を再現している、と捉えると以降の話が理解しやすくなります。

同期か非同期か:「書き込み完了」をいつ返すか

非同期レプリケーションでは、プライマリは自分のディスクに書き込んだ時点でアプリにCOMMIT成功を返し、レプリカへの転送は後追いで行います。書き込みは速いものの、プライマリが直後に壊れると転送前の変更は失われます。同期レプリケーションでは、レプリカが受信(または適用)を確認するまでCOMMITを返しません。データは失われませんが、レプリカとの往復分だけ書き込みが遅くなり、レプリカが落ちると書き込みが止まります(PostgreSQLでは synchronous_standby_namessynchronous_commit で細かく制御でき、MySQLには準同期(semi-sync)という中間の方式があります)。多くのシステムでは、リードレプリカは非同期、耐障害用のスタンバイは同期または準同期、という組み合わせになります。

ストリーミングかロジカルか:「何を」転送するか

ストリーミング(物理)ロジカル(論理)
転送するものWALそのもの(ブロック単位の変更)行単位の変更(INSERT/UPDATE/DELETE)
レプリカの状態プライマリと完全に同一。読み取り専用指定したテーブルだけ受け取れる。書き込みも可能
バージョン同一メジャーバージョン必須異なるバージョン間でも可(アップグレードに使える)
主な用途リードレプリカ、フェイルオーバー用スタンバイ一部テーブルの集約、分析基盤への連携、無停止アップグレード
PostgreSQLでの設定wal_level=replicaprimary_conninfowal_level=logical、PUBLICATION/SUBSCRIPTION

PostgreSQLでのストリーミングレプリカの最小構成は次の通りです。

# プライマリ側: postgresql.conf と pg_hba.conf
wal_level = replica
max_wal_senders = 10
# pg_hba.conf にレプリカからの接続を許可
host  replication  repl_user  10.0.1.0/24  scram-sha-256

# レプリカ側: ベースバックアップを取得し、standby.signalと接続情報を自動生成
pg_basebackup -h 10.0.0.10 -U repl_user -D /var/lib/postgresql/16/main \
  -R -X stream -C -S replica1 -P
# -R: standby.signal と primary_conninfo を書き出す
# -C -S: レプリケーションスロットを作成(WALの削除を防ぐ)

# 起動後、プライマリで状態確認
SELECT client_addr, state, sent_lsn, replay_lsn, replay_lag FROM pg_stat_replication;

リードレプリカへの読み取り分散

振り分けはアプリのコードで決まる

レプリカを作っただけでは読み取りは分散されません。「このクエリはプライマリへ、このクエリはレプリカへ」という判断はアプリ側で行う必要があります。主なフレームワークはこの仕組みを備えています。

# Rails: 書き込み用と読み取り用を宣言し、GETリクエストは自動でレプリカへ
class ApplicationRecord < ActiveRecord::Base
  connects_to database: { writing: :primary, reading: :primary_replica }
end
# 書き込み直後の読み取りは delay 秒間プライマリへ向ける
config.active_record.database_selector = { delay: 2.seconds }

# Django: ルーターで振り分け
class ReplicaRouter:
    def db_for_read(self, model, **hints):
        return "replica"
    def db_for_write(self, model, **hints):
        return "default"

# 明示的にプライマリから読みたい場合
Order.objects.using("default").get(id=order_id)

フレームワークの自動振り分けに頼らず、「一覧・検索・レポート・集計」といった重い読み取りだけを明示的にレプリカへ向け、それ以外はプライマリのまま、という運用から始めるのが安全です。すべての読み取りを機械的にレプリカへ流すと、次に述べる遅延の問題が全画面で起こりえます。

接続先の管理と障害時の挙動

マネージドDBではレプリカ用のエンドポイント(RDSのリーダーエンドポイント等)が用意され、複数レプリカへの振り分けはそちらで行われます。自前構成ではHAProxyやPgBouncerで複数レプリカをまとめ、ヘルスチェックで落ちたレプリカを外すようにします。またレプリカの数だけ接続も増えるため、コネクションプーリングの合計本数も見直してください。

レプリケーション遅延|「書いた直後に読めない」への対策

遅延はゼロにならない

非同期レプリケーションでは、プライマリでのCOMMITからレプリカで読めるようになるまで、通常は数ミリ秒〜数百ミリ秒の遅延があります。しかし、大量更新のバッチ、レプリカ側の重いクエリ、ネットワーク帯域の不足、レプリカのディスクI/O不足によって、遅延は数秒から数分に伸びることがあります。遅延の監視は必須です。

-- PostgreSQL(レプリカで実行): 最後に適用した変更からの経過時間
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;

-- MySQL(レプリカで実行)
SHOW REPLICA STATUS\G
-- Seconds_Behind_Source と Replica_SQL_Running を確認

PostgreSQLの pg_last_xact_replay_timestamp は「プライマリで更新がない間は値が進まない」ため、静かな時間帯には見かけ上の遅延が大きく出ます。プライマリ側の pg_stat_replication.replay_lag と併せて見てください。

Read-your-writesを実現する3つの方法

ユーザーが自分の書き込みを直後に読めることを「Read-your-writes(自分の書き込みの読み取り)一貫性」と呼びます。非同期レプリカでこれを保証するには、アプリ側の工夫が必要です。

  1. 書き込み直後の一定時間はプライマリから読む: Railsの delay オプションがこの方式です。セッションやCookieに最終書き込み時刻を記録し、数秒間はそのユーザーの読み取りをプライマリへ向けます。実装が簡単で、多くの場合これで十分です。
  2. 書き込み直後の遷移先だけプライマリから読む: 「保存→詳細画面へリダイレクト」のような、書いた直後に読むことがわかっている処理だけ明示的にプライマリを使います。遅延の影響範囲を最小にできます。
  3. LSN/GTIDで追いついたレプリカだけを使う: 書き込み時のWAL位置(LSN)やGTIDを記録し、レプリカがそこまで適用済みか確認してから読みます。厳密ですが実装は重く、必要な場面は限られます。

一方、他人の書き込みが数秒遅れて見えることは、ほとんどのWebアプリで許容できます。「誰の書き込みを、いつ読むか」で要件を切り分けるのが遅延対策の本質です。

フェイルオーバーの基本

昇格・切替・旧プライマリの隔離

プライマリが故障したときにレプリカを新しいプライマリに昇格させる操作がフェイルオーバーです。手順は概ね次の通りです。

  1. プライマリが本当に応答不能かを確認する(ネットワークの一時的な断で誤判定すると、プライマリが2台できる「スプリットブレイン」になる)。
  2. 旧プライマリを確実に停止・隔離する(フェンシング)。
  3. 最も進んでいるレプリカを昇格する(PostgreSQLは SELECT pg_promote(); または pg_ctl promote、MySQLは STOP REPLICA; RESET REPLICA ALL; 後に書き込み許可)。
  4. アプリの接続先を新プライマリへ切り替える(DNS更新、VIP移動、接続文字列の更新など)。
  5. 他のレプリカを新プライマリに向け直し、旧プライマリは復旧後に新プライマリのレプリカとして再構築する(PostgreSQLでは pg_rewind が使える)。

非同期レプリケーションの場合、昇格したレプリカには転送前の最新数件の変更が届いていない可能性があり、その分のデータは失われます。「失ってよい量」がRPO(目標復旧時点)で、これが許容できないならスタンバイは同期にする必要があります。マネージドDBのマルチAZ構成は、この手順を自動化し、エンドポイントのDNSを切り替えてくれるものです。自前で行う場合はPatroniやrepmgr(PostgreSQL)、Orchestrator(MySQL)などの自動フェイルオーバーツールを使い、手順書と訓練を必ずセットにします。バックアップとの役割の違いはDBのバックアップとPITRで解説していますが、レプリケーションは誤ったDELETEも忠実に複製するため、バックアップの代わりにはなりません。

トラブル事例:プロフィールを更新したのに古い内容が表示される

症状

リードレプリカを導入した直後から、「プロフィールを保存したのに変わっていない。リロードすると直る」という問い合わせが増えました。エラーは出ておらず、再現も不安定でした。

原因

保存処理はプライマリに書き込んだ後、プロフィール画面へリダイレクトしていました。画面表示のSELECTは自動振り分けでレプリカに向いており、リダイレクト先の読み取りがレプリケーション遅延(このときは平均200ms、ピーク時に2秒)より先に実行されていました。典型的なRead-your-writes違反です。

対処

  1. 書き込みを伴うリクエストの後、そのセッションは3秒間プライマリから読むようにミドルウェアを追加した。
  2. 遅延を replay_lag で常時監視し、5秒を超えたらアラートを出すようにした。
  3. ピーク時の遅延の原因だったレプリカ上の重い集計クエリを、専用の分析用レプリカへ移した。

まとめ

レプリケーションは「読み取り性能」と「耐障害性」の両方を支えますが、その代わりに「遅延」と「切替」という新しい運用課題を持ち込みます。

  1. 同期/非同期は「データを失ってよいか」と「書き込み速度」のトレードオフ。ストリーミング/ロジカルは「丸ごと複製か、選んで複製か」。
  2. 読み取り分散はアプリの振り分け実装があって初めて機能する。重い読み取りから段階的に。
  3. 遅延はゼロにならない。自分の書き込みを直後に読む処理だけプライマリへ向ける。
  4. フェイルオーバーは手順書と訓練込みで設計する。レプリケーションはバックアップの代替ではない。

まずはレプリカの遅延を計測して監視に乗せ、「書いた直後に読む」画面を洗い出すところから始めてみてください。PostgreSQLの基礎はPostgreSQL入門で解説しています。DB構成の設計や可用性向上の取り組みは、Harmonic Societyのシステム開発・インフラ支援でもお手伝いしています。

#レプリケーション#リードレプリカ#PostgreSQL#MySQL

Harmonic Society

この記事の内容、自社の業務でも活かせそうですか?

ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。

共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

関連記事

Related / 9 articles

  1. プログラミング

    DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用

    DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。

  2. プログラミング

    WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド

    WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。

  3. プログラミング

    セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説

    CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。

  4. プログラミング

    クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計

    クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。

  5. プログラミング

    秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除

    APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。

  6. プログラミング

    開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御

    開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。

  7. プログラミング

    ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方

    NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。

  8. プログラミング

    ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方

    ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。

  9. プログラミング

    localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決

    localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。

Harmonic Society

「読んで終わり」にせず、自社の業務で試してみませんか?

AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。

無料・30分・オンラインOK|1営業日以内に返信します