Supabase read replicas: how requests reach them and what they cost
A Supabase read replica is a second database billed at the primary’s compute size, and it only earns that bill if requests reach it. This page maps which requests can, which never do, what the replica costs line by line, and how to measure its share yourself.
Almost everything here is documented rather than measured: the Supabase docs were read on 2026-10-08 and every such claim carries a footnote. Two things were measured in the supabase-lab, both about creating a replica and neither about routing: the prerequisite chain on a Pro org (sfp-platforms S07/S07e, 2026-08-25) and a same-region replica on a Medium primary in ap-southeast-2 (medium-serverless MS06, 2026-09-30). No lab module has measured how the load balancer splits traffic, and the page says so where it matters.
TL;DR
- Over HTTP, only Data API
GETrequests can be served by a replica, and only when they go to the API load balancer endpoint or to the replica’s own API endpoint; SQL over the replica’s own pooler or connection string also reaches it. Non-GETrequests through the load balancer go to the primary.1 - A Postgres function called over
POST /rest/v1/rpc/...is a non-GETrequest. In supabase-js,rpc(fn, args, { get: true })sends it as aGET, which the docs name as the way to run a read-only function on a replica.12 - Auth always runs on the primary, even through the load balancer; Storage and Realtime cannot use a replica either.1 A custom domain bypasses the load balancer.1
- A replica bills compute at the primary’s size by the hour, a disk 1.25x the primary’s billed from the first byte, and copies of any provisioned IOPS, throughput and IPv4.34 Compute credits do not apply to it, and the spend cap does not cover it.35
- Read the replica’s share from the API logs field
metadata.load_balancer_redirect_identifier, from API Reports, or from Database Reports with the Source toggle set to the replica.16
Where each request lands
Section titled “Where each request lands”The same map as a table. “Documented” means the docs say it; “inferred” means it follows from the docs’ wording and was not measured.
| Request | Where it goes | Status |
|---|---|---|
Data API GET to the load balancer endpoint | the closest database, primary included | documented1 |
Data API POST / PATCH / DELETE to the load balancer endpoint, including POST /rest/v1/rpc/... | primary | documented1 |
rpc(fn, args, { get: true }) to the load balancer endpoint | sent as GET, so eligible for a replica | documented1; method choice read in the client source2 |
| Auth through the load balancer endpoint | primary, always | documented1 |
| Storage, Realtime | not a replica | documented1 |
Data API GET to a replica’s own API endpoint | that replica; the endpoint supports GET only | documented1 |
| Any request to a custom domain | not routed through the load balancer, so the primary unless you call a replica endpoint | first half documented,1 landing on the primary inferred |
| Any request to the primary’s project URL | primary | inferred: the API settings page lists the load balancer as a separate endpoint1 |
| SQL over the replica’s pooler or database connection string | that replica, read-only | documented1 |
The load balancer
Section titled “The load balancer”Each replica has its own database and API endpoints, and a project with replicas also gets an API load balancer endpoint, listed on the API settings page.1 The docs on routing, quoted:
The load balancer enables geo-routing for Data API requests to automatically route
GETrequests to the database closest to your user ensuring the lowest latency. You can also send Non-GETrequests through this endpoint, and they are routed to the Primary database automatically.1
Due to the requirements of the Auth service, all Auth requests are handled by the Primary, even when sent over the load balancer endpoint.1
The routing rule changed on 2025-04-04, from round-robin “among all databases (all read replicas + primary) of your project, regardless of location” to geo-routing that “directs requests to the closest available database (all read replicas + primary)”.1 The primary is in that pool. Under geo-routing as documented, a replica takes a GET when it is the closest database to the caller (inferred: a replica farther than the primary takes none). The docs do not say how the load balancer chooses between a primary and a replica in the same region, and no lab module measured it. The platform does accept a same-region replica: MS06 (2026-09-30) created one and it answered a read over its own pooler string 219 s after setup.
The load balancer exists only while a replica does. “If you remove all Read Replicas from your project, the load balancer and its endpoint are removed as well.”1 A client still pointed at the load balancer endpoint when the last replica goes has nowhere to send requests, so repoint it first.
Reads that arrive as POST
Section titled “Reads that arrive as POST”PostgREST runs GET and HEAD in a READ ONLY transaction: “Modifying the database inside READ ONLY transactions is not possible. PostgREST uses this fact to enforce HTTP semantics in GET and HEAD requests.”7 A function call over POST runs read-write when the function is VOLATILE and read-only when it is STABLE or IMMUTABLE, but either way it is a non-GET request and the load balancer sends it to the primary.71 The docs’ fix is the client option:
To call a read-only Postgres function on Read Replicas through the REST API, use the
get: trueoption.1
// POST /rest/v1/rpc/top_products - always the primaryconst a = await supabase.rpc("top_products", { lim: 20 });
// GET /rest/v1/rpc/top_products?lim=20 - eligible for a replicaconst b = await supabase.rpc("top_products", { lim: 20 }, { get: true });The supabase-js reference shows the option as “Call a read-only Postgres function” and the client’s own doc comment says that with get: true “the function will be called with read-only access mode”.82 In the postgrest-js source, get: true sets the method to GET and moves each argument into the query string: arrays become {a,b} literals and any other value goes through JavaScript string conversion, so an object argument would arrive as [object Object] (source read at commit 84e2c5d, not run).2 Two consequences:
- The function must not write. A function that writes (inserts, updates, or calls
nextval()) fails underGET, becauseGETruns READ ONLY; the PostgREST docs’ example, a view callingnextval(), answers HTTP 405 with SQLSTATE25006, “cannot execute nextval() in a read-only transaction”.7 - Keep
get: truefor scalar and array arguments. PostgREST matchesGETparameters to function parameters by name,?a=1&b=2.9
The Securing your API page states the same split from the rate-limiting side: “GET and HEAD requests run in read-only mode. They can be served by Read Replicas, which don’t support writing to the database.”10
What a replica costs
Section titled “What a replica costs”The usage docs describe a replica as “a dedicated database” whose resources are “mirrored from the primary database”.3
| Resource | What the replica gets | Billing |
|---|---|---|
| Compute | ”the same Compute size as the primary database”3 | by the hour; a partial hour bills as a full one3 |
| Disk size | ”1.25x the size of the primary disk to account for WAL archives”3 | from the first byte; the plan’s included disk does not cover it43 |
| Provisioned disk IOPS | inherits any additional IOPS3 | as for the primary |
| Provisioned disk throughput | inherits any additional throughput3 | as for the primary |
| IPv4 add-on | one per replica if the primary has one311 | ”Each replica adds to the total IPv4 cost.”11 |
On the invoice, replica compute is its own line, “Replica Compute Hours”; “Disk Size, Disk IOPS, Disk Throughput and IPv4 are not shown separately for Read Replicas and are rolled up into the project.”3 Searching the invoice for the replica’s disk will not find it.
Two exclusions apply: no credits offset it, and no spend cap limits it. “No, Compute Credits do not apply to Read Replica Compute.”3 Paid plans include $10 a month in compute credits, “enough to cover one Micro instance”,12 and they “apply only to Compute and do not cover other line items, including Read Replica Compute and Branching Compute”.5 And “Read Replicas are not covered by the Spend Cap”: Read Replica Compute sits on the cost-control page’s list of usage the cap excludes.313
Compute per replica, at the monthly prices on the public pricing page (doc-read 2026-10-08). Projects below XL can have up to two replicas, XL and above up to five.14
| Primary compute | Replica compute per month | Replicas allowed |
|---|---|---|
| Small | $15 | up to 2 |
| Medium | $60 | up to 2 |
| Large | $110 | up to 2 |
| XL | $210 | up to 5 |
| 2XL | $410 | up to 5 |
| 4XL | $960 | up to 5 |
| 8XL | $1,870 | up to 5 |
| 12XL | $2,800 | up to 5 |
| 16XL | $3,730 | up to 5 |
Monthly prices from the pricing page;12 the usage docs’ own examples quote Large at $111 for 730 hours.3 Micro and Nano are absent because a replica needs Small or larger.14
The docs’ smallest worked example, quoted line for line: a Pro project on Small with one replica and no add-ons bills Pro Plan $25, primary compute $15, primary disk 8 GB $0, replica compute $15, replica disk 10 GB $1.25, subtotal $56.25, Compute Credits -$10: $46.25 a month.3 The credits cannot apply to the replica’s compute, so the replica is $16.25 of that, none of it offset. The same page’s second example, two Large replicas with IPv4 and provisioned IOPS and throughput, totals $427.06.3
A resize moves the replicas with the primary. When the primary’s compute changes, the primary restarts first while the replicas stay available, then “all the Read Replicas are restarted (and resized, if needed) concurrently”.14 The compute cost of an upsize therefore multiplies by one plus the replica count.
Prerequisites
Section titled “Prerequisites”The docs list four requirements on top of a Pro, Team or Enterprise plan: the project runs on AWS, on “at least a Small compute add-on”, on Postgres 15 or later, and not on legacy logical backups.14 General regions (the Americas, Europe and APAC groupings) “aren’t yet supported for read replicas”; a project needs a specific region.15
The lab hit the compute floor and the backup requirement in order on a fresh Pro project (sfp-platforms S07/S07e, 2026-08-25):
| Step | Response from POST /v1/projects/{ref}/read-replicas/setup |
|---|---|
| Fresh project on the default Micro | 400 "Read replicas require a minimum size of small" |
After a ci_small upgrade | 406 "Failed to check for latest completed physical backup" |
After enabling the pitr_7 add-on (physical backups) | 204 accepted |
MS06 (2026-09-30) skipped the chain on a Medium primary in ap-southeast-2, with the replica in the primary’s own region: it added pitr_7 first (HTTP 200), then setup answered 204 on the first attempt; the replica’s READ_REPLICA entry appeared in the pooler config after 216 s, and a connection through the replica’s Supavisor string answered pg_is_in_recovery() = true after 219 s. Removal answered 204 and the entry was gone on the first poll. The docs say replicas deploy “across multiple regions”1 and do not mention the same-region case.
PITR was the lab’s way to get a completed physical backup on a fresh project; the backups docs say projects on Postgres 15.8.1.079 and newer use physical backups already.16 Whether the 406 clears on its own once the backup that follows a compute change completes was not run; on 2026-09-03 a fresh project already listed a completed physical backup. PITR is its own add-on priced by retention period, so it belongs in the replica’s price when the project needs it.16
What a replica does not do
Section titled “What a replica does not do”- Writes. The replica accepts
selectonly.1 - Auth, Storage and Realtime. “Requests to other Supabase products, such as Auth, Storage, and Realtime, aren’t able to use a Read Replica or its API endpoint.”1
- Failover. The read replica docs describe no way to promote a replica or fail over to one (doc-read 2026-10-08); the production checklist recommends replicas “if you require availability resilience to a disk failure event”.17 For a region outage, the warm standby in the DR tiers reference is the tier that runs somewhere else.
- Survive an upgrade or a restore. “Projects with read-replicas can’t be upgraded. You need to delete the replicas and re-create them after upgrade completes.”18 The getting-started page puts data restorations under the same rule: all replicas come down first, and you re-deploy them once the operation completes.14 The major upgrade guide carries this as a pre-window checklist item; reads go to the primary only once clients are repointed at the primary’s URL (the load balancer endpoint is removed with the last replica), so repoint first and size the primary to carry them.
- Fresh data on demand. Replication is asynchronous,1 and “High replication lag can result in stale data returned for queries executed against the affected read replicas.”14
Checking whether the replica serves reads
Section titled “Checking whether the replica serves reads”The dashboard and the logs both answer it, from the docs (none of these was exercised by a lab module):
- Database Reports, with the Source toggle on the replica, shows its resource use.1 A replica at idle CPU while the primary is busy is the first sign (inferred).
- API Reports covers “API requests going through either a Read Replica or Load Balancer API endpoint”.1
- In the API logs, a request through the load balancer records the database that served it: “The upstream database or the one that eventually handles the request can be found under the
Redirect Identifierfield. This is equivalent tometadata.load_balancer_redirect_identifierwhen querying the underlying logs.”1 The log field reference lists it onedge_logsaslog_attributes['load_balancer_redirect_identifier'].6 - Replication lag sits under Replica Information on Database Reports.14 For a metrics pipeline, the docs name
physical_replication_lag_physical_replica_lag_seconds; the Grafana guide’s alert usesphysical_replication_lag_physical_replication_lag_seconds(not reconciled). The Grafana monitoring guide covers ingesting that endpoint.
The query below counts Data API requests per serving database and method. It is built from the documented field names and the documented query syntax619 and was not run here:
-- Data API requests by serving database and method, over the Explorer's time rangeselect log_attributes['load_balancer_redirect_identifier'] as served_by, log_attributes['request.method'] as method, count() as requestsfrom logswhere source = 'edge_logs' and log_attributes['request.path'] like '/rest/v1/%'group by served_by, methodorder by requests desclimit 50;Rows with an empty served_by are requests the field did not describe; the docs define the field only “for API Load Balancer traffic”,6 so a large empty bucket reads as traffic that never went through the load balancer (inferred). POST rows against /rest/v1/rpc/ are function calls the replica cannot take until they use get: true.
Which do I pick
Section titled “Which do I pick”| Situation | Choice | Why |
|---|---|---|
| Primary CPU high, workload under roughly 80% reads | Larger compute | ”Read Replicas only serve reads and won’t help writes.”1 |
| Read-heavy, users far from the primary’s region | Replica near the users, clients on the load balancer endpoint | geo-routing sends each GET to the closest database1 |
| Analytical queries competing with the app | Replica, the analytics job on the replica’s connection string | the replica has its own pooler and database endpoint1 |
| Region outage cover | Warm standby, not a replica | no promotion path is documented; see the DR tiers |
| Data must stay in one jurisdiction | Replica only in an acceptable region | a replica is a full copy in its own region; see data residency |
| Replica exists, its share of reads is near zero | Fix the client path, or delete it | every hour bills at the primary’s compute size, outside credits and the spend cap3 |
The same check as a list:
- Do the API logs or API Reports show
GETrequests served by the replica? If yes, it is serving reads; keep it and watch replication lag. - If not, are the clients on the load balancer endpoint or the replica’s endpoint? If not, point Data API reads at the load balancer endpoint.
- Are the reads function calls sent as
POST? If yes, call read-only functions withget: true. - Is the Data API behind a custom domain? If yes, send reads to the replica’s own endpoint.
- Is the replica in the primary’s region, or are the users closer to the primary? If yes, move the replica toward the users or delete it.
- If not, check the load balancer’s split in the logs; not measured here.
What to do about it
Section titled “What to do about it”Most rows rest on the docs, read 2026-10-08, because no lab module measured routing; the Module column says so. MS06 and S07 resolve in the medium-serverless RUNLOG and the sfp-platforms RUNLOG.
| Practice | Evidence | Module |
|---|---|---|
| Count the replica’s share in the API logs before paying for another month. | metadata.load_balancer_redirect_identifier names the database that served a load-balanced request; the query above groups by it. | documented; query not run |
| Point Data API reads at the load balancer endpoint or a replica endpoint. | Only those two endpoints reach a replica; the load balancer is a separate endpoint on the API settings page. | documented; not measured |
Call read-only functions with rpc(fn, args, { get: true }). | Non-GET requests go to the primary; get: true sends a GET with arguments in the query string. A function that writes fails under GET with 25006. | documented; client source read |
Keep get: true to functions whose arguments are scalars or arrays. | The client converts each argument to a string for the query string; an object argument becomes [object Object]. | source read at 84e2c5d; not run |
| Send reads to a replica endpoint when the project uses a custom domain. | Custom-domain requests are not routed through the load balancer. | documented; not measured |
| Place a replica closer to the users than the primary is. | Geo-routing picks the closest database, primary included; the same-region tie-break is not documented. | MS06 shows a same-region replica serves SQL; LB split not measured |
| Price each replica as a second database at the primary’s compute size. | Compute, 1.25x disk from the first byte, IOPS, throughput and IPv4 are mirrored; credits and the spend cap do not apply. | documented |
| Budget the replicas into every primary upsize. | Replicas are resized with the primary, after it, concurrently. | documented; not measured |
| Wait for, or force, a completed physical backup after the resize to Small. | Setup answered 406 "Failed to check for latest completed physical backup" on Small; 204 after pitr_7. MS06 served 219 s after setup. | S07/S07e (2026-08-25), MS06 (2026-09-30) |
| Delete replicas before an upgrade or restore; repoint clients and size the primary. | Upgrades and data restorations require all replicas down first; recreate after. | documented |
| Repoint clients off the load balancer endpoint before removing the last replica. | The load balancer and its endpoint are removed with the last replica. | documented |
| Plan region failover on a standby, not a replica. | The docs describe no promotion path; Auth stays on the primary. | documented (absence) |
Not measured anywhere in the lab, and stated as such above: how the load balancer splits GETs between a primary and a same-region replica; whether a custom-domain request lands on the primary; that a get: true call reaches a replica; the contents of load_balancer_redirect_identifier on a real request; replica lines on a real invoice; replication lag on a replica; whether the 406 clears on its own once the backup that follows the resize to Small completes.
Evidence
Section titled “Evidence”| Claim | Status | How it was checked |
|---|---|---|
Replica setup gate: 400 below Small, 406 awaiting a physical backup after the resize to Small, 204 after pitr_7 | measured | sfp-platforms S07/S07e, 2026-08-25, Pro org; responses recorded in the RUNLOG |
Same-region replica accepted, in the pooler config after 216 s, pg_is_in_recovery() = true after 219 s, removal 204, entry gone from the pooler config on the first poll (0 s) | measured | medium-serverless MS06, 2026-09-30, Medium primary in ap-southeast-2; facts |
get: true sends GET with arguments in the query string | source read | postgrest-js rpc() at supabase-js commit 84e2c5d; not run |
| Load balancer routing, Auth on the primary, custom-domain bypass, GET-only replica endpoints, the 2025-04-04 geo-routing change | documented | read replicas docs, doc-read 2026-10-08 |
| Mirrored resources, 1.25x disk, Replica Compute Hours line, credits and spend cap exclusions, the $46.25 and $427.06 examples | documented | read replica usage, compute usage, disk size and cost-control docs, doc-read 2026-10-08 |
| Per-size monthly compute prices | documented | public pricing page, doc-read 2026-10-08 |
| Plans, AWS, Small, Postgres 15+, replica counts, general-region exclusion, upgrade and restore rule | documented | getting-started, regions and upgrading docs, doc-read 2026-10-08 |
load_balancer_redirect_identifier, the Source toggles, the lag metric | documented | read replicas docs and the log field reference, doc-read 2026-10-08 |
No docs-vs-runtime disagreement was found, so nothing on this page has been filed upstream. Two gaps sit between them: the same-region case MS06 measured, which the docs do not describe either way, and the setup gate after a compute change (406 on the Pro org), which the getting-started prerequisites do not list (doc-read 2026-10-08).
Modules
Section titled “Modules”| Module | Experiment | Test | Artifact |
|---|---|---|---|
| MS06 | medium-serverless | ms06-same-region-replica.ts | out/2026-09-30 |
| S07 | sfp-platforms | s07-read-replicas.ts | none published |
| S07e | sfp-platforms | s07-read-replicas.ts | none published |
Related docs
Section titled “Related docs”- Supabase DR tiers - where a replica sits against daily backups, PITR and a warm standby.
- Compute and disk - the compute sizes a replica mirrors and the spend-cap exclusions.
- Postgres major upgrade, end to end - the delete-and-recreate step in the upgrade window.
- Incident resilience - what keeps serving in an outage, and what does not.
- Data residency - a replica as a copy of the data in another region.
- Cloudflare Workers + Supabase - a replica near the edge to cut read latency.
References
Section titled “References”-
Supabase, “Read Replicas,” Supabase Docs. https://supabase.com/docs/guides/platform/read-replicas ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25 ↩26 ↩27 ↩28 ↩29 ↩30 ↩31 ↩32
-
Supabase, “postgrest-js PostgrestClient.ts, rpc(),” GitHub, commit 84e2c5d. https://github.com/supabase/supabase-js/blob/84e2c5d/packages/core/postgrest-js/src/PostgrestClient.ts ↩ ↩2 ↩3 ↩4
-
Supabase, “Manage Read Replica usage,” Supabase Docs. https://supabase.com/docs/guides/platform/manage-your-usage/read-replicas ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17
-
Supabase, “Manage Disk Size usage,” Supabase Docs. https://supabase.com/docs/guides/platform/manage-your-usage/disk-size ↩ ↩2
-
Supabase, “Manage Compute usage,” Supabase Docs. https://supabase.com/docs/guides/platform/manage-your-usage/compute ↩ ↩2
-
Supabase, “Log sources and fields,” Supabase Docs. https://supabase.com/docs/guides/observability/log-field-reference ↩ ↩2 ↩3 ↩4
-
PostgREST, “Transactions,” PostgREST Docs. https://docs.postgrest.org/en/stable/references/transactions.html ↩ ↩2 ↩3
-
Supabase, “JavaScript: rpc(),” Supabase Docs. https://supabase.com/docs/reference/javascript/rpc ↩
-
PostgREST, “Functions,” PostgREST Docs. https://docs.postgrest.org/en/stable/references/api/functions.html ↩
-
Supabase, “Securing your API,” Supabase Docs. https://supabase.com/docs/guides/api/securing-your-api ↩
-
Supabase, “Dedicated IPv4 Address for Ingress,” Supabase Docs. https://supabase.com/docs/guides/platform/ipv4-address ↩ ↩2
-
Supabase, “Pricing,” Supabase. https://supabase.com/pricing ↩ ↩2
-
Supabase, “Control your costs,” Supabase Docs. https://supabase.com/docs/guides/platform/cost-control ↩
-
Supabase, “Getting started with Read Replicas,” Supabase Docs. https://supabase.com/docs/guides/platform/read-replicas/getting-started ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Supabase, “Available regions,” Supabase Docs. https://supabase.com/docs/guides/platform/regions ↩
-
Supabase, “Database Backups,” Supabase Docs. https://supabase.com/docs/guides/platform/backups ↩ ↩2
-
Supabase, “Production Checklist,” Supabase Docs. https://supabase.com/docs/guides/deployment/going-into-prod ↩
-
Supabase, “Upgrading,” Supabase Docs. https://supabase.com/docs/guides/platform/upgrading ↩
-
Supabase, “Query logs with SQL,” Supabase Docs. https://supabase.com/docs/guides/observability/advanced-log-filtering ↩