![[自サーバでNext.jsアプリを動かす#5]リードレプリカ(スレーブ)DBの利用](/_next/image?url=%2Farticles%2Fserver%2Fdb-replica%2Fhero.jpg&w=3840&q=75)
自サーバでNext.jsアプリを動かす#5リードレプリカ(スレーブ)DBの利用
SELECT系専用のリードレプリカ(スレーブ)DBとマスタDBへの接続を同居させる方法
初回公開日
最終更新日
アプライスDBのリードレプリカの作成でリード系専用のレプリカを作成しました。これをNext.jsのWebアプリケーションでどう設定すれば利用できるのか? なんとなくreadと関連のAPIとかサーバアクションのファイルだけ接続先を変える設定をするのかな? と思っていましたが違いました。
Prisma
は本当にいたれり尽せりのツールです。ほとんどのファイルや記述に変更を加えることなく、この辺の処理をやってくれることがわかりました。その方法について記述します。まず、環境の整理です。レプリカDBを増やして下記のような構成になっています。

これまでマスタDBしかない状態でNext.jsのWebアプリケーションを構築しています。DBは
PostgreSQL13
、Next.jsはver15、PrismaでDB接続と操作を行なっています。また、Next.jsはWebサーバではPM2
を利用して動作させています。この状態からリード回りは全てレプリカDBへ接続して処理するように変更していきます。1.接続設定
まずは接続設定を追加します。
ローカルPCから本番DBへの接続設定
○SSHトンネルの増設
ローカルPCから本番DBへの接続は下記のようにSSHトンネルを利用していると思います。
ローカルPCから本番DBへの接続は下記のようにSSHトンネルを利用していると思います。
zsh
1ssh -L 5433:db-master.local:5432 -N -p 22022 -i "~/sever01.pem" user@xxx.xxx.xxx.xxx
db-master.local
はマスタDBのIPもしくはサーバ側の/etc/hosts
の設定になります。上記はマスタDBのトンネルですので、下記のようにレプリカDBへのトンネル接続を増やします。zsh
1ssh -L 5434:db-read.local:5432 -N -p 22022 -i "~/sever01.pem" user@xxx.xxx.xxx.xxx
db-read.local
はレプリカDBのIPもしくはサーバ側の/etc/hosts
の設定○Next.jsの.envファイルへの追加
現状:マスタDBのみの設定
zsh : .env
1DATABASE_URL="postgresql://dbuser:dbuserpassword@127.0.0.1:5433/dbname?schema=public"
これにレプリカDBのURLを追加
zsh : .env
1DATABASE_URL="postgresql://dbuser:dbuserpassword@127.0.0.1:5433/dbname?schema=public"
2DATABASE_URL_REPLICA="postgresql://dbuser:dbuserpassword@127.0.0.1:5434/dbname?schema=public"
レプリカDB用のトンネルのポート番号にします。
ローカル側の接続設定はこれで完了です。
ローカル側の接続設定はこれで完了です。
サーバ側PM2の設定ファイルへの追加
PM2の詳細については自サーバでNext.jsアプリを動かす#2 PM2を利用して、快適にNext.jsアプリを動かすを参照してください。
PM2のエコシステムのファイルに環境設定をしています。そこにレプリカDB用のURLを追加します。
PM2のエコシステムのファイルに環境設定をしています。そこにレプリカDB用のURLを追加します。
bash
1vi /home/user/pm2/ecosystem-example.config.js
diff : ecosystem-example.config.js
1module.exports = {
2 apps: [
3 {
4 name: 'example-app',
5 cwd: '/var/www/example.com/html/',
6 script: 'npm',
7 args: 'start',
8 instances: 1,
9 autorestart: true,
10 watch: false,
11 max_memory_restart: '1G',
12 env: {
13 NODE_ENV: 'production',
14 NEXT_PUBLIC_SITE_NAME: 'example',
15 DATABASE_URL: 'postgresql://dbuser:yourpassword@db-master.local:5432/dbname?schema=public',
16+ DATABASE_URL_REPLICA: 'postgresql://dbuser:yourpassword@db-read.local:5432/dbname?schema=public'
17 }
18 }
19 ]
20}
env:{}
の中の各種パラメータはカンマで区切って記述しますので、追加する際はDATABASE_URL
の末尾にカンマを忘れずに。
これで、サーバ側に設定も完了です。ここまでで、準備完了です。ここからが本番です。
2.@prisma/extension-read-replicasのインストール
extension-read-replicas
がPrismaの便利ツールです。Prismaの拡張機能になります。
これは、findMany などの「読み取り」クエリを自動でレプリカにルーティングして、create, update, delete などの「書き込み」は常にマスターへルーティングしてくれます。ローカルPCで作業します。
zsh
1npm install @prisma/extension-read-replicas
extension-read-replicasのマニュアルはhttps://github.com/prisma/extension-read-replicasへ
@prisma/extension-read-replicas を導入すると
Prisma に @prisma/extension-read-replicas を導入すると、readReplicas() という拡張機能が使えるようになります。
ts
1basePrisma.$extends(
2 readReplicas({ url: process.env.DATABASE_URL_REPLICA! })
3)
このように使うことで、下記のように自動で切り替えてくれる便利機能を追加できます!
- 読み取りクエリ(findManyなど)は レプリカDB へ
- 書き込みクエリ(create, update, deleteなど)は マスタDB へ
3.src/lib/database.ts を修正
続けて、レプリカDBと
extension-read-replicas
を利用するための設定をdatabase.ts
に追記します。ここはPrismaを利用する際の記述の仕方が異なるかもしれません。私は下記のようにして、サーバアクションなどでimport { prisma } from "@/lib/database";
として呼び出すようにしています。現状:
ts : database.ts
1import { PrismaClient, Prisma } from "@prisma/client";
2
3// グローバル変数としてPrismaクライアントを定義
4const globalForPrisma = global as typeof globalThis & { prisma?: PrismaClient };
5
6// Prismaクライアントを再利用(開発環境ではHot Reloadの影響を防ぐ)
7export const prisma =
8 globalForPrisma.prisma ||
9 new PrismaClient({
10 log:
11 process.env.NODE_ENV === "development"
12 ? ["query", "info", "warn", "error"]
13 : ["error"],
14 });
15
16// 開発環境ではグローバル変数に設定
17if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
18
19// Prismaの型もエクスポート
20export { Prisma };
上記を下記のように変更します。Prismaの拡張設定でレプリカDBのURLを設定します。
ts : database.ts
1import { PrismaClient, Prisma } from "@prisma/client";
2import { readReplicas } from "@prisma/extension-read-replicas";
3
4const globalForPrisma = global as typeof globalThis & { prisma?: PrismaClient };
5
6const basePrisma = new PrismaClient({
7 log:
8 process.env.NODE_ENV === "development"
9 ? ["query", "info", "warn", "error"]
10 : ["error"],
11});
12
13export const prisma =
14 globalForPrisma.prisma ||
15 (basePrisma.$extends(
16 readReplicas({
17 url: process.env.DATABASE_URL_REPLICA!,
18 }),
19 ) as unknown as PrismaClient);
20
21if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
22
23// Prismaの型もエクスポート
24export { Prisma };
as unknown as PrismaClient
のところが変な感じですが、$extends() の戻り値は完全には PrismaClient と一致しないから、単純な as PrismaClient は TypeScript に拒否されるのを防ぐために、まず unknown にキャストしてから PrismaClient に変換することで、安全であることを明示するようにしています。ここら辺の独特な型チェックにはいつも悩まされます。。$extends() の戻り値は「ちょっと違う型」
問題は $extends() を使ったときに戻ってくるのが、通常の PrismaClient とは厳密にはちょっと違う型であるという点です。
- 通常の PrismaClient は公式が用意した「ちゃんとした型」
- $extends() を使うと、動的に作られたクライアントになって、型が少し違うものになる
型が「微妙に違う」だけで、実際の動作には問題ないのですが、TypeScript はとても厳格なので:
「これは PrismaClient と完全に一致してない!危ないかも!」
と判断して、エラーを出してしまいます。
そこで登場するのがこの一行です。
そこで登場するのがこの一行です。
ts
1(basePrisma.$extends(...) as unknown as PrismaClient)
これは TypeScript に対して:
「この値の型は一旦わからない (unknown) として受け止めておいて。
そのあとで、それが PrismaClient だと信じて扱って大丈夫だよ!」
という意思表示をするテクニックです。
サーバアクションなどで
これは元々ですが、私の場合、下記のように
database.ts
の内容を読み込んで利用しています。tsx
1import { prisma } from "@/lib/database";
2
3const categories = await prisma.category.findMany({・・・・
上記のような内容は全く変更は必要ありません。
@prisma/extension-read-replicas
をインストールして、.$extends
でレプリカDBのURLを読み込むようにする。これだけで、なんと、クエリ種別 | 接続先 |
---|---|
.findMany(), .findUnique() など読み取り | レプリカDB(DATABASE_URL_REPLICA) |
.create(), .update(), .delete() など書き込み | マスターDB(DATABASE_URL) |
という振り分けが自動化されます。Prismaの凄さ!
schema.prisma
や各種サーバアクションなど変更不要です!あとはローカルPCでビルドしたデータをサーバへアップして、PM2を再起動して終わりです。
あっさりしすぎて怖いくらいです。これで念願の負荷分散ができました!
前回の確認はこちら...
この記事の執筆・編集担当
DE
松本 孝太郎
DELOGs編集部/中年新米プログラマー
ここ数年はReact&MUIのフロントエンドエンジニアって感じでしたが、Next.jsを学んで少しずつできることが広がりつつあります。その実践記録をできるだけ共有していければと思っています。
▼ 関連記事
[自サーバでNext.jsアプリを動かす#2]PM2を利用して、快適にNext.jsアプリを動かす
Next.jsで作成したWebサイト・アプリを自サーバで運用するために「PM2」というミドルウェアを利用して、快適に運用して方法のまとめ
2025/6/20公開
![[自サーバでNext.jsアプリを動かす#2]PM2を利用して、快適にNext.jsアプリを動かすのイメージ](/_next/image?url=%2Farticles%2Fnext-js%2Fpm2%2Fhero-thumbnail.jpg&w=1200&q=75)
[自サーバでNext.jsアプリを動かす#3]Nginxのキャッシュゾーン設定
キャッシュゾーンを利用してサーバ負荷を軽減しながらNext.jsで構築したWebアプリケーションを運用
2025/6/20公開
![[自サーバでNext.jsアプリを動かす#3]Nginxのキャッシュゾーン設定のイメージ](/_next/image?url=%2Farticles%2Fnext-js%2Fnginx-cache%2Fhero-thumbnail.jpg&w=1200&q=75)
[自サーバでNext.jsアプリを動かす#1]Node.jsインストール+Nginx設定+デプロイ
Next.jsで作成したWebサイト・アプリを自サーバのNginxで動かすための設定とデプロイ作業の整理
2025/6/20公開
![[自サーバでNext.jsアプリを動かす#1]Node.jsインストール+Nginx設定+デプロイのイメージ](/_next/image?url=%2Farticles%2Fnext-js%2Fnginx-setting%2Fhero-thumbnail.jpg&w=1200&q=75)
[自サーバでNext.jsアプリを動かす#4]セキュリティヘッダと静的アセットのキャッシュ期間の設定
Next.jsで作成したWebサイト・アプリケーションを自サーバで運用するためのNginxのセキュリティヘッダと静的アセットのキャッシュ期間設定を実施
2025/6/20公開
![[自サーバでNext.jsアプリを動かす#4]セキュリティヘッダと静的アセットのキャッシュ期間の設定のイメージ](/_next/image?url=%2Farticles%2Fnext-js%2Fnginx-header%2Fhero-thumbnail.jpg&w=1200&q=75)