PostgreSQL | SQLite+MySQL経験者向け、30日で習得するPostgreSQL:プロレベル運用 - Day28 レプリケーション

SQL PostgreSQL
スポンサーリンク

Day28 前半のゴール

「“DBは1台だけ”という前提を捨てて、“コピーして読ませる”をイメージする」

今日のテーマはレプリケーションです。 一言でいうと「あるDBサーバのデータを、別のDBサーバにリアルタイム(またはほぼリアルタイム)でコピーし続ける仕組み」です。

前半のゴールはこうです。 なぜレプリケーションが必要になるのかを、負荷と障害の観点から説明できる。 PostgreSQLの「プライマリ(マスター)」「スタンバイ(レプリカ)」の役割をイメージできる。 読み取り分散(リードレプリカ)という使い方の基本イメージを持てる。

まずは、「そもそも1台じゃダメになる瞬間っていつ?」からいきます。

なぜレプリケーションが必要になるのか

「“1台で全部やる”と、どこで詰むのか」

最初のうちは、DBサーバ1台で十分です。 アプリも少ない、ユーザーも少ない、トラフィックも少ない。 でも、サービスが育ってくると、次の2つの問題が見えてきます。

一つ目は「負荷」の問題です。 読み取り(SELECT)が増え続けると、1台のDBサーバがさばききれなくなります。 CPUが張り付き、ディスクI/Oが詰まり、レスポンスが遅くなり、最悪落ちます。

二つ目は「障害」の問題です。 DBサーバが1台しかないと、その1台が落ちた瞬間にサービス全体が止まります。 ハード故障、OSトラブル、設定ミス、アップデート失敗…。 どれか1つで「全部止まる」状態は、本番運用としてはかなり危険です。

レプリケーションは、この2つに対する答えの一つです。 データを別サーバにコピーしておくことで、

読み取りの負荷を複数台に分散できる。 片方が落ちても、もう片方を使ってサービスを継続できる(設計次第)。

という状態を作れます。

ここでの大事な気づきは、「レプリケーションは“性能のため”と“生存のため”の両方に効く」ということです。

プライマリとスタンバイの役割

「“書く人”と“読む人”を分けるイメージ」

PostgreSQLのレプリケーションの基本形は、「プライマリ(primary)とスタンバイ(standby)」です。 昔の言い方だと「マスター/スレーブ」ですが、今は primary / standby という用語が主流です。

プライマリ 書き込み(INSERT / UPDATE / DELETE)を受け付ける“本体”のDBサーバ。 アプリからの更新系クエリは、基本的にここに飛ぶ。

スタンバイ プライマリのデータをコピーしている“追従側”のDBサーバ。 通常は読み取り専用(SELECTのみ)として使う。

イメージとしては、「プライマリが日記を書いていて、その内容をスタンバイが後ろからずっと写経している」感じです。 プライマリで起きた変更は、WAL(後で説明するログ)を通じてスタンバイに流れ、スタンバイはそれを順番に適用していきます。

重要なのは、「スタンバイは基本的に“書き込み禁止”」ということです。 スタンバイに直接INSERTしたりUPDATEしたりはできません(やろうとするとエラーになります)。 あくまで「プライマリのコピー」であり、「読み取り専用の鏡」です。

PostgreSQLのレプリケーションのざっくり構造

「WALという“変更ログ”を流して、向こうで再生する」

中身の細かい設定は後半や実務で覚えればよくて、前半では「どういう仕組みでコピーしているか」のイメージだけ掴めれば十分です。

PostgreSQLは、データの変更を「WAL(Write-Ahead Log)」というログに必ず書き出します。 これは、「どのページのどの部分をどう変えたか」という変更履歴です。

プライマリでは、 アプリからのINSERT / UPDATE / DELETEを受ける。 その変更内容をWALに書く。 WALをディスクにフラッシュしてから、実際のデータファイルに反映する。

スタンバイでは、 プライマリからWALを受け取る。 受け取ったWALを順番に適用して、自分のデータファイルを更新する。

という流れで、「プライマリと同じ状態」を追いかけ続けます。

ここでのキモは、「スタンバイは“SQLを再実行している”わけではない」ということです。 INSERT文をもう一度実行しているのではなく、「ページのこの部分をこう書き換えろ」という低レベルな指示(WAL)を適用しています。 だからこそ、プライマリとほぼ同じ状態を効率よく再現できます。

例題:読み取り分散のシンプルな構成イメージ

「書き込みは1台、読み取りは2台でさばく」

具体的な構成を、図を頭に描くつもりで言葉にしてみます。

サーバA:PostgreSQL(プライマリ) サーバB:PostgreSQL(スタンバイ1) サーバC:PostgreSQL(スタンバイ2)

アプリケーションはこう接続します。

書き込み系(ユーザー登録、注文作成、更新など)は、必ずサーバA(プライマリ)に送る。 読み取り系(一覧表示、検索、レポートなど)は、サーバBとサーバCに振り分ける。

これにより、

プライマリは「書き込み+一部の読み取り」だけを担当する。 スタンバイは「読み取り専用」としてフルにCPUとI/Oを使える。

という状態になります。

例えば、1秒間に1000件のSELECTが飛んでくるサービスで、 1台ではギリギリだったところを、スタンバイ2台に分散することで「1台あたり約500件」にできる、というイメージです。

ここでの重要ポイントは、「アプリ側が“どのクエリをどのサーバに投げるか”を意識して設計する必要がある」ということです。 DBが勝手に「これは読み取りだからスタンバイへ」と振り分けてくれるわけではありません。

レプリケーションの“遅延”という現実

「“ほぼリアルタイム”は“完全リアルタイム”ではない」

レプリケーションは便利ですが、「プライマリとスタンバイの状態は、常に完全に同じ」とは限りません。 WALを送る・受け取る・適用する、というプロセスには、どうしてもわずかな遅延が発生します。

例えば、こんなケースを考えます。

ユーザーが新規登録をする。 そのINSERTはプライマリに書き込まれる。 すぐに「自分のプロフィールを表示する」画面に遷移する。 その画面のSELECTを、スタンバイに投げている。

このとき、「スタンバイへのレプリケーションがまだ追いついていない」と、 「さっき登録したはずのユーザーが見つからない」という現象が起きます。

これが「レプリケーション遅延」の典型的な問題です。

対策としては、

「直後に読む必要があるデータ」は、必ずプライマリから読む。 「少し古くてもいい読み取り」(ランキング、一覧、分析など)はスタンバイに任せる。

というように、「どのデータはどれくらいの鮮度が必要か」をアプリ側で設計する必要があります。

ここでの大事な気づきは、「読み取り分散は、“全部のSELECTをレプリカに投げればOK”ではない」ということです。 「一貫性」と「スケール」をどうバランスさせるかが、設計の肝になります。

レプリケーションと障害対応の関係

「“読めるだけ”から、“代わりに書けるようにする”まで」

Day28 前半では主に「読み取り分散」の話をしていますが、 レプリケーションは「障害時のフェイルオーバー」にも使われます。

ざっくり言うと、

平常時 プライマリが書き込みを受け、スタンバイがそれを追いかける。

障害時(プライマリが落ちた) スタンバイの1台を「新しいプライマリ」に昇格させる。 アプリの接続先を、その新しいプライマリに切り替える。

という流れです。

ただし、これは「自動で勝手に全部やってくれる」わけではなく、 専用ツールや仕組み(Patroni, repmgr, pgpool-II など)と組み合わせて設計する世界になります。 Day28 前半では、「レプリケーションがあるからこそ、“プライマリが死んでも復活しやすい”構成が取れる」というイメージだけ持っておけば十分です。

Day28 前半のまとめ

レプリケーションは「プライマリDBの変更をWALという変更ログとしてスタンバイDBに送り、スタンバイ側で順番に適用することで“ほぼ同じ状態のコピー”を維持する仕組み」であり、プライマリは書き込みを受け、スタンバイは基本的に読み取り専用として使うことで、「読み取り負荷を複数台に分散する」「プライマリ障害時にスタンバイを昇格させてサービス継続する」といった構成が取れる。 一方で、レプリケーションには必ずわずかな遅延があるため、「直後に読みたいデータはプライマリから読む」「少し古くてもよい集計・一覧はスタンバイに投げる」といった“鮮度に応じた読み先の設計”が必要であり、「全部のSELECTをレプリカに投げればOK」ではない――ここまでの構造と感覚が掴めていれば、Day28 前半としてはとても良いスタートラインに立てている。

タイトルとURLをコピーしました