🙆
Go で N+1 クエリを検知する軽量ライブラリ「nplusone」を作った話
この記事は ChatGPT を使って書かれた
Ruby の bullet に触発されて、Go 向けに N+1 クエリを検知するためのライブラリ nplusone を開発した。この記事では、設計の考え方と使い方を簡潔に紹介する。
背景
Rails を使っていた頃、N+1 クエリの検知には bullet が便利だった。Go に移行してからは ORM を使わない構成が多く、同様の検知が難しいと感じた。ORM に依存せず、database/sql ドライバを直接ラップする形で検知できる軽量な仕組みを目指したのが nplusone だ。
コンセプト
- トランザクション単位で SQL の呼び出しを集計する
- 同じ SQL 形(正規化済み)が複数回、異なる引数で実行されたら N+1 とみなす
- Commit 時に検知・報告・失敗のいずれかを選べる
Go の標準的な database/sql API に完全準拠しており、pgx, mysql, sqlite などどのドライバでも利用できる。
インストール
go get github.com/mickamy/nplusone
使い方
conn := nplusone.WrapConnector(stdlib.GetConnector(*cfg), nplusone.Config{
Mode: nplusone.FailOnCommit,
NPlus1Threshold: 2,
MinDistinctArgs: 2,
OnDetect: func(d nplusone.Detection) {
log.Printf("N+1 detected: %+v", d)
},
})
db := sql.OpenDB(conn)
defer db.Close()
ctx := context.Background()
tx, _ := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
_ = simulate(ctx, tx)
_ = tx.Commit()
トランザクション内で同じ SQL を異なる引数で実行した場合、Commit 時に検知されてエラーが返る。
2025/11/11 14:42:17 N+1 detected: {TxID:1731300134916863000 Findings:[{NormalizedSQL:SELECT id, title FROM posts WHERE user_id = ? Count:3 DistinctArgs:2}]}
2025/11/11 14:42:17 commit: nplusone: N+1 suspected in transaction
まとめ
- Go でも bullet のように N+1 を検知できる
- ORM なしでも動作する軽量設計
- トランザクション単位で安全に集計・報告できる
興味があれば GitHub リポジトリ を覗いてほしい。
Discussion