supabase-postgres — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited supabase-postgres (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Comprehensive Postgres performance guide organized by impact priority. Each rule includes incorrect vs. correct SQL examples with explanations.
| Priority | Category | Impact | Reference Prefix |
|---|---|---|---|
| 1 | Query Performance | CRITICAL | query- |
| 2 | Connection Management | CRITICAL | conn- |
| 3 | Security & RLS | CRITICAL | security- |
| 4 | Schema Design | HIGH | schema- |
| 5 | Concurrency & Locking | MEDIUM-HIGH | lock- |
| 6 | Data Access Patterns | MEDIUM | data- |
| 7 | Monitoring & Diagnostics | LOW-MEDIUM | monitor- |
| 8 | Advanced Features | LOW | advanced- |
See references/ for detailed rule files with full SQL examples.
npx clawhub@latest install supabase-postgresAlways index WHERE and JOIN columns. Unindexed columns cause full table scans that get exponentially slower as tables grow. Create indexes on every column used in WHERE, JOIN, or ORDER BY on large tables.
-- Create index on frequently filtered column
create index orders_customer_id_idx on orders (customer_id);Choose the right index type. B-tree (default) handles =, <, >, BETWEEN. Use GIN for JSONB/arrays/full-text, BRIN for large time-series tables, Hash for equality-only lookups.
create index products_attrs_idx on products using gin (attributes); -- JSONB
create index events_time_idx on events using brin (created_at); -- Time-seriesUse partial indexes for queries that always filter on the same condition. They're smaller, faster, and cheaper to maintain.
create index orders_pending_idx on orders (created_at)
where status = 'pending';Always use connection pooling. Each Postgres connection uses 1-3MB RAM. Without pooling, 500 concurrent users = 500 connections = crashed database.
(CPU cores * 2) + disk_countSet appropriate connection limits. Monitor with:
select count(*), state from pg_stat_activity group by state;Enable RLS for multi-tenant data. Application-level filtering alone is one bug away from exposing all data.
alter table orders enable row level security;
create policy orders_policy on orders
for all to authenticated
using ((select auth.uid()) = user_id); -- Wrap in SELECT for performanceOptimize RLS policies. Wrap function calls in (select ...) so they execute once, not per-row. On a 1M-row table, this is 100x+ faster.
-- BAD: auth.uid() called per row
using (auth.uid() = user_id);
-- GOOD: auth.uid() called once, cached
using ((select auth.uid()) = user_id);Choose appropriate data types:
create table users (
id bigint generated always as identity primary key, -- Not serial
email text, -- Not varchar(n)
created_at timestamptz, -- Not timestamp
is_active boolean default true, -- Not varchar
price numeric(10,2) -- Not float
);Key guidelines: bigint over int, text over varchar(n), timestamptz over timestamp, numeric over float for money.
Select optimal primary keys: bigint identity for single-database, UUIDv7 for distributed systems. Avoid random UUIDv4 as PK on large tables (causes index fragmentation).
Always index foreign key columns. Postgres does NOT auto-index FKs. Missing FK indexes cause slow JOINs and CASCADE operations.
-- Find missing FK indexes
select conrelid::regclass as table_name, a.attname as fk_column
from pg_constraint c
join pg_attribute a on a.attrelid = c.conrelid and a.attnum = any(c.conkey)
where c.contype = 'f'
and not exists (
select 1 from pg_index i
where i.indrelid = c.conrelid and a.attnum = any(i.indkey)
);Prevent deadlocks with consistent lock ordering. Always acquire locks in a deterministic order (e.g., by ID).
-- Lock rows in ID order before updating
begin;
select * from accounts where id in (1, 2) order by id for update;
update accounts set balance = balance - 100 where id = 1;
update accounts set balance = balance + 100 where id = 2;
commit;Eliminate N+1 queries. Batch with ANY(array[...]) or use JOINs instead of per-row queries.
-- BAD: 101 round trips
select id from users where active = true; -- 100 IDs
select * from orders where user_id = 1; -- repeated 100 times
-- GOOD: 1 round trip
select * from orders where user_id = any($1::bigint[]);Use cursor-based pagination. OFFSET scans all skipped rows. Keyset pagination is O(1) regardless of page depth.
-- BAD: page 1000 scans 20,000 rows
select * from products order by id limit 20 offset 19980;
-- GOOD: page 1000, same speed as page 1
select * from products where id > $last_id order by id limit 20;Use EXPLAIN ANALYZE to diagnose slow queries:
explain (analyze, buffers, format text)
select * from orders where customer_id = 123 and status = 'pending';What to look for:
Seq Scan on large tables → missing indexRows Removed by Filter → poor selectivityBuffers: read >> hit → data not cachedSort Method: external merge → increase work_memMonitor with pg_stat_statements for aggregate query performance across the system.
| Problem | Solution |
|---|---|
| Slow filtered queries | Add index on WHERE columns |
| Slow JOINs | Index foreign key columns |
| JSONB/array queries slow | Use GIN index |
| Time-series table huge | Use BRIN index |
| Too many connections | Use connection pooling |
| Data leaks | Enable RLS with policies |
| RLS is slow | Wrap functions in (select ...) |
| N+1 queries | Batch with ANY(array[...]) |
| Deep pagination slow | Cursor/keyset pagination |
| Deadlocks | Consistent lock ordering |
| Schema bloat | Use correct data types |
| Index fragmentation | Use bigint identity or UUIDv7 PKs |
numeric for exact arithmeticDetailed rule files in references/:
Query Performance (Critical):
query-missing-indexes.md — Index WHERE and JOIN columnsquery-index-types.md — B-tree vs GIN vs BRIN vs Hashquery-partial-indexes.md — Partial indexes for filtered queriesquery-composite-indexes.md — Multi-column index designquery-covering-indexes.md — Index-only scansConnection Management (Critical):
conn-pooling.md — Connection pooling with PgBouncerconn-limits.md — Setting connection limitsconn-idle-timeout.md — Idle connection managementconn-prepared-statements.md — Prepared statement poolingSecurity (Critical):
security-rls-basics.md — Row-Level Security fundamentalssecurity-rls-performance.md — Optimizing RLS policiessecurity-privileges.md — Role and privilege managementSchema Design (High):
schema-data-types.md — Choosing data typesschema-primary-keys.md — Primary key strategiesschema-foreign-key-indexes.md — FK index requirementsschema-lowercase-identifiers.md — Naming conventionsschema-partitioning.md — Table partitioning strategiesConcurrency & Locking (Medium-High):
lock-deadlock-prevention.md — Avoiding deadlockslock-short-transactions.md — Keeping transactions shortlock-skip-locked.md — Queue processing patternlock-advisory.md — Advisory locksData Access (Medium):
data-n-plus-one.md — Eliminating N+1 queriesdata-pagination.md — Cursor vs offset paginationdata-batch-inserts.md — Bulk insert patternsdata-upsert.md — Upsert patternsMonitoring (Low-Medium):
monitor-explain-analyze.md — Query plan analysismonitor-pg-stat-statements.md — Aggregate query statsmonitor-vacuum-analyze.md — Table maintenanceAdvanced Features (Low):
advanced-jsonb-indexing.md — JSONB query optimizationadvanced-full-text-search.md — Full-text search setupExternal:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.