Agent Workloads Are Just Beginning To Expose What Row-Level Security Really Costs
The argument that row-level security is sound is settled. What almost nobody has measured is what it costs per query, a bill set in the planner and in how the policy was written, and AI-agent concurrency is about to multiply how often it comes due.

Make The Read Replica one of your go-to sources on Google
Humans coordinate implicitly. Agents don't have that social coordination. They fire autonomously, from wherever they're running, at whatever frequency their loop dictates.
Years ago, on the Postgres performance mailing list, core committer Tom Lane gave a warning that has aged into the most relevant sentence in the row-level security debate. He wouldn't recommend RLS, he wrote, "if you can equally well stick the equivalent conditions into your queries," because there was "way too much risk of taking a serious performance hit due to a bad plan." At the time it read as a footnote. The feature was new, the workloads were human, and a few extra milliseconds per query vanished into the time a person takes to click a button.
But now the workloads are about to stop being human, and the footnote is about to become an operations problem.
Row-level security's soundness is settled as the check moves into the database. The default is to deny, and a forgotten filter in a forgotten code path stops being the thing that puts one tenant's rows on another tenant's screen. What was never settled is what the check costs once it runs often enough to matter. The cost is paid once per query, and the number of queries is exactly what's changing.
The load changed shape
Consider the load OpenAI now puts on Postgres. The company serves ChatGPT and its API from a single primary instance with nearly fifty read replicas, an instance that caps at five thousand connections and has, in its engineers' words, "had incidents caused by connection storms that exhausted all available connections," with database load that grew more than tenfold in a year. That's the extreme, but the shape is general.
Hari Kiran, a Postgres consultant who has run production databases for two decades and founded the consultancy OpenSource DB, framed the difference in human terms in a recent interview with The Read Replica. "Humans coordinate implicitly," he told us. "If I'm editing a record in New York, my colleagues in Calcutta or my colleagues in London probably aren't editing the same exact record. Agents don't have that social coordination. They fire autonomously, from wherever they're running, at whatever frequency their loop dictates."
Automated clients, and increasingly fleets of agents, don't read at human speed and don't leave connections idle; they break work into many small queries and ask for many connections at once. Postgres feels that early, because it answers each connection by forking a full operating-system process, as Percona's Fernando Laudares Camargos described it, and the project's own capacity guidance warns that past a saturation point throughput starts to fall off "due to the overhead from that contention." Every query now lands in a system with less headroom, and every query carrying an RLS policy pays that policy's cost on the way through.
The planner gives up the fast path
The cost lives in the planner on a structural level. RLS is enforced as a security barrier, which makes the optimizer surrender a freedom it normally leans on. Its usual move is to take a cheap, selective filter, evaluate it early, and throw away most rows before the expensive work begins. A security barrier forbids that whenever the cheap filter can't be proven safe, because running a user-supplied function early on rows the policy would have hidden is how a maliciously chosen function reads what it shouldn't.
It's right there in the documentation: the fastest possible plan must be rejected if it may compromise security. So the policy condition runs first, on every row, while the query's own filters wait their turn.
The lever called leakproof
There's a lever for this, and most teams never reach for it. A function the database can trust may be marked leakproof, and once it is, the planner is free to run it early again. Few functions qualify.
On a standard Postgres 18 install, Laurenz Albe, a longtime Postgres contributor at CYBERTEC, counted 345 leakproof functions, nearly all of them basic comparison operators, so a policy that leans on anything else loses its index. Many teams, Albe wrote, "find the performance loss daunting," and it's "often a stumbling stone for the acceptance of this security feature."
Cheap or ruinous, depending on the writing
The other common waste is cruder, but it has a clean fix: a policy that re-derives the caller's identity for every row instead of once for the whole query. The solution is to wrap the identity function in a subquery so the planner evaluates it once up front. Supabase CEO and Co-Founder Paul Copplestone described the technique in a conference talk, noting that "the Postgres planner will actually call that value in first, and it'll only call it once," rather than assessing it for every row. Supabase's own measurement of one such case puts the drop at roughly nine seconds to twenty milliseconds. Those are a vendor's numbers on a synthetic table, so take them as direction rather than constants. The direction is the point, because the same policy can be cheap or ruinous depending only on how it was written.
None of this is theoretical. Pierre Ducroquet, a lead Postgres DBA, reported on the Postgres hackers list that after his team turned RLS on, "a lot of queries started to go south, going extremely slow," and traced the cause to exactly this corner of the feature: "a lot of the PostgreSQL functions are not marked as leakproof, especially the ones used for operators." That's Lane's warning arriving in production, years later, as a real incident rather than a mailing-list caution.
The benchmark that doesn't exist
This is why Lane's warning is surfacing now. These costs didn't change; what changed is how often a system pays them. Jimmy Angelakos, a Postgres architect and the author of PostgreSQL Mistakes and How to Avoid Them, told The Read Replica that this is the usual shape of the worst Postgres surprises, the ones where "you discover under real world conditions with increased usage that things are not what they thought they were." A per-row identity lookup that adds a few milliseconds is invisible when one person triggers it a few times a minute, and it's a different thing across a fleet issuing hundreds of small queries a second against a pool already near its ceiling.
The policies most teams run were written and tested when there was one user per connection. Almost none have been measured against the load they're about to take. The benchmark that would matter doesn't exist: not the cost of an RLS policy on a quiet table queried by a person, which everyone has run, but the cost of that policy under sustained agent concurrency, on the real table, with the plan the security barrier actually forces. So far, nobody's run that test.
The signal, once a week
Reporting, contributor perspectives and sharp notes from the people building with Supabase in the real world. No noise, no spam.






