Web Architecture

Supabase at Scale: What Changes When You Hit 1M+ Events Per Day

Supabase is an excellent starting point. It's also a platform that can surprise you at scale if you haven't designed around its limits. Here's what changes at 1M+ events/day.

April 14, 202610 min read
SupabasePostgreSQLscalabilitySaaS architecturedatabase performance

Supabase is genuinely good software. The developer experience is fast, the managed Postgres is competent, and for most B2B SaaS products in the $1M–$10M ARR range, it removes significant operational overhead. I've shipped production systems on it and would again.

It is also a platform that can surprise you at scale if you haven't designed around its constraints ahead of time. After building the Meridian Analytics Platform — 2M+ events/day, sub-200ms P99 query time — I have a clearer picture of what those constraints are and when they start to matter.

The Three Constraints That Emerge at Scale

1. Row-Level Security Has a Cost

Supabase's RLS is one of its best features. Database-layer tenant isolation means a misconfigured API route cannot return another tenant's data — the constraint is enforced at the query level, not the application level.

The cost: every query with an active RLS policy runs the policy check. At low volume, this is undetectable. At 1M+ events/day, it shows up as additional CPU and query planner overhead.

The mitigation is not to turn off RLS. It's to ensure your policies are simple and indexed correctly. A policy like auth.uid() = user_id is cheap. A policy that calls a function which queries another table is expensive and can cause table scans.

Design your RLS policies in week one. Treat them like indexes — understand the query plan before you ship.

2. The Default PostgREST Layer Has Limits for Aggregate Queries

Supabase's auto-generated REST API (via PostgREST) is excellent for CRUD operations and simple filters. It struggles with complex aggregations — multi-join queries, window functions, conditional aggregates.

At 1M+ events/day, your analytics queries are almost certainly in this category. A dashboard query that calculates "events by type, last 30 days, grouped by tenant, with 7-day trailing average" cannot be expressed cleanly through PostgREST.

The answer is a custom query layer — either a Supabase Edge Function, a dedicated server function, or a tRPC procedure that runs parameterised SQL. This is the right call regardless of volume; the volume just forces it sooner.

At Meridian, we wrote a custom aggregation layer for everything dashboard-related. PostgREST handled the CRUD paths. The boundary was clean and worth maintaining.

3. The Event Ingestion Path Needs Explicit Design

At 1M+ events/day, you're ingesting ~700 events/minute average, with likely 3-5× spikes. Supabase can handle this — but the naive architecture (HTTP request → Supabase client → insert) will either drop events under load or create unpredictable latency.

The production architecture:

  1. Edge Function for ingestion acknowledgement. Clients get a 200 within 50ms. The event is buffered, not committed.
  2. Queue layer for backpressure. Upstash Redis works well here. Events accumulate during spikes without client-facing latency impact.
  3. Async write from the queue. A worker drains the queue into Postgres in batches. Batch inserts are dramatically faster than single-row inserts.

This pattern decouples ingestion latency from write throughput. Your event producers get fast acknowledgements; your database gets writes at a sustainable rate.

Partitioning Is Not Optional at This Volume

Postgres table partitioning is commonly taught but rarely implemented until teams hit a wall. At 1M events/day, you'll accumulate 30M rows per month. Without partitioning, queries against recent data scan the entire table.

Partition the events table by month on event_timestamp. This is a one-way door — retrofitting partitioning on a live table with 100M+ rows is a multi-day migration. Do it at schema design time.

With monthly partitions:

Pair partitioning with composite indexes on (tenant_id, event_timestamp). Most dashboard queries filter on both — the index lets the planner do a single index scan rather than a table scan filtered after the fact.

Connection Pooling via PgBouncer

Supabase includes PgBouncer. Use it. Direct Postgres connections are expensive to open and have a hard limit. At scale, you'll hit the connection limit before you hit the query limit.

Configure your clients to connect through the PgBouncer port, not the direct Postgres port. For most SaaS applications, transaction-mode pooling is the right setting — connections are returned to the pool after each transaction rather than held for the session duration.

Materialized Views for Dashboard Performance

The dashboards with the slowest queries are almost always aggregate views over large windows — "total events by type, last 90 days" or "cohort retention, last 6 months." Running these queries on request at 200ms P99 is hard if the underlying data is at 30M rows.

Materialized views are the right answer for metrics that recalculate on a schedule rather than on every request. At Meridian, we materialized the 7-day and 30-day aggregate views on a 1-hour schedule. Dashboard queries hit the materialized view — fast, predictable. The underlying event data was still queryable for ad-hoc analysis.

The trade-off: data freshness. Materialized views are stale by up to your refresh interval. For most B2B analytics dashboards, 1-hour lag is acceptable. If your use case requires real-time aggregation, that's a harder problem and a different architecture (ClickHouse territory).

What Supabase Does Not Replace

At 2M+ events/day with complex analytics requirements, Supabase on Postgres is a reasonable choice if you're willing to design carefully. At 10M+ events/day, or if your analytics queries require arbitrary dimensional slicing across large windows, you're probably looking at a dedicated OLAP store — ClickHouse, BigQuery, or Redshift alongside Postgres.

Supabase is an OLTP database with excellent developer tooling. It will serve as the transactional backbone of your SaaS product as long as you need. For high-volume event analytics with complex aggregations, treat it as one component in a larger data architecture rather than the whole answer.

The Short Version

None of these are Supabase-specific failures. They're database engineering decisions that scale matters on regardless of vendor.

Apply

If this maps to a problem you're working on.

I work with $1M–$20M ARR founders whose digital investment isn't producing the return it should. Applications reviewed personally within 48 hours.

2 Diagnostic slots / month · 2–3 full engagements / quarter · 48h review