مرکزی مواد پر جائیں
友田 陽大
Prisma ORM
Prisma
TypeScript
PostgreSQL
パフォーマンス
型安全

Prisma パフォーマンス最適化ガイド:N+1の撲滅、select/omit、カーソルページング、接続プール、cacheStrategy、TypedSQLまで

Prisma(v7)のパフォーマンスを本番品質に引き上げる実装ガイド。include/selectによるN+1回避とinオペレータ、selectとomit(GA)の取りすぎ防止、オフセットvsカーソルのページング、aggregate/groupBy/_countの集計、driver adapterでの接続プール設定、AccelerateのcacheStrategy(ttl/swr)、ホットパスのTypedSQL(Preview)まで、公式に忠実な実コードと計測起点の最適化順で解説します。

Published
Reading time
10 min read
Author
友田 陽大
شیئر کریں

ORM の「遅い」は、たいてい ORM のせいではありません。N+1、取りすぎ、インデックス欠如、接続の使い方——原因の大半は使い方にあります。そして良いニュースは、それらが型安全を保ったまま、宣言的に直せることです。Prisma v7 は Rustフリー化でクライアント自体も軽量・高速になりました(公式ベンチマークで最大3.4倍高速・バンドル約90%減)が、本当に効くのはアプリ側の最適化です。

この記事は、Prisma(v7)のパフォーマンスを本番品質に引き上げる実装ガイドです。Prisma 全体の運用はPrisma ORM 本番運用ガイド(v7)に、スキーマ側のインデックス設計はスキーマ設計ガイドにあります。本記事は「速くする実装テクニック」を、効く順に並べます。

この記事のルール:API・挙動は Prisma 公式ドキュメント(2026年6月時点、v7系) に基づきます。一部に「Preview(プレビュー)」段階の機能(relationLoadStrategy / TypedSQL)が含まれるため明記します。接続プールの細かな既定値は版で変わるため、本番投入前に必ず公式ドキュメントで確認してください。


0. メンタルモデル:計測してから、効く順に直す

性能改善の鉄則は「推測するな、計測せよ」です。私は PostgreSQL の本番チューニングでも、まず pg_stat_statementsEXPLAIN ANALYZE で重いクエリを特定し、効く順に手を入れます。Prisma でも同じで、当てずっぽうにクエリを書き換える前に、どのクエリが何回・何ms かを掴みます(Prisma のログ、APMのDBスパン、PostgreSQL側の統計)。

直す順番もほぼ決まっています。

① N+1の撲滅 → ② インデックス → ③ 取りすぎ(select/omit)→ ④ 接続プール → ⑤ キャッシュ → ⑥ ホットパスの生SQL。

上ほど「効果が大きく・コストが小さい」。この順で潰すのが最短です。


1. N+1 を撲滅する(最大の効果)

N+1 問題——一覧を1クエリで取った後、各行の関連を1件ずつ引いてクエリが行数分に膨れる現象——は、ORM性能事故の筆頭です。Prisma では関連を include / select で宣言的に取得すれば、行ごとに追加クエリが飛びません。

// ❌ N+1:ユーザー取得後、ループで投稿を引く(1 + N クエリ)
const users = await prisma.user.findMany();
for (const u of users) {
  u.posts = await prisma.post.findMany({ where: { authorId: u.id } }); // 行数分
}

// ✅ include:投稿をまとめて取得(公式いわく「2クエリ」で済む)
const usersWithPosts = await prisma.user.findMany({
  include: { posts: true },
});

公式ドキュメントは、include を使うと「ユーザー取得とポスト取得の2クエリ」で完結すると明言しています。手動ループのように行数に比例して増えることはありません。手動でバッチ取得したい場面では in オペレータも有効です。

// 別の手:関連IDをまとめて1クエリで取る
const userIds = users.map((u) => u.id);
const posts = await prisma.post.findMany({
  where: { authorId: { in: userIds } },
});

発展(Preview):取得時の関連ロード戦略を選ぶ relationLoadStrategy: "join" | "query" があり、"join" は DB の LATERAL JOIN 等で単一クエリ化します。ただし執筆時点では PreviewrelationJoins プレビューフラグが必要)で、将来 join が既定になる予定です。本番採用はフラグと検証を前提に。いずれにせよ include/select を素直に使えば N+1 は回避できます。


2. 取りすぎを止める:select と omit

ネットワーク転送・メモリ・シリアライズのコストは、取得する列とレコード数に比例します。一覧で本文や巨大JSONまで取れば、それだけで遅くなります。

2.1 select:必要な列だけ取る

const list = await prisma.post.findMany({
  select: {
    id: true,
    title: true,
    author: { select: { name: true } }, // ネストでも必要分だけ
  },
});

2.2 omit(GA):既定から機密・巨大列を除外する

select が「足し算(要るものを挙げる)」なのに対し、omit は「引き算(既定から要らないものを抜く)」です。omitPrisma 6.2.0 で GA(v7 ではフラグ不要)です。

// 単発:このクエリだけ password を除外
const user = await prisma.user.findUnique({
  where: { id: 1 },
  omit: { password: true },
});

// グローバル:全クエリで常に password を除外(最も安全)
const prisma = new PrismaClient({
  adapter,
  omit: {
    user: { password: true }, // 既定で漏れなくする
  },
});

設計のコツ(セキュリティ)passwordHash のような機密列はグローバル omit で既定から外しておくのが安全側です。「select し忘れたら漏れる」ではなく「明示的に取りに行かない限り出ない」状態を作る。取りすぎ防止は、性能と機密最小化を同時に満たします。


3. ページング:深いページはカーソルへ

ページングには2方式あり、データ規模で選びます

// オフセット:実装は単純だが、深いページほど遅くなる
const page = await prisma.post.findMany({ skip: 20, take: 10 });

// カーソル:大規模データで安定・高速
const firstPage = await prisma.post.findMany({
  take: 10,
  orderBy: { id: "asc" },
});
const last = firstPage.at(-1);
const nextPage = last
  ? await prisma.post.findMany({
      take: 10,
      skip: 1, // カーソル行自身を除外
      cursor: { id: last.id },
      orderBy: { id: "asc" }, // 安定した並び順が必須
    })
  : [];

公式の指針どおり、オフセットは「オフセットが大きくなるほど高コスト」になります。「ページ番号でジャンプしたい管理画面」にはオフセット、「無限スクロールやフィード、巨大データ」にはカーソル——と使い分けます。カーソルは安定した orderBy(一意なキー)が前提です。


4. 集計は DB に寄せる:aggregate / groupBy / _count

「アプリで全件取ってから数える/合計する」のは最悪です。集計はDBにやらせます。

// 集計:平均・合計などはDB側で
const stats = await prisma.user.aggregate({
  _avg: { age: true },
  _count: true,
  where: { active: true },
});

// グループ集計:having で集計値にフィルタ
const byCountry = await prisma.user.groupBy({
  by: ["country"],
  _sum: { profileViews: true },
  having: { profileViews: { _avg: { gt: 100 } } },
});

// 関連の件数:_count で「投稿数」をDB側で数える(投稿本体は取らない)
const usersWithCounts = await prisma.user.findMany({
  select: {
    name: true,
    _count: { select: { posts: true } }, // フィルタ付きも可
  },
});

_count は「ユーザー一覧に投稿数だけ出したい」という頻出要件を、投稿本体を取らずに満たします。一覧で関連を丸ごと include して .length する、というアンチパターンを避けられます。distinct で重複排除もDB側で可能です。


5. 接続プール:v7 は driver adapter で設定する

接続管理は、特にサーバーレスで性能と安定性を左右します。**ここは v6 から大きく変わりました。**v6 では接続文字列のクエリパラメータ(connection_limit など)で設定しましたが、v7 では driver adapter のコンストラクタで設定します。

import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL,
  // プールサイズ・タイムアウトは adapter(=ドライバ)側のオプションで
  // 例(pg ドライバ):max(プール上限), connectionTimeoutMillis, idleTimeoutMillis
});

export const prisma = new PrismaClient({ adapter });

要点。

  • プールサイズ・タイムアウトは adapter 側pg なら max / connectionTimeoutMillis / idleTimeoutMillis)。旧来の DATABASE_URL?connection_limit=... は v7 では使いません。
  • プールサイズはDBの上限から逆算する:max_connections を、稼働しうるインスタンス数 × プールサイズが超えないように設計します。サーバーレスでインスタンスが大量に立つ構成では、これが接続枯渇の主因です。
  • 具体的な既定値は版・ドライバで動くため、自環境で adapter のオプションを明示し、負荷試験で詰めるのが安全です。

サーバーレスの定石:多数の関数が直接DBを叩くなら、プーラ(PgBouncer)か、接続プール内蔵の Prisma Postgres / グローバルプールの Prisma Accelerate を前段に置きます(→ §6)。詳しくはNext.js × Prisma 実装ガイド


6. キャッシュ:Accelerate の cacheStrategy(ttl/swr)

読み取りが重い・同じ結果を何度も返す箇所は、キャッシュでDBそのものを叩かないのが最も効きます。Prisma Accelerate を使うと、クエリ単位で cacheStrategy を指定できます。

import { withAccelerate } from "@prisma/extension-accelerate";

const prisma = basePrisma.$extends(withAccelerate());

const posts = await prisma.post.findMany({
  where: { published: true },
  cacheStrategy: {
    ttl: 60, // 60秒は無条件にキャッシュを返す(DBを叩かない)
    swr: 60, // ttl切れ後60秒は古い値を返しつつ背後で再取得
    tags: ["published_posts"], // タグで対象を絞って無効化
  },
});
  • ttl(time-to-live):この秒数内のヒットはDBクエリなしでキャッシュを返します。
  • swr(stale-while-revalidate)ttl 切れ後この秒数内は、古い値を即返しつつ裏で更新。レイテンシを犠牲にせず鮮度を回復します。
  • tags:関連するキャッシュをまとめて無効化できます。

整合性の線引き(重要)cacheStrategyキャッシュしてよい読み取りにだけ付けます。残高・在庫・権限のような「常に最新が要る」読み取りには付けない。ttl/swr の値は「どれだけ古くてよいか」というビジネス判断です。


7. ホットパス:TypedSQL(Preview)で型安全な生SQL

ORM では表現しづらい複雑な集計やDB固有最適化が要るホットパスには、生SQLを使います。Prisma の TypedSQL は、.sql ファイルからパラメータと結果行の両方に型が付いた関数を生成します(Preview 機能、typedSql フラグが必要)。

-- prisma/sql/topAuthors.sql
-- @param {Int} $1:minPosts
SELECT u.id, u.name, COUNT(p.id) AS post_count
FROM "User" u
JOIN "Post" p ON p."authorId" = u.id
GROUP BY u.id
HAVING COUNT(p.id) >= $1
ORDER BY post_count DESC;
import { topAuthors } from "./generated/prisma/sql";

// 戻り値も引数も型付き。手書きの $queryRaw より安全
const rows = await prisma.$queryRawTyped(topAuthors(5));

TypedSQL が Preview のうちは、通常の $queryRaw(タグ付きテンプレートで必ずパラメータ化)でも安全に書けます。$queryRawUnsafe にユーザー入力を渡さない——この一線は生SQLでも不変です(→Prisma 本番運用ガイド §8)。

可観測性:Prisma Postgres には、遅いクエリを可視化する Query Insights が同梱されます(かつての Prisma Optimize は Query Insights に置き換えられました)。本番のスロークエリは「勘」ではなく、こうした計測で特定して §0 の順で潰します。


8. パフォーマンス最適化チェックリスト

効く順に並べた、本番前の確認項目です。

  • まず計測:遅いクエリを特定(Prismaログ/APM/PostgreSQL統計/Query Insights)
  • N+1を撲滅:ループ内クエリを include/select、またはバッチの in に置換
  • インデックス:検索条件・並び替え・FK列に @@index(→スキーマ設計
  • 取りすぎ防止select で必要列だけ、機密・巨大列はグローバル omit
  • ページング:深いページはオフセットからカーソルへ
  • 集計はDB側aggregate/groupBy/_count/distinct を使い、全件取得して数えない
  • 接続プール:v7 は adapter 側で設定。max_connections から逆算して枯渇を防止
  • キャッシュ:キャッシュ可能な読み取りに cacheStrategy(ttl/swr)。最新必須データには付けない
  • ホットパス:必要なら TypedSQL(Preview)。生SQLは必ずパラメータ化
  • バルク操作(createMany/updateMany/deleteMany)でループ書き込みを置換

まとめ

Prisma を速くする道筋は、ORM の魔法ではなく規律です。計測してから、N+1 → インデックス → 取りすぎ → 接続 → キャッシュ → 生SQL の順で潰す。include/select で N+1 を消し、omit で取りすぎと機密漏れを止め、カーソルで深いページを救い、集計はDBに寄せ、接続は adapter で管理し、キャッシュは整合性を見極めて使う——どれも型安全を保ったまま実行できます。

遅いと言われ続けているアプリのDB層を、計測起点で本番品質まで引き上げたい」——スロークエリの特定からスキーマ・クエリ・接続・キャッシュの最適化まで、効く順に手を入れるお手伝いができます。PostgreSQL 本体のチューニングと合わせれば、体感は大きく変わります。

友田

友田 陽大

وزیر (METI) ایوارڈ یافتہ پروڈکٹ کے ڈویلپر۔ TypeScript + Python + AWS کے ساتھ میں SaaS، صنعتی ڈیجیٹل تبدیلی اور پروڈکشن کے قابل جنریٹو اے آئی (RAG) تنہا ابتدا سے انتہا تک فراہم کرتا ہوں — تقاضوں کی تعیین سے انفراسٹرکچر اور آپریشن تک۔

Got a challenge?

From design to implementation and operations — solo × generative AI

Implementation like this article's, end to end from requirements to production. Start with a free 30-minute technical consult and tell me about your situation.

پروجیکٹ کی بنیاد پر کام اور تکنیکی مشاورت دونوں کے لیے دستیاب ہوں۔ 30 منٹ کی مفت مشاورت سے آغاز کریں۔

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

یہ بھی پڑھنے کے قابل