← Glossary / Row-Level Security

What is Row-Level Security?

Row-level security (RLS) is a database access control mechanism that restricts which records a user can query based on their identity or execution context. Instead of maintaining separate tables for different clients or permission tiers, RLS applies a hidden filter predicate to every query at the engine level. For data pipelines, it's the foundation of secure multi-tenant delivery — ensuring a client only extracts the rows they paid for, without risking cross-tenant leakage.

Data GovernanceMulti-TenancyPostgreSQLAccess ControlData Delivery
// 02 — definitions

Filter at
the engine.

Why maintaining separate tables for different clients is an anti-pattern, and how modern data warehouses handle multi-tenant isolation natively.

Ask a DataFlirt engineer →

TL;DR

Row-level security pushes access control down to the database engine. When a user queries a table, the database silently appends a policy predicate (e.g., WHERE tenant_id = current_user). It eliminates the need for application-layer filtering and guarantees that a compromised application cannot leak unauthorized rows.

01Definition & structure

Row-level security is a database feature that restricts data access at the row level based on the user's execution context. When a policy is applied to a table, the database engine automatically rewrites incoming queries to include a hidden filter predicate.

Instead of relying on the application code to append WHERE client_id = 123 to every query, the database enforces it natively. This provides a defense-in-depth guarantee: even if an application has a SQL injection vulnerability or a developer forgets a WHERE clause, the database will refuse to return unauthorized rows.

02How it works in practice

In a typical implementation (like PostgreSQL), an administrator enables RLS on a table and creates a policy. The policy defines a boolean expression that must evaluate to true for a row to be visible.

When an application connects, it sets a session variable (e.g., the current tenant ID). Any subsequent SELECT, UPDATE, or DELETE operations are silently filtered against that session variable. If a user tries to update a row they don't own, the database treats the row as if it doesn't exist.

03RLS vs. Column-Level Security

While RLS dictates which records you can see, Column-Level Security (CLS) or column masking dictates which fields within those records you can read. They are often used together.

For example, in a scraped HR dataset, RLS might ensure a client only sees candidates in their specific industry. CLS would ensure that standard users see a masked email address (j***@gmail.com), while privileged users see the raw email, all within the same row.

04How DataFlirt handles it

We use RLS extensively for our direct database delivery options. When we deliver a continuous feed of pricing data to multiple clients via Snowflake, we don't maintain separate tables. We maintain one master table and use Snowflake's native row-level access policies.

This architecture allows us to backfill or patch data once, and all authorized clients see the update instantly. It reduces our storage footprint, simplifies our ETL orchestration, and provides mathematical certainty regarding data isolation.

05The performance trap

The most common mistake with RLS is writing policies that require subqueries or complex function calls. If your policy says "allow access if the user's ID exists in this other permissions table," the database has to execute that lookup for every single row it scans.

For high-volume scraping pipelines, RLS policies must be simple, deterministic, and backed by indexes (like a direct match on an indexed tenant_id column). Complex policies will grind a data warehouse to a halt.

// 03 — the policy model

How predicates
are evaluated.

RLS policies act as mandatory, invisible WHERE clauses. DataFlirt uses these models to guarantee strict isolation across our shared delivery warehouses.

Effective Query = Qeff = Quser AND Prls
The database engine rewrites the query before execution. Standard RLS Implementation
Multi-tenant predicate = tenant_id = current_setting('app.current_tenant')
Standard PostgreSQL pattern for application-tier connection pooling. PostgreSQL Documentation
Leakage probability = P(leak) = 0
Assuming engine-level RLS is correctly configured, application bugs cannot bypass it. Security Architecture Principle
// 04 — database execution

One table,
multiple realities.

A trace of two different clients querying the exact same DataFlirt delivery table, yielding completely different result sets based on their RLS context.

PostgreSQLRLS PolicyMulti-tenant
edge.dataflirt.io — live
CAPTURED
// Client A context
SET role client_a;
SELECT count(*) FROM scraped_products;
-> EXPLAIN: Seq Scan on scraped_products (filter: tenant_id = 'client_a')
-> RESULT: 14,200 rows

// Client B context
SET role client_b;
SELECT count(*) FROM scraped_products;
-> EXPLAIN: Seq Scan on scraped_products (filter: tenant_id = 'client_b')
-> RESULT: 8,940 rows

// Unauthorized access attempt by Client B
SELECT * FROM scraped_products WHERE tenant_id = 'client_a';
-> RESULT: 0 rows // RLS policy overrides explicit WHERE clause
// 05 — failure modes

Where RLS
implementations break.

RLS is robust, but misconfigurations can lead to performance degradation or silent data exposure. These are the most common pitfalls in multi-tenant data architectures.

COMMON PITFALLS ·  ·  ·   Performance & Leaks
IMPACT ·  ·  ·  ·  ·  ·   High
MITIGATION ·  ·  ·  ·  ·  Strict Auditing
01

Missing policy indexes

Performance killer · Full table scans because the tenant_id column isn't indexed
02

Connection pooler leaks

Security risk · Session state not reset between pooled connections
03

Superuser bypass

Security risk · Running application queries as a database admin ignores RLS
04

Complex policy functions

Performance killer · Slow row-by-row evaluation of custom PL/pgSQL functions
05

Materialized view bypass

Security risk · Views created by superusers that expose underlying rows
// 06 — delivery architecture

One massive table,

safely sliced for hundreds of clients.

When DataFlirt delivers datasets via direct database shares (like Snowflake or BigQuery), we don't duplicate data into hundreds of client-specific tables. We maintain a single, highly optimized master table and use row-level security to project a client-specific view. This ensures instantaneous updates, zero replication lag, and mathematical certainty that Client A cannot query Client B's custom extractions.

RLS Delivery State

Live status of a multi-tenant delivery table in Snowflake.

table.name global_product_catalog
total_rows 482.5M
rls.status active
policy.name tenant_isolation_policy
active_tenants 142
query.overhead +2ms
leakage.events 0

Stay ahead of the pipeline

Data engineering
intel, weekly.

Anti-bot shifts, scraping infrastructure updates, dataset delivery patterns, and business outcomes from our pipelines. Short, technical, no fluff.

// 07 — FAQ

Common
questions.

Common questions about row-level security, performance impacts, and multi-tenant data delivery.

Ask us directly →
What is the difference between RLS and RBAC? +
Role-Based Access Control (RBAC) determines if you can access a table at all (e.g., SELECT on users). Row-Level Security (RLS) determines which specific rows you can see once you have access to that table. They work together: RBAC is the door, RLS is the filing cabinet inside.
Does RLS impact query performance? +
Yes, because it appends a WHERE clause to every query. If the column used in the RLS policy (like tenant_id) isn't properly indexed, the database will perform a full table scan. With proper indexing and partition pruning, the overhead is typically negligible (under 5ms).
How does RLS work with connection pooling like PgBouncer? +
It requires careful configuration. Because connection poolers share database sessions across different application users, you must ensure the execution context (like app.current_tenant) is explicitly set at the start of every transaction and cleared at the end, otherwise Tenant A might see Tenant B's data.
Can a user bypass RLS by guessing IDs? +
No. That's the primary benefit of RLS over application-layer filtering. Even if a user explicitly runs SELECT * FROM table WHERE tenant_id = 'someone_else', the database engine intercepts the query and appends AND tenant_id = 'my_id'. The result is always zero rows.
How does DataFlirt use RLS for data delivery? +
We use RLS in our Snowflake and BigQuery data shares. Instead of managing hundreds of ETL pipelines to copy data into client-specific buckets, we load scraped data into a single master table. RLS policies ensure that when you connect your BI tool to our share, you only see the datasets scoped to your contract.
What happens to RLS when data is exported to S3? +
RLS is a compute-engine feature. If you export data to a flat file (CSV/Parquet) on S3, the RLS policies do not travel with the file. The export process itself must be run under the correct RLS context to ensure the resulting file only contains the authorized rows.
$ dataflirt scope --new-project --target=row-level-security READY

Tell us what
to extract.
We do the rest.

20-minute scoping call. Pilot dataset within the week. Production within two. Whether you need a one-off catalogue dump or a continuous feed across millions of records — we scope, build, and operate the pipeline.

hello@dataflirt.com  ·  Bengaluru  ·  IST  ·  typical reply < 4h