Supabase 無料枠の egress 5GB を 10.26GB で超過した話 — 原因特定と削減対応の全記録
Supabase の egress を意識したことがあるでしょうか。
無料プランは月 5GB 制限。Next.js から Supabase を叩いているだけなら「そんなに出ないでしょ」と思いがちです。私もそう思っていました。
2026-05-31、KOBO プロジェクト(issue-finder)のダッシュボードを開いたとき、egress の数字が 10.26GB になっていました。無料枠の 2 倍超。205% の超過です。
結論
5 秒 polling × select=*(重い列含む)が egress を溶かした。
collection-queue-form というフォームコンポーネントが、5 秒ごとに if_jobs テーブルを select=* でポーリングしていました。このテーブルには raw_input_text(最大 20 万字)が含まれており、1 回のリクエストで大量のバイトが Vercel Function を経由して流れ続けていました。
修正は 3 段階で止血。commit d946719 で egress の増加を抑制しました。
この記事の前提
- プロジェクト: KOBO(issue-finder)— Next.js + Supabase で構築した個人用ツール
- デプロイ先: Vercel(Functions 経由で Supabase を叩く構成)
- 超過確認日: 2026-05-31
- 超過量: 5GB 枠に対し 10.26GB(前月比での蓄積、dev_journal ID: dc24db57)
- Supabase プラン: 無料(Free)
「それ俺だ」になるパターン
以下のどれかに当てはまるなら、同じ問題が起きる可能性があります。
- フォームの状態管理に
setIntervalかuseEffectポーリングを使っている - Supabase の query を
select('*')と書いている(全列取得) - テーブルにテキスト系の大きな列(JSON、本文、ログ)がある
- Vercel などサーバーレス Function 経由で Supabase を叩いている
特に「Vercel Function 経由」は盲点です。ブラウザ → Vercel Function → Supabase という構成だと、egress は Vercel 側から Supabase への通信量としてカウントされます。クライアントから直接叩く構成より egress が出やすい。
実際に起きたこと
2026-05-31 の作業中、Supabase ダッシュボードで egress の数字を確認したことで発覚しました。
collection-queue-form(コレクション登録キューを操作するフォーム)が犯人でした。このコンポーネントは 5 秒ごとに if_jobs テーブルをポーリングし、ジョブの完了状態を確認していました。
問題は select=* で全列を取得していた点です。if_jobs テーブルには raw_input_text(ユーザーが入力した生テキスト、最大 20 万字)が含まれており、ジョブ完了チェックのためだけに毎回この列を引っ張っていました。
5 秒 × 20 万字 × 1 日の稼働時間 = 意識しないまま数 GB が消える計算です。加えて、ブラウザタブを裏に回した状態でも polling が止まらない実装だったため、稼働時間が長くなっていました。
原因分析
根本は 「取得列の設計を後回しにした」 ことです。
開発初期は select=* で全列取得するのが最速です。「あとで絞ればいい」と思ってそのままにする。ジョブ完了チェックなら id, status だけで十分なのに、raw_input_text を毎回引っ張っていた。
高頻度 polling と重い列が組み合わさると、ローカル開発中は気づけません。Vercel の本番環境で動き続けて初めて egress として現れます。
Supabase の無料枠では egress アラートが設定できないため(計測中 2026-06-17 時点)、ダッシュボードを手動で確認しないと超過後に気づく構造になっています。
3 段階の止血対応(commit d946719)
1. 生成列 has_raw_input を追加してフラグ化
if_jobs テーブルに has_raw_input boolean GENERATED ALWAYS AS (raw_input_text IS NOT NULL) STORED の生成列を追加しました。フォームがジョブの「本文あり/なし」を確認したいだけなら、20 万字の本文を運ぶ必要はありません。フラグ 1 bit で足ります。
2. select=* を軽量列に絞る
list 系の 3 箇所(キュー一覧、ステータス確認、完了チェック)のクエリを以下のように変更しました。
// Before
const { data } = await supabase.from('if_jobs').select('*')
// After
const { data } = await supabase
.from('if_jobs')
.select('id, status, has_raw_input, created_at, updated_at')
raw_input_text が必要な画面(詳細表示)では個別に取得する設計に変更。一覧取得と詳細取得を分けることで、不要なデータを流さなくなりました。
3. polling 間隔を 5s → 30s + 裏タブ停止
setInterval の間隔を 5 秒から 30 秒に変更。加えて document.addEventListener('visibilitychange', ...) で裏タブになったときに polling を止める処理を追加しました。
useEffect(() => {
let interval: ReturnType<typeof setInterval> | null = null
const startPolling = () => {
interval = setInterval(fetchQueueStatus, 30_000)
}
const stopPolling = () => {
if (interval) clearInterval(interval)
}
document.addEventListener('visibilitychange', () => {
document.hidden ? stopPolling() : startPolling()
})
startPolling()
return () => stopPolling()
}, [])
判断軸:同じ問題が起きる条件
| 条件 | リスク |
|---|---|
select=* + テキスト列あり | 高 |
| polling 間隔 10 秒以下 | 高 |
| Vercel Function 経由の構成 | 中(直接より egress が出やすい) |
| 裏タブでも polling が止まらない | 中(稼働時間が増える) |
| egress アラートなし | 高(超過後に気づく) |
Supabase 無料枠の 5GB は「少量のデータをたまに取得する」想定です。リアルタイム性を求めるフォームに polling を使うなら、取得列の設計が先です。
で、どう稼ぐ?
この件で学んだのは、「動く」と「コストが適切」は別の問題だということです。
Next.js + Supabase で個人開発ツールを作るとき、開発速度を優先して select=* のまま進めるのは普通のことです。問題は「後で直せばいい」が後回しになり続けることで、本番環境で気づかずコストが膨らむ。
今回の 205% 超過は無料枠の話でしたが、有料プランに移行後に同じ設計のままスケールしたら課金額が一気に跳ね上がります。個人開発ツールを AI に任せる設計を組む際、データ取得の粒度設計はインフラ設計と同じレイヤーとして扱う必要があります。
Supabase の egress を意識した設計パターン(生成列活用、軽量 select の切り分け、visibilitychange 連動)は、AI に実装を任せる際の指示書(CLAUDE.md)にあらかじめ書いておくと同じ事故を防げます。
今日やること(3 つだけ)
- 自分のプロジェクトで
select=*を使っている箇所を grep する —grep -rn "select\('\*'\)" src/で一覧化し、テキスト列が含まれるテーブルを特定する - Supabase ダッシュボードの egress 数値を今すぐ確認する — 無料枠の何% を消費しているか把握する(月次 billing cycle に注意)
- polling があるなら visibilitychange で止める処理を追加する — 裏タブ中の polling 停止だけで稼働時間が大幅に減る
masatoman のメルマガ — 毎週月曜の朝に手紙を 1 通
masatoman.net の今週の記事 1 本を、読者目線で深掘りした手紙が毎週月曜 9:00 に届きます。「これ自分のことだ」が見つかる予告編。登録特典に「個人開発の収益化チェックリスト 15 項目」。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
Next Step
次に読むならこの導線です
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第10回】Claude Code × Supabase で管理画面を30分で生成する Skill 実装ガイド
Supabase のテーブルを参照して管理画面を自動生成する Skill を、実コード付きで解説。Claude Code が DB スキーマから Next.js 管理画面を30分で生成する仕組みを公開します。
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
Claude Code で 0→MVP を1日で作る全記録 — recipe-ai Build in Public
Claude Codeを使い、YouTube料理動画からレシピを自動抽出するAIアプリ「recipe-ai」を0からMVPまで1日で構築した全記録。CLAUDE.md設計、API実装、Supabase連携、Vercelデプロイ、Stripe課金導入までの工程を時系列で公開。
登録特典:ブログ解析を Claude Code に丸投げする仕組み(8ステップ・15分)
GSC と GA4 から毎週自動でアクセス数・検索順位を取って Claude Code に分析させる、実運用中のセットアップ完全ガイドを登録後すぐにお送りします。+個人開発の収益化チェックリスト15項目も。あわせて毎週月曜、今週の記事1本を「悩み→結論→原因→今日やること」に絞った手紙が届きます。解除はいつでも。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
この記事が役に立ったらシェア