Skip to content
Back to skills

Rls Performance

ASecurity

RLS 效能診斷:EXPLAIN ANALYZE、PGRST003 pool timeout、connection pool 耗盡、API 變慢。NOT for RLS policy 的正確性設計(走 supabase-rls)。

  • 44 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 5, 2026
developmentgosqldockerapidatabasebackendperformance

Works with

  • cursor
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 30, 2026

npx -y skills add Charles5277/nuxt-supabase-starter --skill rls-performance --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rls Performance?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rls Performance
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/charles5277-rls-performance/badge)](https://www.skillsdirectory.com/skills/charles5277-rls-performance)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rls-performance
description: >-
  RLS 效能診斷:EXPLAIN ANALYZE、PGRST003 pool timeout、connection pool 耗盡、API 變慢。NOT for RLS policy 的正確性設計(走 supabase-rls)。
---

<!-- clade-skill-scope: project -->

# RLS Performance Playbook

RLS 效能問題的診斷操作手冊。決策原則在 `db-runtime/<variant>/database.md` 與 `db-schema/<variant>/database-design.md` 規約;本檔只放診斷與優化工具。適用於:新增 policy join 或改 policy WHERE、新增含 pagination / filter 的 endpoint、`PGRST003`、pool 耗盡、特定頁面變慢、LXC 連線 / Tunnel 問題。

## EXPLAIN ANALYZE — 正確的 RLS 測量方式

**關鍵陷阱**:superuser 跑 EXPLAIN 會 **bypass RLS**,測出來的 plan 跟 production 完全不同。**一定要** `set local role` 模擬目標角色。

```sql
-- 1. 模擬目標 role(不是 postgres superuser!)
set local role authenticated;
set local request.jwt.claims to '{"sub": "<user_uuid>", "role": "authenticated"}';

-- 2. 跑實際 query
explain (analyze, buffers, verbose, format text)
select ... from <schema>.<table> where ...;

-- 3. 還原
reset role;
```

### 讀 plan 重點

| Plan 片段                          | 意義                         | 處理                                               |
| ---------------------------------- | ---------------------------- | -------------------------------------------------- |
| `Seq Scan on xxx`                  | 全表掃描                     | 檢查 WHERE 欄位是否有 index                        |
| `Rows Removed by Filter: > 1000`   | Index 取太多又濾掉           | Composite index 或 partial index                   |
| `Subquery Scan ... InitPlan`       | `(SELECT auth.uid())` 有快取 | ✅ 正確模式                                        |
| `Filter: (auth.uid() = user_id)`   | **沒快取**                   | 改 subselect `(SELECT auth.uid())`                 |
| `Nested Loop` + 高 rows            | Policy 內 per-row JOIN       | 改 subselect 預先算 ID set                         |
| `Planning Time` > `Execution Time` | Plan cache 沒命中            | 通常是 schema cache 過期或 prepared statement 問題 |

## Index 設計(RLS 專屬部分)

**Policy WHERE/USING 引用的欄位**(`user_id`、`tenant_id`、`department_id`)**MUST** 有 index(RLS 是 per-row 檢查)。其餘 index 設計走上游 `supabase-postgres-best-practices` 的 `references/query-*.md` 與 `schema-foreign-key-indexes.md`。

## Pagination

深層 pagination 的通則(cursor-based 優於 `limit + offset`)見上游
`references/data-pagination.md`。自家慣例只有一條:`PAGE_SIZE_MAX` **MUST** 從
`shared/schemas/pagination` 取用,不要各處自訂上限。

## Connection Pool 診斷(pg_stat_activity)

Self-hosted Supabase 的連線結構:

- **PostgREST (`authenticator` role)** 佔用 pool,所有 `/rest/v1/*` 請求共用
- **Auth (`supabase_auth_admin`)**、**Storage (`supabase_storage_admin`)**、**Realtime (`supabase_admin`)** 各自佔用
- **Supavisor pool** 是所有 client 的總閘口,預設 pool size 不應超過 DB `max_connections` 的 **40%**(若重度使用 PostgREST),其餘留給 Auth / Storage / admin

### 診斷 query

當 API 變慢或 `PGRST003`(504 timeout)出現,在目標 LXC 上跑:

```sql
-- 所有 live connection
SELECT
  pid, usename, application_name, client_addr,
  state, query_start, backend_start,
  left(query, 80) as query
FROM pg_stat_activity
WHERE datname = 'postgres'
ORDER BY backend_start DESC;

-- 按角色分組統計
SELECT usename, state, count(*)
FROM pg_stat_activity
WHERE datname = 'postgres'
GROUP BY usename, state
ORDER BY count DESC;

-- 找 idle connection 超過 5 分鐘
SELECT pid, usename, state, query_start, left(query, 80)
FROM pg_stat_activity
WHERE state = 'idle' AND state_change < now() - interval '5 minutes';
```

### 角色對照表

| `usename`                    | 來源                      |
| ---------------------------- | ------------------------- |
| `authenticator`              | PostgREST(Data API)     |
| `supabase_auth_admin`        | GoTrue                    |
| `supabase_storage_admin`     | Storage                   |
| `supabase_admin`             | Realtime / 監控           |
| `supabase_replication_admin` | Read replica sync         |
| `postgres`                   | Dashboard / psql / Prisma |

### 症狀 → 原因速查

- **大量 `authenticator` idle in transaction** → 前端有未完成的 transaction,或 `db_pre_request` hook 異常
- **`supabase_admin` 連線數突然暴增** → Studio schema introspection(通常來自 MCP `list_tables` / Kong 8001)
- **pool 滿到拒絕新連線** → 檢查 Supavisor pool size 配置、Compute add-on 的 max_connections

## pg_stat_statements — 找慢 query

```sql
-- 開啟 pg_stat_statements(已啟用)
select
  calls,
  total_exec_time::int as total_ms,
  mean_exec_time::int as mean_ms,
  query
from pg_stat_statements
where query ilike '%<your-schema>.%'  -- 換成業務 schema(或拿掉 where 子句看全部)
order by total_exec_time desc
limit 20;
```

## 效能基準

### Query 延遲

| Query 類型                    | 可接受延遲 | 異常訊號             |
| ----------------------------- | ---------- | -------------------- |
| Single row by PK              | < 5ms      | > 50ms 要查          |
| List with filter + pagination | < 50ms     | > 200ms 要查         |
| Aggregation / group by        | < 200ms    | > 1s 告警            |
| Full-text search              | < 100ms    | > 500ms 查 GIN index |

### RLS policy 開銷

| Policy 類型                          | 可接受延遲增量 |
| ------------------------------------ | -------------- |
| 單欄位比對(`user_id = auth.uid()`) | < 1ms          |
| 單層 subselect(`IN (select ...)`)  | < 5ms          |
| JOIN 到另一表(應避免)              | > 10ms(警訊) |

**異常訊號**:若 `Execution Time` 在有/無 policy 差異 > 50%,或出現 `Seq Scan` → 檢查 index 與 policy 寫法。

## Transaction 與批次寫入

- **批次寫入 ≤500 筆/transaction** — 超過會造成 WAL bloat 與 replica lag
- **避免 long-running transaction** — `idle in transaction` > 5 分鐘會卡 vacuum、blocking lock
- **Server 端遇到 `40001`(serialization_failure)** — retry transaction,但需有 idempotency 保證(見 `api-patterns.md`)
- **NEVER** 在 RLS policy 中放重型 JOIN 或外部函式 — 會被 per-row 執行
- **Dry run**:production 大量 UPDATE/DELETE 前先跑 `EXPLAIN` 確認 plan,避免 full scan

## Self-Hosting LXC 責任模型

當專案自建 dev / prod LXC(Docker Compose 部署),Supabase Cloud 提供的功能必須自行維護:

| 面向              | Cloud 提供                 | 自建需自行處理                              |
| ----------------- | -------------------------- | ------------------------------------------- |
| OS 與 kernel 更新 | 自動                       | `apt upgrade` 週期 + 重啟排程               |
| Postgres 備份     | PITR + 每日 snapshot       | `pg_dump` 排程 + Tailscale 拉到 NAS         |
| 監控告警          | Dashboard charts + Grafana | 自行建置;未建置時只剩 `docker logs` + 手動 SSH |
| SSL / HTTPS       | 自動                       | Cloudflare Tunnel 代管(cloudflared)       |
| Rate limiting     | Platform 層                | Nuxt 層自行實作                             |
| Schema cache 重載 | 自動                       | `notify pgrst, 'reload schema'` 手動        |
| Postgres 升級     | 一鍵                       | 需手動規劃 + 停機                           |

### 關鍵依賴

- **Cloudflare Tunnel**(若採此架構):production LXC 對外唯一入口。Tunnel 斷 = 全站掛(登入 / API 都不通)。任何影響 host 頻寬的操作都會波及(見 `database.md` Production 存取安全規範)
- **Tailscale**(若採此架構):Dev / 備份 / SSH 的通道。若 relay mode 密集連線會癱瘓整條網路
- **備份策略**:自架沒有內建 PITR / 自動 snapshot — **MUST** 規劃排程備份(例:`pg_dump` 經 Tailscale 推到 NAS / 物件儲存);無備份 = 復原倚賴最近一次手動 dump

## 效能事故處理 SOP

API 變慢或 `PGRST003` 出現時:

1. 先看 `log.error` 是否有 `PGRST003`(pool 耗盡)
2. SSH 到目標 LXC 跑 `pg_stat_activity`(上方診斷 query)找 long-running query
3. 取該 query 跑 `EXPLAIN ANALYZE`(記得 `set local role`)
4. 若是 RLS policy 問題 → 改 policy 或加 index
5. 若是 N+1 → 改用 `select(*, related(*))` embed 或 RPC

定期稽核:`pg_stat_user_indexes`(零使用量 index)、`pg_stat_statements`(total_exec_time 最高)、`pg_stat_activity` idle > 5 min(連線洩漏)。

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.