Thinking like an RLS cost calculator
Somewhere between "premature optimisation" and "the invoice arrived" sits a question worth answering on paper first: what is this bad predicate actually costing? Managed Postgres platforms bill in proportion to database work, and a policy forcing sequential scans multiplies work by table size on every request. You can reason about the shape of that multiplication without measuring anything.
This page offers the estimation habit, not a calculator — RowShield does not compute cost figures, and we decline to invent conversion rates between rows scanned and currency.
RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.
The unit of cost is rows considered
Database compute consumption tracks work performed, and for a query the dominant term is usually the number of rows the executor must consider. An indexed predicate considers a handful — the matches, plus the depth of the tree walked to find them, which grows logarithmically as data grows. A sequential scan under an unindexed policy considers every row in the table, every time, so the work term is linear in table size and is paid on every request that touches the policy.
That asymmetry is the whole story of RLS-related bill growth. Traffic stayed flat, but the table tripled, so per-request cost tripled; multiply by request rate and the curve bends upward even though nothing in the application changed. When someone reports that a Supabase bill grows faster than traffic, this mechanism — or the per-row auth call with the same effect — is the usual suspect.
A back-of-envelope method
Four numbers, multiplied with deliberate roughness. Estimate the live row count of the table — the catalog maintains a planner estimate good enough for arithmetic. Estimate requests against that table per day from your own analytics or logs. Multiply: rows considered daily equals table size times request rate for the unindexed path, versus a small constant per request for the indexed path. Finally, take the ratio between the two paths — that dimensionless number, not a currency figure, is the honest output of the exercise, and it tells you the ceiling on what fixing the predicate can save.
-- Planner's estimated live row count, per table:
SELECT relname, reltuples::bigint AS estimated_rows
FROM pg_class
WHERE relkind IN ('r', 'p')
AND relnamespace = 'public'::regnamespace
ORDER BY estimated_rows DESC;Signals the bill is already paying
The estimator above predicts; the statistics collector confirms. Sequential-scan counters that climb steadily on your largest tables, tuple-read counts wildly out of proportion to result sizes, and list endpoints whose latency tracks table age rather than traffic are the fingerprints. Cross-check against billing rhythm: costs tracking table growth rather than user growth point at scan-per-request mechanics, while costs tracking egress point elsewhere entirely — payload size, not predicate quality.
SELECT relname, seq_scan, seq_tup_read, idx_scan FROM pg_stat_user_tables WHERE schemaname = 'public' ORDER BY seq_tup_read DESC;
Turning estimates into priorities
Rank by the ratio, not by severity labels: a medium-severity index finding on your largest, hottest table outranks a critical finding on a lookup table queried twice a day, because exposure and expense are different axes. Fix in descending ratio order, re-run the plan comparison after each, and expect the curve — bill growth relative to traffic — to flatten rather than the bill to fall, since the work was growing, not static. Where the estimate says the ratio is trivial, note it and move on; not every finding is worth an afternoon.
RowShield does not automate this arithmetic — no scanner sees your request rates, and we would rather teach the method than fake precision. What it automates is finding the structural causes on every scheduled scan: UNINDEXED_RLS_PREDICATE for predicate columns without indexes, RLS_UNWRAPPED_AUTH_CALL for per-row auth evaluations, each with generated fixes. Catalog reads only, rows never touched, and RowShield is an independent product, not affiliated with or endorsed by Supabase. Run a free audit at rowshield.dev/audit to see the structural findings first.
Frequently asked
- Can you tell me how much money a fix will save?
- No, and a page that quoted a figure would be inventing it. The honest output of the method is a dimensionless ratio — rows considered per day on each path — which bounds the possible saving without pretending to know your platform's pricing curve.
- Do the new indexes cost much to maintain?
- Indexes add write-time maintenance, so a heavily written table feels each one. In the situation this page addresses — read-dominated scanning driving compute — the maintenance overhead is ordinarily far outweighed by the eliminated scan work.
- Where does egress fit into this model?
- It is a separate axis. Egress tracks bytes returned to clients and rewards narrower selects and pagination, while the predicate problem tracks rows considered internally. A query can scan millions of rows and return kilobytes, which is why the two bills move independently.
Check your project in about ten seconds
Paste a URL. No signup, no writes, nothing stored.
Run the free audit