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.
The hypotheses
Section titled “The hypotheses”| # | Hypothesis | Outcome |
|---|---|---|
| H1 | An operation has one duration, so one number describes it | Falsified |
| H2 | A restart takes the whole project offline | Falsified |
| H3 | A resize is a restart with extra steps, so it costs about the same | Falsified |
| H4 | Resizing up and resizing down cost the same | Partly held |
| H5 | A network restriction locks down the project | Falsified |
| H6 | The upgrade window can be measured on a throwaway project | Falsified |
Method
Section titled “Method”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.
H1 and H2: the paths do not move together
Section titled “H1 and H2: the paths do not move together”| operation | REST | Realtime | Auth | Storage | pooler |
|---|---|---|---|---|---|
| restart | - | - | 75 s | 78 s | 158 s |
| network restriction | - | - | - | - | bites in 1 s |
| resize up (micro -> small) | - | - | 131 s | 127 s | 207 s |
| resize down (small -> micro) | - | - | 99 s | 100 s | 196 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.
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”| operation | pooler error |
|---|---|
| restart | Failed to connect to database: {:error, :timeout} |
| network restriction | (EADDRNOTALLOWED) address not in tenant allow_list |
| resize up | Failed to connect to database: {:error, :econnrefused} |
| resize down | terminating 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.
Reading the numbers
Section titled “Reading the numbers”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.
Evidence
Section titled “Evidence”| Claim | How it was checked |
|---|---|
| Restart: REST and Realtime zero failed samples; Auth 75 s, Storage 78 s, pooler 158 s | Measured, 500 ms sampling, one Micro project |
| Resize up 131/127/207 s, down 99/100/196 s | Measured, same probe set, same project |
| Restriction leaves the HTTP tier serving | Measured twice, zero failed samples on four paths |
| Restriction bites in ~1 s, recovers in ~2 s | Measured; the dwell between them was chosen, not measured |
| Per-operation pooler error strings | Captured verbatim from the failing probe |
| Compute size is an addon mutation | Read from the published OpenAPI document1 |
| A fresh project is at the latest version and cannot be created older | Read from the eligibility endpoint and the create schema1; nothing was upgraded |
duration_estimate_hours of 1 on three projects | Read from the eligibility endpoint; not an observed outage |
| Direct port 5432 | Not tested - IPv6-only, unreachable from this vantage |
| Upgrade window | Not tested - not constructible on a throwaway project |
| Why the HTTP tier is insulated | Not tested - inferred from the ordering |
Reproducing
Section titled “Reproducing”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.
References
Section titled “References”-
Supabase, “Management API OpenAPI document,” api.supabase.com. https://api.supabase.com/api/v1-json ↩ ↩2 ↩3 ↩4