Skip to content

Does a Supabase platform operation have one duration?

I had been describing a restart as taking about a minute. That phrasing assumes an operation has one duration - that the project is either up or down, so one number describes it.

This tests that assumption, and four related ones, by watching five connection paths at once while triggering each operation.

#HypothesisOutcome
H1An operation has one duration, so one number describes itFalsified
H2A restart takes the whole project offlineFalsified
H3A resize is a restart with extra steps, so it costs about the sameFalsified
H4Resizing up and resizing down cost the samePartly held
H5A network restriction locks down the projectFalsified
H6The upgrade window can be measured on a throwaway projectFalsified

One probe per connection path - PostgREST, Auth, Storage, Realtime, and the pooler on 6543 - each sampled independently every 500 ms while the operation was triggered out of band. A path counts as answering if it returns anything other than a 5xx, because anon receives a 401 from /rest/v1/ and a 400 from Storage without a bearer, and a probe demanding 200 would report a permanent outage on two paths.

Recovery required five seconds of sustained success rather than one passing sample. A path already failing before the operation voided that row.

The direct port 5432 was not tested: it is IPv6-only and unreachable from this vantage, so there is no unpooled baseline here.

operationRESTRealtimeAuthStoragepooler
restart--75 s78 s158 s
network restriction----bites in 1 s
resize up (micro -> small)--131 s127 s207 s
resize down (small -> micro)--99 s100 s196 s

A dash is zero failed samples.

REST and Realtime never failed under any of the four operations. On the same restart that left Auth unavailable for 75 s, both served continuously at 500 ms resolution. So H1 is false - there is no single number - and H2 is false with it, since a restart did not take the project offline in any sense a REST client would notice.

Auth and Storage also fail differently from each other, HTTP 521 against HTTP 500, so even the two paths that behaved similarly did not behave identically.

Every path that failed did so 2-3 seconds after the API returned, so time-to-bite was effectively constant and recovery was the variable.

The explanation this suggests, which was not tested

Section titled “The explanation this suggests, which was not tested”

The obvious reading is that the four HTTP services reach Postgres from inside the project and a client’s connection terminates at the service, while a pooled client’s socket sits much closer to what is being restarted.

That is an inference from the result, not something measured here. It is drawn because it predicts the ordering, not because the topology was inspected.

answered throughout / recovered in ~75-131sslowest path every timeclientPostgRESTAuthStorageRealtimeSupavisor:6543pooledPostgresdirect :5432NOT TESTED (IPv6 only)

H3 and H4: a resize is not a restart, and direction matters unevenly

Section titled “H3 and H4: a resize is not a restart, and direction matters unevenly”

Auth was down 131 s resizing up against 75 s restarting; the pooler 207 s against 158 s. So H3 is false: on this account a resize cost roughly twice a restart rather than about the same.

H4 held on one tier and not the other. Growing cost about a third more than shrinking on the HTTP tier (Auth 131 s against 99 s), while the pooler was within 5 % either way (207 s against 196 s). Whatever dominated the pooler’s window did not depend on direction.

Compute size turned out not to be a resize endpoint at all - it is an addon mutation, PATCH on the project’s billing addons with an addon_type of compute_instance1. Returning to the smallest size REMOVES the addon rather than setting one, and the subsequent read reports no compute addon.

H5: a network restriction reaches the pooler, not the HTTP tier

Section titled “H5: a network restriction reaches the pooler, not the HTTP tier”

Locking the database to a CIDR that excluded this vantage left REST, Auth, Storage and Realtime serving - four paths, zero failed samples, across two runs. H5 is false as stated: it is a database-socket control.

It did reach the pooler, one second after the API returned, and the refusal named the rejected address. That is worth separating from the rest: it means the pooled port is covered by the restriction, not only a direct connection.

The pooler returned a different error for each operation

Section titled “The pooler returned a different error for each operation”
operationpooler error
restartFailed to connect to database: {:error, :timeout}
network restriction(EADDRNOTALLOWED) address not in tenant allow_list
resize upFailed to connect to database: {:error, :econnrefused}
resize downterminating connection due to administrator command

Only the last is a Postgres message; the others come from Supavisor. Across all four operations the pooler answered and reported the backend as unavailable in four different ways - unreachable, refused, deliberately shut down, or the client not permitted - rather than going away itself.

H6: the upgrade window could not be measured

Section titled “H6: the upgrade window could not be measured”

A freshly created project came up already at the latest app version: eligible: false, current equal to latest, no upgrade targets. And the conditions could not be arranged, because the version selectors on project creation are both deprecated and typed null in the published schema1, so a created project takes the current default.

Measuring a client-visible upgrade window therefore requires a project that has aged past the current version - which means upgrading something real. H6 is false, and that is a property of how projects are created rather than a gap in the method.

What was readable without upgrading anything: the eligibility payload carries duration_estimate_hours, which returned 1 on each of three aged projects in this organization, all offering a single patch-level target with no validation errors. That figure is a value the API returned, not an outage anyone observed - and this page has just shown those differ by up to 2x on the same project. All three returning exactly 1 also reads as a coarse figure rather than a per-project computation.

n=1, one account. One run per operation, on projects belonging to a single organization. The ordering repeated across four operations and two projects, which is the part worth any confidence; the individual seconds are not.

500 ms resolution. An earlier measurement of the same restart sampled every 5 s on a single path. At that resolution a short outage rounds away and a per-path difference cannot appear at all, which is how the one-duration assumption survived as long as it did.

One vantage, IPv4. The direct port was never in the matrix, so the pooler column has nothing unpooled to be compared against.

What the result supports: that these paths did not move together on this account, in this region, on these dates. What it does not: any figure for a different region, plan, project size, or point in time.

ClaimHow it was checked
Restart: REST and Realtime zero failed samples; Auth 75 s, Storage 78 s, pooler 158 sMeasured, 500 ms sampling, one Micro project
Resize up 131/127/207 s, down 99/100/196 sMeasured, same probe set, same project
Restriction leaves the HTTP tier servingMeasured twice, zero failed samples on four paths
Restriction bites in ~1 s, recovers in ~2 sMeasured; the dwell between them was chosen, not measured
Per-operation pooler error stringsCaptured verbatim from the failing probe
Compute size is an addon mutationRead from the published OpenAPI document1
A fresh project is at the latest version and cannot be created olderRead from the eligibility endpoint and the create schema1; nothing was upgraded
duration_estimate_hours of 1 on three projectsRead from the eligibility endpoint; not an observed outage
Direct port 5432Not tested - IPv6-only, unreachable from this vantage
Upgrade windowNot tested - not constructible on a throwaway project
Why the HTTP tier is insulatedNot tested - inferred from the ordering

Sample every path continuously while triggering the operation out of band, and record the failure mode beside the duration. Two details decide whether the result means anything.

Recovery has to require sustained success rather than one passing sample. The pooler queues before it refuses, so a single lucky probe mid-outage otherwise registers as recovery.

A path already failing before the operation has to void that row, or a bad credential reads as a platform outage.

  1. Supabase, “Management API OpenAPI document,” api.supabase.com. https://api.supabase.com/api/v1-json 2 3 4