ORMやクエリビルダを使っていると、アプリが実際にデータベースへ何を投げているかは意外と見えません。 ログレベルを上げる、ドライバにフックを差す、デバッグビルドを作る——どれも手間で、本番に近い構成だと余計に面倒です。 sql-tapは、その「裏で流れているSQL」をアプリ無改修で生のまま覗くためのGo製OSSです。
30秒で理解する sql-tap
・アプリとDBの間に挟む透過プロキシ(sql-tapd)+リアルタイム表示クライアント(TUI/Web UI)の二段構成。Go製・MIT・2026年6月時点で1,500★超
・接続先ポートをプロキシに向け替えるだけで全クエリを表示。アプリのコード変更もORM設定変更も不要
・対応DBはPostgreSQL/MySQL/TiDB。ネイティブのワイヤープロトコルを解釈してクエリ・トランザクション・実行時間・エラーを取り出す
・N+1検出・スロークエリ検出・EXPLAIN/EXPLAIN ANALYZE・タイムライン表示を内蔵。sql-tap -ci でCIのリグレッション検出にも使える
まずは動いている様子を見るのが早いので、公式READMEのTUIデモを冒頭に置きます。 左ペインでプロキシ経由のアプリを動かし、右ペインのsql-tapがクエリを次々と拾っていく流れです。
公式リポジトリは mickamy/sql-tap です。 作者はTokyo拠点のGoエンジニア Tetsuro Mikami(mickamy)氏で、プロフィールに「開発生産性・可観測性・コード品質にフォーカスしたOSSをGoエコシステムで作る」とあります。 本記事は公式READMEを一次ソースに、何ができて何ができないかを実務目線で整理します。
sql-tapとは何か——SQLトラフィックを経路上で覗くプロキシ型ビューア
sql-tapは「リアルタイムSQLトラフィックビューア」をうたうツールで、役割の違う2つのバイナリで構成されます。
・sql-tapd:アプリとDBの間に座る透過プロキシ(デーモン)。経路を流れるクエリを捕捉する側
・sql-tap:sql-tapdにgRPCで接続し、捕捉されたクエリを表示するクライアント(ターミナルUI)
アプリは「DBに直接つないでいるつもり」のままプロキシ越しに通信し、その通信内容をsql-tapが横から表示する、という構図です。 プロキシはPostgreSQL・MySQL・TiDBのワイヤープロトコルを解釈するため、アプリ側のコードもORMの設定も変える必要がありません。 READMEの表現を借りれば「アプリのコードを変えずに、クエリを調べ、トランザクションを見て、EXPLAINを実行できる」のが核です。
捕捉できるのは単発のクエリだけではありません。 プリペアドステートメント、パラメータバインディング、トランザクション(BEGIN/COMMIT/ROLLBACK)、実行時間、影響行数、エラーまで追跡します。 つまり「ORMが裏で何回・どんな順序でクエリを発行し、どのトランザクションに束ねたか」を、ログを仕込まずに観察できるわけです。
ポイント:sql-tapは「アプリを変えて計測する」のではなく「経路に1段挟んで覗く」発想です。 だからこそ本番に近い構成のまま、ORMやドライバの素の挙動を観察できます。
名前の由来と立ち位置
「tap」はネットワークの世界で通信を分岐して覗く「タップ」のニュアンスです。 作者は同じ発想をgRPCに適用した grpc-tap(HTTP/2リバースプロキシでgRPC/gRPC-Web/Connect呼び出しを捕捉)も公開しており、sql-tapはその「SQL版」と捉えると位置づけが分かりやすくなります。
ギャラリー——TUIとWeb UIの実画面
実際の画面をいくつか見ておきます。
まずプロキシを立ち上げてアプリをつなぐと、左のsql-tapは最初「Waiting for queries…」と待機し、右のアプリがプロキシ(:5433)経由でクエリを投げ始めると拾い始めます。
クエリが流れ始めると、リストビューにBegin/Execute/Commitやロールバックが実行時間つきで並びます。
Enter で1件を選ぶと下部のインスペクタにクエリ全文・バインド値・メタデータが表示され、その場で x(EXPLAIN)や X(EXPLAIN ANALYZE)を叩けます。
--http=:8080 を付けて起動するとブラウザ版も使えます。
Web UIはSSE(Server-Sent Events)でクエリをストリーム表示し、クリックで詳細を開く、テキストでフィルタする、N+1をトースト+行ハイライトで知らせる、Gantt風のタイムラインで見る、といった操作に対応します。
TUIとWeb UIは排他ではなく、N+1検出のようなコアな処理はサーバー側(sql-tapd)で動くため、どちらのクライアントからでも同じ検出結果の恩恵を受けられます。
アーキテクチャ・しくみ——なぜ無改修で覗けるのか
仕組みはシンプルです。 アプリはDBではなくsql-tapdに接続し、sql-tapdが本物のDBへ中継します。 その中継の途中でワイヤープロトコルを解釈し、クエリを取り出してクライアントへgRPC(TUI)やSSE(Web)で配信します。
(接続先を
プロキシに向ける)"] -->|native wire protocol| TAPD["sql-tapd
透過プロキシ
クエリを解釈・捕捉"] TAPD -->|中継| DB[("PostgreSQL /
MySQL / TiDB")] TAPD -->|gRPC stream| TUI["sql-tap(TUI)"] TAPD -->|SSE| WEB["ブラウザ(Web UI)"]
ポイントは、sql-tapdがDBと「同じ言葉(ワイヤープロトコル)」を話すことです。 アプリから見ればプロキシは本物のDBと区別がつかず、DBから見ればプロキシは普通のクライアントに見えます。 この透過性があるからこそ、ドライバの差し替えやコード変更なしに割り込めるわけです。
捕捉時には単にSQL文字列を読むだけでなく、プリペアドステートメントの登録・パラメータの束縛・トランザクションの開始終了・実行時間・影響行数・エラーまでを状態として追跡します。
これが重要なのは、現代のドライバの多くがプリペアドステートメントを使うため、ログには $1 ? のようなプレースホルダだけが残り、実際にどんな値が渡ったか分からないことが多いからです。
sql-tapdはParse(文の登録)とBind(値の束縛)を対応づけて追うので、C(バインド値込みコピー)で「実際に実行された形」のクエリを取り出せます。
トランザクションも単発クエリの羅列ではなく、BEGIN〜COMMIT/ROLLBACKの束として扱われます。
TUIでは Space でトランザクションを折りたためるため、「この1リクエストが何個のトランザクションに分かれ、各トランザクションに何本のクエリが入っているか」を構造として読めます。
リトライで同じトランザクションが二重に走っていないか、想定外の早期COMMITがないか、といった切り分けに効きます。
EXPLAINを動かす際は、DATABASE_URL(または -dsn-env で指定した環境変数)のDSNを使って本物のDBへEXPLAINを発行します。
このDSNを与えなければクエリの捕捉自体は続きますが、EXPLAIN系の機能だけが無効になります。
捕捉とEXPLAINが別経路(捕捉は中継トラフィック、EXPLAINはDSNで張る別接続)になっているのは、観察対象のトランザクションを汚さずにEXPLAINを撃つための設計と読めます。
3つの登場人物を整理
・アプリ:接続先をプロキシのポートに向けるだけ。それ以外は何も変えない
・sql-tapd:プロキシ本体。クエリ捕捉・N+1検出・スロー検出・EXPLAIN実行はここで動く
・sql-tap / ブラウザ:捕捉結果を見るだけのクライアント。表示と対話に専念する
インストールと起動
配布形態が豊富で、Homebrew・Go・ソースビルド・Nix・Dockerから選べます。 手元に入れるだけならHomebrewが手軽です。
# Homebrew
brew install mickamy/tap/sql-tap
# Go(クライアントとデーモンを両方入れる)
go install github.com/mickamy/sql-tap@latest
go install github.com/mickamy/sql-tap/cmd/sql-tapd@latest
# Nix(flakeを直接)
nix profile install github:mickamy/sql-tap
CIやコンテナ環境ではDockerでデーモンをサイドカーとして動かす形が便利です。
READMEはalpineベースで sql-tapd を取り込むDockerfile例を示しており、--network=host でDBの隣に並べます。
# PostgreSQLの隣でデーモンを動かす(プロキシ:5433、gRPC:9091)
docker run --rm --network=host sql-tap \
--driver=postgres --listen=:5433 --upstream=localhost:5432 --grpc=:9091
起動は「デーモンを立てる → アプリをプロキシに向ける → クライアントで覗く」の3ステップです。
# 1. プロキシデーモンを起動(PostgreSQL例)
DATABASE_URL="postgres://user:pass@localhost:5432/db?sslmode=disable" \
sql-tapd --driver=postgres --listen=:5433 --upstream=localhost:5432
# 2. アプリの接続先を 5432 → 5433 に変える(コード変更は不要、接続文字列のポートだけ)
# 3. TUIを起動して覗く(引数はsql-tapdのgRPCアドレス)
sql-tap localhost:9091
毎回フラグを並べたくない場合は、プロジェクト直下に .sql-tap.yaml を置けば自動で読み込まれます。
ドライバ・リッスンアドレス・upstream・スローやN+1の閾値などをまとめて宣言でき、CLIフラグはこのファイルの値を上書きします。
driver: postgres
listen: ":5433"
upstream: "localhost:5432"
grpc: ":9091"
http: ":8080"
dsn_env: DATABASE_URL
slow_threshold: 100ms
nplus1:
threshold: 5
window: 1s
cooldown: 10s
基本的な使い方——絞り込み・EXPLAIN・エクスポート
TUIはvim風のキーバインドで操作します。
j/k で移動、Enter でインスペクタ、Space でトランザクションの展開折りたたみ、q で戻る、という具合です。
よく使うのは検索と構造化フィルタで、/ がインクリメンタルなテキスト検索、f が条件式によるフィルタです。
フィルタは単なる文字列一致を超えた条件を書けます。
スペース区切りの複数トークンはAND結合され、テキスト検索(/)と同時に効かせることもできます。
| 構文 | 意味 | 例 |
|---|---|---|
d>100ms |
実行時間がこれより長い | d>1s、d>500us |
d<10ms |
実行時間がこれより短い | d<50ms |
error |
エラーを含むイベントのみ | |
n+1 |
N+1フラグの付いたクエリ(別名 nplus1) |
|
slow |
スロークエリのみ | |
op:select |
SQLキーワード前方一致 | op:insert、op:update、op:delete |
op:begin |
プロトコル操作 | op:commit、op:rollback |
たとえば op:select d>100ms と書けば「100msを超えたSELECTだけ」に絞り込めます。
気になるクエリを見つけたら、その場で x のEXPLAINや X のEXPLAIN ANALYZEを実行できますし、e/E でクエリを編集してから実行することもできます。
c でクエリをコピー、C でバインド値込みのコピー、w で表示中のクエリをJSON/Markdownへエクスポートできるため、調査結果をそのままチケットやPRに貼れます。
地味に効く機能:a のAnalyticsビューは同じクエリテンプレートを合計時間・回数・平均でソートでき、t のTimelineビューはクエリの重なりをGantt風に俯瞰できます。
「遅い1本」だけでなく「塵も積もる多発クエリ」を見つけやすくなります。
リストビューの主要キーは下表のとおりです。
vimに馴染みがあればほぼ説明なしで使えますが、調査でよく叩くのは検索(/)・フィルタ(f)・並び替え(s)・EXPLAIN(x/X)・エクスポート(w)あたりです。
| キー | 動作 |
|---|---|
j / k | 下/上に移動 |
Ctrl+d / Ctrl+u | 半ページ送り |
/ | インクリメンタルなテキスト検索 |
f | 構造化フィルタ(条件式) |
s | ソート切替(時系列/実行時間) |
Enter | クエリ/トランザクションを検査 |
Space | トランザクションの展開・折りたたみ |
x / X | EXPLAIN / EXPLAIN ANALYZE |
e / E | クエリを編集してからEXPLAIN / ANALYZE |
a / t | Analyticsビュー / Timelineビュー |
c / C | クエリをコピー / バインド値込みでコピー |
w | クエリをファイルにエクスポート(JSON/Markdown) |
q | 終了 / 1つ前のビューに戻る |
インスペクタ・Analytics・Timeline・Explainの各ビューにも個別のキーマップがありますが、いずれも j/k で移動し q で戻る共通作法なので、最初に覚えるのはこのリストビューの表だけで十分です。
ユースケース——ORMデバッグ/CIのリグレッション検出/本番調査
sql-tapが効く局面は、大きく3つに分けられます。
1. 開発中のORMデバッグ
「この一覧表示、なんだか遅い」というとき、ORMが裏で何本のクエリを投げているかを即座に確認できます。
プロキシ経由でアプリを動かし、画面を1回操作してTUIを見れば、N+1が N+1 マーカーで一目で分かります。
EagerローディングやJOINへの書き換え効果も、同じ画面の前後比較で検証できます。
2. CIでのクエリリグレッション検出
sql-tap -ci はCI向けのモードです。
sql-tapdに接続してイベントを集め続け、SIGTERM/SIGINTで停止すると、N+1や遅いクエリが見つかった場合に終了コード1を返します。
テストをプロキシ経由で走らせ、終了時にレポートを出すことで「PRでN+1が増えていないか」を機械的にチェックできます。
# CIモードでバックグラウンド起動
sql-tap -ci localhost:9091 &
CI_PID=$!
# テストをプロキシ(:5433)経由で実行
DATABASE_URL="postgres://user:pass@localhost:5433/db?sslmode=disable" go test ./...
# 停止するとレポートを出し、問題があれば exit 1
kill "$CI_PID" 2>/dev/null || true
wait "$CI_PID" 2>/dev/null || true
レポートは「捕捉142クエリ/N+1とSLOWの内訳/Exit: 1」のように要約されるため、失敗時にどのクエリが原因かをログから追えます。
3. ステージング・調査時の本番に近い観察
ドライバのデバッグログを有効化しづらい環境でも、プロキシを1段挟むだけで素の挙動を観察できます。 トランザクション境界や、リトライ時に同じクエリが二重発行されていないか、といった「ログだけでは追いにくい」事象の切り分けに向きます。
類似OSS比較——リアルタイム型かバッチ集計型か
SQL可視化・解析の定番ツールと並べると、sql-tapの立ち位置が見えてきます。 pgBadgerやpt-query-digestは「DBが吐いたログを後から集計する」バッチ型、ProxySQLは「本番のトラフィックを捌く」プロキシで可視化は副次的、というように目的が異なります。
| ツール | 方式 | 対象DB | リアルタイム性 | 主目的 | 導入の重さ |
|---|---|---|---|---|---|
| sql-tap | 経路プロキシ+TUI/Web | PostgreSQL / MySQL / TiDB | ◎ いま流れる様子を表示 | 開発時デバッグ・N+1検出・CI | 軽(接続先を向け替えるだけ) |
| pgBadger | ログ解析バッチ | PostgreSQL | × 事後レポート | 定常的なログ集計・統計レポート | 中(ログ出力設定が前提) |
| pt-query-digest | ログ/キャプチャ解析 | MySQL系 | △ 事後集計が中心 | スロークエリの集計・正規化 | 中(slow log等の準備) |
| ProxySQL | 本番用プロキシ | MySQL系 | △ 統計は取れるが用途は運用 | ルーティング・負荷分散・運用 | 重(運用基盤として設計) |
ざっくり言えば、sql-tapは「いま流れているクエリを生で対話的に見たい」局面に最適化されています。 逆に「1日分のスロークエリを統計レポートにまとめたい」「本番トラフィックを恒常的に集計したい」なら、pgBadgerやpt-query-digest、APM製品の方が向きます。 どちらかではなく、開発・調査はsql-tap、定常監視は既存ツール、という併用が現実的です。
対話的に観察| RT["リアルタイム型
sql-tap"] Q -->|過去ログを
統計レポート化| BATCH["バッチ集計型
pgBadger / pt-query-digest"] Q -->|本番トラフィックの
ルーティング・運用| OPS["運用プロキシ型
ProxySQL"]
制限と注意点
便利な一方で、性質上の前提と既知の制限があります。
・経路に1段挟む:プロキシを通すぶん、ごくわずかなオーバーヘッドと、プロキシ自体の死活が経路に乗ります。常時本番に挟みっぱなしにするより、開発・テスト・調査での利用が素直です
・対応DBは3種:PostgreSQL・MySQL・TiDBのみ。SQLiteや他のRDBMSは対象外です
・N+1検出はSELECT限定:INSERT/UPDATE/DELETEやトランザクション制御は対象外で、FROMを伴わないメタクエリ(SELECT 1、SELECT @@version 等)はヘルスチェック扱いで除外されます。あくまで「アプリのデータ取得が多発していないか」を見る機能です
・EXPLAINにはDSNが必要:DATABASE_URL(または -dsn-env 指定の環境変数)を与えないとEXPLAIN系が無効になります。捕捉自体は続きます
・TUI入力の既知の制約:Bubble Tea v1のターミナル入力パーサの制約により、検索・フィルタ入力中に限り [A [B [C [D [F [H という2文字のリテラルが入力できません。矢印キーの分割シーケンス対策の副作用で、SQL検索では実用上ほぼ影響しないとREADMEは説明しています
運用上の勘どころ:sql-tapは「観測のための一時的な割り込み」と捉えると失敗しません。 本番の恒常監視を置き換えるものではなく、スロークエリログやAPMと役割を分けて併用するのが安全です。
まとめ——「裏で流れるSQL」を最短で可視化する一手
sql-tapは、アプリとDBの間にプロキシを1段挟むだけで、ORMやドライバが裏で投げているSQLをリアルタイムに覗けるGo製OSSです。 コード変更なしで全クエリ・トランザクション・実行時間・エラーを追え、N+1とスロークエリの検出、EXPLAIN、タイムライン、CIモードまで揃っているため、開発時のデバッグからPRごとのリグレッション検出まで一気通貫で使えます。 PostgreSQL・MySQL・TiDBを使っていて「この処理、裏で何本クエリ投げてる?」が気になったら、まずプロキシを立てて1画面操作してみるのが最短の確認方法です。
図解やダイアグラムでアーキテクチャを整理したい人は、自然言語から技術図を起こす fireworks-tech-graphとは|自然言語から8スタイルの技術ダイアグラムを生成するOSS も合わせて読むと、こうした「経路に挟む」系ツールの構成図づくりが楽になります。
参照ソース
・mickamy/sql-tap — GitHubリポジトリ(README全文・インストール・使い方・キーバインド・N+1検出・How it works)
・mickamy/grpc-tap — 同コンセプトのgRPC版(sql-tapの立ち位置の参考)
・charmbracelet/bubbletea — TUIの基盤フレームワーク(既知の入力制約の出典)