Skip to content

What a Supabase platform operation costs a client

A restart, a compute resize, a network restriction, provisioning a fresh project, and moving one between organizations each interrupt a client differently: on a different connection path, for a different duration, by a different failure mode. A plan that assumes “a restart takes about a minute” can be checked here, per path, against what happened.

TL;DR:

  • REST reads and Realtime subscriptions went down under a restart and under a resize in either direction, for about as long as Auth and Storage: restart 247 and 194 s, Micro to Small 171 and 218 s, Medium to Large 113 s (the second run, 299 s, is void: the orchestrator slept 263 s inside it, lab #211) for the row read (2026-10-23; two passes on one Micro project, one run each on two Medium projects). The 2026-08-04 “never failed” was the HTTP tier answering.
  • On 2026-08-04 a resize cost more than a restart, but not by a constant factor: Auth 131 s against 75 s is 1.75x; the pooler 207 s against 158 s is 1.31x. Those are Micro to Small. On 2026-10-23 the order did not hold (Auth restart 247 and 192 s against Micro to Small 171 and 218 s). A Medium to Large resize on another project took Auth 35 s, Storage 34 s and the pooler 30 s (medium-serverless MS07, 2026-09-30, REST and Realtime probed only at the HTTP tier); two Medium projects resized Medium to Large on 2026-10-23 took 107 to 115 s (B; C’s Medium to Large is void, the orchestrator slept inside it) on the row read, the Realtime join, Auth, Storage and the pooler, and MS07 re-run the same day on two more took every data path down 118 to 310 s across eight legs, Auth 128 to 221 s. Neither region nor project age since the create call up to one hour explains the 2026-09-30 figures.
  • compute-disk D09 re-ran Micro -> Small -> Large -> Small -> Micro on 2026-10-23 (n=1 per op): REST reads, Auth and Realtime joins down 144.528 to 254.886 s per resize, REST back 18 to 35 s after ACTIVE_HEALTHY. Its 2026-08-19 figures (1.0 s and 17.0 s REST outages) are withdrawn.
  • A network restriction reaches the pooler in ~1 s and leaves the HTTP tier and the data path alone (0 failed row reads and Realtime joins in two runs, 2026-10-23). The reported 62 s window is mostly a chosen 60 s test dwell, not a platform duration.
  • A restart re-checked on 2026-10-10 (n=5, one Micro project) had Auth 54 s, Storage 58 s, pooler 54 s and direct 50 s at p50, so the n=1 D01 figures of 75, 78 and 158 s are one observation of a window that was about 30 s in two of five re-check runs and 50 to 60 s in the other three. The same module re-run on 2026-10-23 with the data-path probes had REST reads and Realtime joins failing in 5 of 5 restarts (p50 150 s and 146 s) and the six paths that failed at 43 to 182 s (rest_http and realtime_ws 0 of 5); why the two days differ is not explained.
  • The restart-pooler window (158 s) fails by a 5 s timeout (TIMEOUT_MS = 5000 in probes.ts), so it carries multi-second resolution despite the rest of the matrix sampling at 500 ms.
  • pg_cron jobs resume across a restart on schedule, with no catch-up doubling - a 1/min heartbeat job had 2 rows before the restart and 5 after, measured separately (supabase-lab edge-resilience W23, 2026-08-16). An instant inside the restart is skipped rather than run late, and a run in flight is recorded failed (pg_cron behaviour, CB04/CB05, 2026-10-28).
  • Provisioning a fresh project to ACTIVE_HEALTHY took 131-159 s (n=5); moving a project into a Free organization cost 75.2 s continuous, because the instance resizes to Nano on arrival.

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.

What counts as answering differs per path.

PathCounts as answering
PostgREST, Auth, Storageany HTTP status other than a 5xx
Realtimethe WebSocket handshake completes
pooler (6543)a connection opens and select 1 returns

The HTTP rule is loose on purpose: anon receives a 401 from /rest/v1/ and a 400 from Storage without a bearer, so a probe demanding 200 would report a permanent outage on two paths. That looseness is what made the REST column wrong: the 401 comes from the API gateway before PostgREST or the database is reached, so the probe answered whether the gateway was up; it never attempted a read, and the Realtime handshake never touches the database either. The 2026-10-23 re-measure kept both probes (renamed rest_http and realtime_ws) and added two that read data: rest_read, an anon GET /rest/v1/pvlab_probe?id=eq.1&select=id that counts only HTTP 200 with exactly that row, and realtime_join, a fresh socket that counts only when the Postgres Changes subscription is confirmed and the probe’s own broadcast comes back1. The pooler’s rule is stricter. EADDRNOTALLOWED under a network restriction is a reply - the pooler is reachable and refusing - but it is scored as a failure, because the probe asks whether a query reached Postgres.

Recovery required five seconds of sustained success rather than one passing sample. The reported recovery time is the first sample of that sustained run; the settle period confirms recovery and is not counted into the outage.

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.

The REST and Realtime columns in this first table are the HTTP-tier probes (the 401 at /rest/v1/ and the WebSocket handshake), so their dashes say only that the API gateway and the WebSocket endpoint answered; the data path is in the 2026-10-23 table. Even so, the paths do not move together: on 2026-10-23 the row read, the Realtime join, Auth and Storage stayed within 8 s of each other in every run except Small to Micro, but the pooler ran longest by 15 to 96 s in six of the ten restarts and resizes (D02 and B’s no-op D03 excluded; C’s Medium to Large, one of the other four, is void since lab #211), and a single duration for “a restart” or “a resize” throws away which path failed, for how long, and by what failure mode.

operationREST (HTTP tier)Realtime (handshake)AuthStoragepooler
restart--75 s78 s158 s
restart, re-check 2026-10-10 (LO05, p50 of 5)-not sampled54 s58 s54 s
network restriction----see below
resize up (micro -> small)--131 s127 s207 s
resize down (small -> micro)--99 s100 s196 s
resize up (medium -> large)--35 s34 s30 s
resize down (large -> medium)--42 s44 s39 s

A dash is zero failed samples. The Medium rows come from a different project and day (medium-serverless MS07, ap-southeast-2, 2026-09-30, n=1 each way); their pooler column is Supavisor 6543, and the same run watched four more paths (up / down): shared 5432 36 s / 39 s, dedicated PgBouncer 6543 34 s / 55 s, direct 5432 35 s / 41 s, and the HTTP-tier REST probe and the Realtime handshake with zero failed samples both ways (MS07 had no row read then; its 2026-10-23 re-run with one is below). The dedicated pooler was the last path back on the way down, answering server login has been failing, cached error: connect failed (server_login_retry) for about 14 s after Postgres was up. The restriction row has no duration because its window is a test parameter; the network restriction section has the dwell and what was measured around it.

The restart row above is n=1 (D01, 2026-08-04). The re-check row is a separate run on 2026-10-10 (LO05): one Micro project in ap-southeast-1, restarted five times in sequence, each after a readiness check, from one macOS orchestrator with IPv6, with REST, Auth, Storage, the pooler on 6543 and the direct port 5432 sampled every 0.5 s. Realtime was not sampled, hence its cell. The figures are the p50 of the five windows; the direct path, which D01 could not reach, had a p50 of 50 s.

pathruns with a failurewindow p50window max
REST (HTTP tier)0 of 5--
Auth5 of 554 s60 s
Storage5 of 558 s59 s
pooler (6543)5 of 554 s151 s
direct (5432)5 of 550 s56 s
  • The first failed sample came 2-3 s after the POST /restart on every path that failed (direct: p50 3 s, max 3 s), which matches the 2 s bite time of D01. POST /restart answered 200 in about a tenth of a second each time.
  • The control plane read ACTIVE_HEALTHY again at 40, 41, 61, 61 and 71 s on a 10 s poll, ahead of the data plane in at least one run: in run 2 the status read ACTIVE_HEALTHY at 41 s while the pooler window ran to 151 s. A script that waits for ACTIVE_HEALTHY can hand traffic to a pooler that has not recovered.
  • The windows split into two groups. Runs 1 and 2 were about 30 s on every path except the pooler’s 151 s in run 2; runs 3 to 5 were 50 to 60 s on every path (Auth 32, 31, 54, 59, 60 s by run; pooler 31, 151, 52, 54, 59 s). Five sequential restarts of one project cannot separate drift across back-to-back restarts from platform load or chance.
  • The 2026-08-04 pooler figure of 158 s is close to this run’s single 151 s and not to its other four. Different day, different project, and Realtime dropped: not a controlled comparison, and the re-check does not replace the D01 row.
  • A second POST /restart sent while the status read RESTARTING returned 200 with an empty body, as the first had (LO05c). Whether it queued a second restart or was absorbed into the first was not sampled.

How these windows read from a client, and the workarounds, is in the incident resilience reference; provisioning and fleet operations through the same API are in the platform Management API guide.

On the same restart that left Auth unavailable for 75 s, the HTTP-tier REST probe and the Realtime handshake answered continuously at 500 ms resolution, which says the gateway and the WebSocket endpoint stayed up and nothing about reads. Auth and Storage also fail differently from each other, HTTP 521 against HTTP 500.

Re-measured with data-path probes (2026-10-23)

Section titled “Re-measured with data-path probes (2026-10-23)”

The same four modules (D01 to D04) ran again on 2026-10-23 with rest_read and realtime_join beside the older probes, on throwaway projects in a Pro organization in ap-southeast-1: project A (Micro, two passes), and projects B and C (created at Medium, one Medium to Large and back each). A and B, then A and C, ran at the same time from one machine. Sampling was every 500 ms, but a failing request often takes its full timeout (5 s for the HTTP probes, 10 s for realtime_join), so a path that fails by timeout is resampled about every 5.5 s or 10.5 s and its recovery end carries up to that much slack. Outage window in seconds, pass 1 / pass 2 on A, B / C on Medium2:

operationREST readRealtime joinAuthStoragepooler
restart (A)247 / 194242 / 191247 / 192250 / 195329 / 216
network restriction (A)0 failed samples, both passes0 failed samples, both passes0 failed samples, both passes0 failed samples, both passes60 / 61, inside the 60 s dwell
Micro to Small (A)171 / 218168 / 214171 / 218174 / 218189 / 216
Small to Micro (A)not sampled / 54not sampled / 67191 / 54196 / 51249 / 150
Medium to Large (B / C)113 / void107 / void115 / void113 / void113 / void
Large to Medium (B / C)217 / at least 101217 / at least 98216 / at least 101217 / at least 104217 / at least 200
  • C’s Medium to Large is void and its Large to Medium figures are lower bounds: the orchestrator machine slept 263 s inside the first, with every path up on the first sample after it, and 43 s just before the second’s first failure, so that outage began unobserved (lab #211). The first published version of this table carried 298 to 299 s for C’s Medium to Large.
  • rest_read failed with HTTP 521 (and HTTP 525 in B’s Large to Medium); realtime_join with pg_changes: {:error, "Too many database timeouts"} or an :rpc_error. The channel join itself was acknowledged; the Postgres Changes subscription is what failed, which a handshake probe cannot see.
  • Through the same windows the HTTP-tier probes had at most one failed sample per run, a single request that timed out.
  • Small to Micro on pass 1 has no data-path figures: its 180 s readiness budget ran out while the test machine slept 263 s (lab #211), so that pass ran with the HTTP-tier probes only, and the data path for that direction is n=1.
  • These windows are well above the 2026-08-04 restart (75 to 78 s for Auth and Storage) and the 2026-10-10 re-check p50s (54 to 58 s). Different day and projects; not explained.

The same day, LO05 re-ran on one Micro project with the data-path probes, five sequential restarts3. REST reads and Realtime joins failed in 5 of 5, p50 150 s and 146 s, max 181 s and 179 s, with the first failure 2 s after the restart at p50; Auth (p50 149 s), Storage (151 s), the pooler (147 s) and direct (145 s) failed alongside. rest_http and realtime_ws failed in 0 of 5. Per run, rest_read was down 181, 154, 46, 46 and 150 s: runs 3 and 4 look like the 2026-10-10 windows and the other three do not.

MS07 (medium-serverless) re-ran Medium to Large and back the same morning with the same data-path probes on two throwaway Medium projects in a Pro organization, E in ap-southeast-2 (the 2026-09-30 region) and F in ap-southeast-1, about 4 minutes and one hour after each create call, n=1 per leg4. Outage window in seconds, up (Medium to Large) / down:

pathE, 4 minE, 1 hF, 4 minF, 1 h2026-09-30
REST read222 / 183140 / 195179 / 190127 / 211not probed
Realtime join212 / 184138 / 200180 / 193118 / 213not probed
Auth221 / 183141 / 195180 / 190128 / 21135 / 42
Storage222 / 184140 / 195179 / 190124 / 21234 / 44
shared 6543222 / 180141 / 194173 / 238125 / 31030 / 39
dedicated 6543235 / 197140 / 209193 / 204128 / 21034 / 55
direct 5432223 / 182139 / 194177 / 189128 / 21035 / 41
  • Every data path went down in all eight legs, for 118 to 310 s; the REST read was within 1 s of Auth in each leg, and the HTTP-tier rest_http and realtime_ws had 0 failed samples throughout. The 2026-09-30 “REST and Realtime untouched” was the HTTP-tier probe.
  • Region does not explain the 2026-09-30 figures (E ran in that region and measured Auth 141 to 221 s), and neither does age since the create call up to one hour. Untested differences between the two days: org plan (Team then, Pro now), Postgres image (17.6.1.166 then, 17.11.0.003 now), the 2026-09-30 project’s two hours of earlier modules, and the platform itself.
  • F’s shared pooler ran longest on the way down (238 and 310 s against 189 to 213 s on F’s other paths), its errors running on to (ECIRCUITBREAKER) too many authentication failures after Postgres was back.

compute-disk D09 re-ran its four resizes on 2026-10-23 on one Pro project in ap-southeast-1 with a row read, /auth/v1/health with the anon key and a Realtime join, sampled every 250 ms, the Realtime join every 1 s, n=1 per op5. The longest REST read outage was 231.465 s on Micro -> Small, 223.28 s on Small -> Large, 152.466 s on Large -> Small and 254.886 s on Small -> Micro, Auth and the Realtime join within 10 s of it in each; the status read ACTIVE_HEALTHY 218, 216, 136 and 245 s after the PATCH and REST reads recovered 18 to 35 s after that. The 2026-08-19 D09 figures that stood in other pages (REST outages of 1.0 and 17.0 s, settle 61-105 s) are withdrawn: the old module dropped outages still open at a phase end and timed PATCHes refused with 429 as resizes.

Bite time is measured from when sampling started, not from when the operation’s own API call returned: sampleDuring in sampler.ts sets t0 before the probe loops begin, and the operation runs inside that same sampled window, so the reported time already includes however long that call itself took to complete. It also differs by operation.

operationbite time, HTTP tierbite time, pooler
restart2 s2 s
network restriction-1 s
resize up2 s3 s
resize down2 s3 s

None of it is a flat 2-3 s range, and none of it is measured against the API’s response.

The explanation this suggests, which was not tested

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

The 2026-08-04 reading was that the four HTTP services insulate a client from a restart because they reach Postgres from inside the project. The data-path probes withdraw that for restarts and resizes: the API gateway and the Realtime WebSocket endpoint kept answering, and PostgREST reads, Realtime joins, Auth and Storage all failed while Postgres was down. What is left is narrower. The services reach Postgres from inside the platform, so a network restriction on the database leaves them serving reads (0 failed row reads and Realtime joins in two runs) while it refuses the pooler, whose client socket is subject to the allow-list.

That is an inference from the result, not something measured here; the topology was not inspected.

gateway and WebSocket answered throughout; reads, Auth and Storage failed with Postgres (2026-10-23)slowest path in the 2026-08-04 runsclientPostgRESTAuthStorageRealtimeSupavisor:6543pooledPostgresdirect :5432NOT TESTED (IPv6 only)

The same topology as a list:

  1. The client reaches four HTTP services - PostgREST, Auth, Storage and Realtime - and, on the pooled path, Supavisor on 6543.
  2. The API gateway and the Realtime WebSocket endpoint answered throughout; on 2026-10-23 PostgREST reads, Realtime joins, Auth and Storage all failed while Postgres was down; the pooler and Postgres were the slowest paths in the 2026-08-04 runs.
  3. The four HTTP services, and the pooler, each reach Postgres.
  4. A direct connection to Postgres on 5432 was not tested here: it is IPv6-only from this vantage.

On 2026-08-04 Auth was down 75 s restarting and 131 s resizing up, 1.75x. The pooler was down 158 s restarting and 207 s resizing up, 1.31x. The two ratios are far enough apart that neither stands in for the other. On 2026-10-23 the order did not hold: Auth was down 247 and 192 s restarting against 171 and 218 s resizing Micro to Small on the same project.

Direction matters unevenly too. On the HTTP tier, growing costs about a third more than shrinking: Auth 131 s up against 99 s down. On the pooler the two directions are closer, 207 s up against 196 s down, which is +5.6% and just outside 5%. The 2026-10-23 runs do not keep that ordering: Auth was down 171 and 218 s up against 191 and 54 s down on one Micro project, so treat the direction as unsettled.

Compute size is set by an addon mutation rather than a resize endpoint: PATCH on the project’s billing addons with an addon_type of compute_instance6. Returning to the smallest size REMOVES the addon rather than setting one, and the subsequent read reports no compute addon.

The network restriction only reaches the pooler

Section titled “The network restriction only reaches the pooler”

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. Those were the HTTP-tier probes; on 2026-10-23 the row read and the Postgres Changes join also had zero failed samples in two runs. The restriction controls database sockets and leaves the HTTP tier and its reads alone.

It did reach the pooler, biting 1 s after sampling started, with a refusal that named the rejected address. The restriction covers the pooled port as well as a direct connection.

The pooler error string, per operation and per day

Section titled “The pooler error string, per operation and per day”
operationpooler error, 2026-08-04pooler error, 2026-10-23
restartFailed to connect to database: {:error, :timeout}terminating connection due to administrator command (D01, both passes; LO05 also logged timeout expired and (EAUTHQUERY) authentication query failed: connection to database not available)
network restriction(EADDRNOTALLOWED) address not in tenant allow_listthe same, both passes
resize upFailed to connect to database: {:error, :econnrefused}terminating connection due to administrator command (A both passes, B, C)
resize downterminating connection due to administrator commandthe same on A (both passes) and B; timeout expired on C

terminating connection due to administrator command is a Postgres message; the others come from Supavisor. On 2026-08-04 the four operations each answered a different way: unreachable, refused, deliberately shut down, or the client not permitted. On 2026-10-23 restarts, resizes up and three of four resizes down all answered terminating connection due to administrator command, so only EADDRNOTALLOWED still names its operation by itself.

The probe timeout is 5 s (TIMEOUT_MS = 5000 in probes.ts), and each path’s sampler loop is serial: await probe.run(); await sleep(intervalMs). A sample that fails BY TIMEOUT can take up to that full 5 s to return, before the 500 ms sleep between samples even starts.

Bite time is recorded against when a sample STARTS, not when it returns (see Time to bite is not one number either), so a slow-to-return timeout does not push the reported bite time later. It does delay every sample after the first failure: on a path that keeps timing out, the next attempt cannot start until the previous one has waited out the full timeout. The loop’s period becomes the 5 s timeout plus the 500 ms sleep, so a path stuck in timeout mode is resampled about every 5.5 s, and the recovery boundary is only located to within that interval.

The restart’s pooler mode is {:error, :timeout} - a timeout, not a fast refusal. Its bite (2 s) was recorded on a sample that started while the previous sample had still returned fast, but for the rest of the 158 s window the pooler was resampled at roughly 5.5 s intervals rather than 500 ms. The 158 s therefore carries several seconds of slack at the recovery end.

In the 2026-08-04 runs, fast-failing modes were unaffected: econnrefused, EADDRNOTALLOWED, HTTP 521 and HTTP 500 returned immediately rather than waiting out the timeout, so those windows kept 500 ms resolution throughout; on 2026-10-23 an HTTP 521 read was resampled about every 1.2 s (206 failed samples across D01’s 247 s window on project A, pass 1).

The ~2x difference between the pooler and the HTTP tier on restart survives this: 158 s and 75-78 s are tens of seconds apart whichever side of the gap carries more slack. It does mean the numbers on this page do not share one sampling resolution.

The 2026-08-04 gap is one run. In the 2026-10-10 restart re-check (LO05, n=5) the pooler’s p50 window was 54 s against 54 s for Auth and 58 s for Storage, and its window was longer than Auth’s in 1 of the 5 runs (run 2: 151 s against 31 s, derived from the per-run windows). A pooler that is reliably about twice the HTTP tier is therefore not a figure to carry from one restart.

Every published figure is also rounded to a whole second: flatten() divides each millisecond count by 1000 and rounds before it is reported, so a window described as “158 s” was some value between 157.5 and 158.5 seconds in the raw sample data.

A freshly created project comes up already at the latest app version: eligible: false, current equal to latest, no upgrade targets. The version selectors on project creation are deprecated and typed null in the published schema6, and the project this rig created took the current default, so the version fields give no way to create one that has already aged past its version.

A field typed null is not necessarily ignored. Measured 2026-10-10 on one Small project per engine in ap-southeast-1 (OR01a, OR01b), postgres_engine is typed deprecated null on the create body in the published OpenAPI document6, yet POST /v1/projects with postgres_engine: "17-oriole" was accepted and applied. The project read back 17-oriole, PostgreSQL 17.6, aarch64, OrioleDB public beta 14, while the heap control, created without the field, read 17.11 on x86_64. release_channel is typed the same way on that body; whether a non-default value of it is honoured was not tested. The engine field selects a different build line rather than an older copy of the default one, and the upgrade eligibility of the Oriole project was not read, so the aged-project limitation below still holds.

Measuring a client-visible upgrade window therefore needs a project that has aged past the current version, which this rig’s throwaway-project method cannot produce. That limits the rig and says nothing about how long an upgrade takes a client.

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 is a value the API returned, not an observed outage - and this page has already shown paths differing by more than 2x on the same operation. Three projects returning exactly 1 also reads like a coarse, published estimate rather than a per-project computation.

What a fresh project costs before its first write

Section titled “What a fresh project costs before its first write”

Measured 2026-08-04 against a live organization, n=5, in the multitenant reference - a separate run from the restart/resize/restriction matrix above, on the same day but a different project set.

OperationMeasuredMethod
Create project -> ACTIVE_HEALTHY131-159 s (n=5, median 131 s)poll GET /v1/projects/{ref}
ACTIVE_HEALTHY -> first write succeeds+1 poll tick (5 of 5 refused the first write)poll, then retry the write
Delete project (API call)~2 sDELETE /v1/projects/{ref}

ACTIVE_HEALTHY was not writable on any of those five projects: each refused its first POST /auth/v1/admin/users with 500 unexpected_failure and accepted the next attempt, one poll tick later. A script that provisions a project and immediately creates a user has to retry.

Two lifecycle runs on 2026-10-10 (LO03, LO05a; one Micro-requested project per create in ap-southeast-1, one Pro organization, a different day and project set from the table above) read differently, and the two sets are unreconciled:

OperationMeasured 2026-10-10Method
Create -> ACTIVE_HEALTHY (LO03, n=3)at the first 10 s poll twice (2 s after the POST), at 145 s once (COMING_UP at 3 s)10 s poll from before the POST
Project that read healthy at the first poll (LO05a)REST, Auth, Storage, pooler and direct all answered 5 s after that pollone probe of each path
Create call (LO03)2358, 1499 and 1656 mstime of the POST /v1/projects call

The earlier table has 131-159 s in 5 of 5 creates and a refused first write in 5 of 5; this run has an immediate ACTIVE_HEALTHY in 2 of 3 and, in the one immediate case that was probed, every path answering. The lifecycle run records probe answers, not a write, so the two do not test the same thing on the write side, and n=3 says both a 2 s and a 145 s create happen rather than how often. The size actually applied was not read back, and no cause for the difference was found.

A restart sent right after a create was refused in one observation (LO05f) and accepted in another:

  • About 70 s after the creating POST, on a project handed over from the duplicate-create check below, POST /v1/projects/{ref}/restart answered 400 with Project restarts are only allowed ten minutes after the creation process has completed. followed by text the artifact cut off. The 70 s is read off the timestamps of two modules, not timed by one.
  • 300 s after the first ACTIVE_HEALTHY read, on a different project, a restart was accepted (200, empty body) and restarted the project. The runs that followed, from 696 s after that read, were accepted with no refusals.

The message says ten minutes and a restart at 300 s went through, so either the stated window is not what is enforced or the clock it runs from (“the creation process has completed”) is not the first ACTIVE_HEALTHY read. The boundary between about 70 s and 300 s is unresolved, and the two observations are on two projects.

Create bodies that are invalid fail fast and create nothing (LO03a, one request each, 55-194 ms):

RequestAnswer
unknown region code400 region_selection.code: Need to use one of available regions.
unknown organization slug404 Not Found
missing name400 name: Invalid input: expected string, received undefined
unknown instance size400 desired_instance_size: Need to use one of available instance sizes.

The organization listing held no project with the run’s name tag afterwards. A create whose response the caller abandoned does create the project (LO04, n=1): with the client wait capped at 1 s, the POST timed out client-side, and the listing then held one project with that name. The identical request re-sent 1 s later answered 400 in 0.17 s with Project with name "<name>" already exists in your organization., and 60 s later there was still one project with that name, ACTIVE_HEALTHY. A client that retries after a lost response therefore has to read that 400 as “probably created, look it up by name”. A retry wrapper that adds a random suffix per attempt would create a second project; that consequence is reasoned from the 400, not run. Not measured: whether the name match is case-sensitive, whether a name freed by DELETE is reusable at once, and two concurrent identical requests.

What moving a project between organizations costs

Section titled “What moving a project between organizations costs”

Measured 2026-08-03 against live projects moved between a Team-plan, a Pro-plan and a Free-plan organization, polling /auth/v1/health every 250 ms across each move, in the org-consolidation guide - a separate run from both tables above.

DirectionMeasured
Paid to paid2.3 s, no outage above the probing host’s noise floor
Paid to Free75.2 s continuous

A Free organization only offers Nano compute7, so the instance is resized on arrival; during that window the edge returns HTTP 521 and HTTP 525 rather than a clean 503. The 75.2 s is the arrival resize, charged to the transfer; a paid-to-paid move produces no data-plane interruption distinguishable from measurement noise.

Measured 2026-10-10 on throwaway projects in ap-southeast-1, driven over the Management API and the shared pooler from one IPv4-only workstation (RP01 to RP03). In the PITR runs REST, Auth and Realtime were probed every 1 s, the pooler by a login every 5 s, and the project status every 10 s; the Free runs polled status every 5 s for pause and every 10 s for unpause. A separate run (RP01) read which restore routes a Pro project exposes and timed nothing, so it has no row in any table above. The tiers, what a restore does not bring back, and the cost of each are in the DR tiers reference; this section adds the per-path outage of the two restores a lab organization could drive.

PITR needs at least Small compute8. The subject was a Pro project on Small with the pitr_7 add-on, a 2 GB gp3 disk, a seed row, a bucket and an object (RP02, five runs). Each run chose a target instant, rotated the database password 15 s later, inserted a second row and called restore-pitr; a control then rotated again, chose a target after that rotation and restored again. That is 9 clean restores: 4 with a target before a rotation and 5 controls.

RestoreBack to ACTIVE_HEALTHY (10 s polling)n
target before a rotation, 10 MB database30 s3
target before a rotation, 574 MB database81 s1
control, target after a rotation40, 30, 41, 40, 30 s (median 40 s, 30-41 s)5

Two of the five controls ran on larger databases (715 MB at 41 s, 574 MB at 40 s), so the 81 s at 574 MB is a single point.

PathFailed samples across the 9 clean restores
REST (HTTP tier), Auth, Realtime (handshake) (1 s probes)none
Storagea 12 s failure window in 8 of 9 restores, 24 s in one; The operation timed out, never an HTTP 5xx
pooler (5 s login poll)5-13 s on the 10 MB runs and on a 715 MB control; 41 s and 23 s on the 574 MB runs

REST, Auth and Realtime recorded one simultaneous timeout on all three, 338 s after the first restore request of the 574 MB run, with no recovery recorded and no explanation. RP02 had no Data API read: its REST probe is the same anon GET /rest/v1/ the gateway answers with 401. Later restores that carried one (rest_read, a service_role read up only on 200) saw it answer 503 PGRST002 for 15 and 14 s in two restores of a database a few minutes old (RP06 R24 and R25, 30 s and 41 s to healthy, 2026-10-23) and for 192 to 544 s in seven restores of 3.2 to 10.5 GB databases (RP07, 2026-10-22 and 2026-10-23; the test machine slept during these runs and the lab has not yet checked which samples that removed, lab #211), while the HTTP-tier REST probe (rest) in the same runs failed in 1 of those 9 restores, for 6 s (R18, 3 GB); Auth failed for 144 s in one 3 GB RP07 restore (R19) and the Realtime handshake for 144 s (R18) and 6 s (R19)9. The pooler errors were the database system is not accepting connections, is starting up, is shutting down, econnrefused and terminating connection due to administrator command. The 41 s pooler window sat on the 574 MB database and the 5-13 s one on a 715 MB database, so the database size does not order them.

  • The first restore-pitr call was refused in 8 of the 9 clean restores with 400 Recovery time target must be within range: <earliest> <= t <= <latest>; where the target was recorded it sat 19-96 s past the quoted upper bound. A retry 60 s later (or 121 s later) was accepted. Retries ran in 60 s steps, so the lag is bounded and not resolved.
  • With pitr_7 just applied, the first backups read already had pitr_enabled=true and a window of 359 or 360 s in 5 of 5 runs.
  • After every clean restore the current password logged in through the pooler and the replaced one was refused. The current password was refused for one 5 s poll between “the database answers” and “the password works” in 6 of 7 restores observed at that resolution: a short stale window of unmeasured length. The changelog says current credentials are reapplied after every physical restore10, and the stored verifier differed from both the pre-rotation and post-rotation value each time, which is what that predicts (an inference from fingerprints).
  • Storage listed, downloaded (bytes equal) and accepted an upload with 200 after every restore. A restore to before an object’s upload made its download 404 and the listing omit it, and writing the same path again answered 200.
  • Every target was minutes old. A target hours or days old, a database over 1 GB and direct 5432 were not run. Seeding a 1500 MB database on the 2 GB disk filled it at 715 MB, and the first restore request after that was refused as Database appears to be unreachable and accepted at 301 s; what filled the disk was not separated.
  • A poller that tried a known-wrong password every 4 s plus a 1 s probe opened the pooler’s circuit breaker within about a minute, and every login, the correct password included, then answered (ECIRCUITBREAKER) too many authentication failures, new connections are temporarily blocked before the password was checked. The poller moved to once a minute for the replaced password, and the breaker reappeared only in the state left by the full disk above.

A Pro organization has no pause (POST /pause answers 400 Project is not free-tier, entitlement project_pausing false on the Pro organization, RP01), so the only pause and unpause a lab organization could run was a Free project (RP03, 3 runs of 2 cycles = 6 cycles, one project per run).

Measurement5 of 6 cyclesThe outlier cycle
POST /pause -> INACTIVE (5 s polling)62, 62, 62, 63, 62 s276 s
unpause -> ACTIVE_HEALTHY (10 s polling)183-212 s444 s
REST answers15-20 s6 s
Realtime answers16-18 s445 s
Auth answers152-175 s445 s
Storage answers181-217 s445 s
pooler login with the current password178-211 s445 s

The status passed through INACTIVE -> COMING_UP -> RESTORING -> ACTIVE_HEALTHY on each unpause. The outlier, the first cycle of the third run, is slow on both operations and no cause was found; other runs were using the same control plane at the time. Its pause-time sampling window ended before INACTIVE, so its pause-path columns are partial. “Answers” on an HTTP path is any non-5xx, so REST at 15-20 s means the gateway answered, not that PostgREST served rows.

  • While a project was pausing, the first failing sample read HTTP 540 on REST and Auth, and on Storage HTTP 500 in 2 of the 3 runs, and the pooler answered tenant/user ... not found.
  • The password set before the pause worked after the unpause (3 of 3 first cycles) and the original was refused. A password change while the project was INACTIVE was refused (400 Cannot reset password for non-active projects), so the stale-credential case the changelog describes10 could not be set up through that route.
  • Storage list, download and upload answered 200 after every unpause.

Restore to a new project (a clone) was not run. The published OpenAPI document has no clone path and no source field on the create body, and the Dashboard’s clone route answers a PAT 401 Unsupported access token. POST database/backups/restore and GET database/backups/restore-point answered 400 This endpoint is unavailable at the moment on a fresh Pro project, and the same by a manual call on a project with pitr_7 applied. Read those as not run.

What a password change and a config change cost

Section titled “What a password change and a config change cost”

Measured 2026-10-01 on one Micro project in ap-southeast-1 (platform-downtime D05), a separate run from the tables above: one PATCH /v1/projects/{ref}/database/password to a throwaway value, then PostgREST, Auth, Storage and Realtime sampled every 500 ms for a 420 s window, with the password restored afterwards. pg_postmaster_start_time() read 2026-10-01T06:38:25.763Z before and after the window, and none of the four paths recorded a failed sample. On that project the password change did not restart Postgres within the 420 s window. The pooler was not probed: its probe authenticates with the password being changed, so a failure there could not be told apart from a stale credential. n=1, on a project provisioned with OpenTofu through the Management API; a project whose database credentials an integration syncs was not tested. The PostgREST and Realtime paths were HTTP-tier probes.

Re-run on 2026-10-23 with the data-path probes (D05, one throwaway Micro project in a Pro organization, ap-southeast-1, two passes)11: pg_postmaster_start_time() read 2026-10-23T07:44:20.792Z before and after both passes, and the REST read and the Realtime join had 0 failed samples (pass 1: rest_read 0 of 767, realtime_join 0 of 662 over 420 s; pass 2: 0 of 154 and 0 of 132 over about 83 s, because the sampler stops once a single realtime_ws failure at +77 s had recovered). The password change did not restart Postgres and did not touch REST reads or Realtime joins. The pooler stayed out of the probe set for the same reason as before.

A Postgres config change through the CLI is documented rather than measured here. The custom-config docs say CLI v2 “checks the parameter’s context and requests the correct action (reload or restart)”: a reloadable setting (pg_settings.context = 'sighup') is applied with a reload, and parameters marked “Restart: Yes” restart the database and any read replicas12. --no-restart defers that restart; the docs warn that leaving a changed parameter pending can break a read replica if the primary or the replica later restarts on its own. max_wal_size and work_mem are listed “Restart: No”. Whether a “Restart: No” change ever restarts the database was not measured.

Whether a platform restart keeps Postgres’s cumulative statistics (pg_stat_checkpointer, pg_stat_statements) was not measured here. Locally, how a stop ended and the Postgres major version decided it for pg_stat_checkpointer (pg_stat_statements was not measured); those measurements are in the Grafana monitoring guide.

n=1 per operation on 2026-08-04, except the restriction and the restart re-checks. One run each for restart (D01) and both resize directions, two runs for the network restriction, all on projects in a single organization; the 2026-10-10 restart re-check (LO05) is five restarts of one other project; on 2026-10-23 each cell is n=2 at most and LO05 again five restarts. The ordering repeated across every operation and project in the 2026-08-04 matrix; the HTTP-tier REST probe answering repeated in the 2026-10-10 re-check (5 of 5), but the row read failed in 5 of 5 on 2026-10-23; the pooler being the slowest path did not repeat (1 of 5 runs on 2026-10-10). The individual seconds are not worth much confidence either: the same restart read 75 s on Auth in D01 and 54 s at p50 in LO05.

500 ms resolution, except where the pooler times out. The HTTP tier and the non-timeout pooler modes were sampled every 500 ms. The restart-pooler window fails by a 5 s timeout and was resampled at that timeout’s cadence for most of its duration, not at 500 ms - see Sampling resolution is not uniform either.

One vantage, IPv4, one account. The direct port was never in the matrix, so the pooler column has nothing unpooled to compare against, and every restart/resize/restriction figure here is from a single organization in ap-southeast-1 observed from Singapore.

Provisioning and transfer are separate sessions. The 131-159 s provisioning window and the 75.2 s paid-to-Free transfer window were measured on different days, by different runs, against different projects than the restart/resize/restriction matrix above - each section carries its own date and method.

The result supports that these paths, on these operations, did not move together on this account, in this region, on these dates. It does not support any figure for a different region, plan, project size, or point in time.

Every method in this doc polls the Management API to watch an operation, so the API’s own throttle is part of the rig’s envelope. Measured 2026-08-17 on a normal paid organization:

  • every response carries x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset, so the budget is readable off the wire. The limit read 120 and the remaining count decremented 1:1 per call on a scoped single-project read. The budget is cumulative across a user’s PATs: alternating two tokens from the same user, each token’s remaining count drops on the other token’s calls - one shared counter per user per scope, so PAT sharding does not multiply it (measured 2026-08-18).
  • a deliberate burst on that cheap scoped read tripped 429 {"message":"ThrottlerException: Too Many Requests"} at request 118, with retry-after: 60 and a clean 200 after the window. On this endpoint the 429 body is the API’s own JSON. Other endpoints and burst shapes have produced an HTML page from the edge layer instead, so a client still treats “non-JSON body” and “JSON 429” as the same back-off signal.

At 500 ms sampling the rig spends 120 requests per minute per poller - exactly the limit the response headers report - which is why the pollers here read x-ratelimit-remaining rather than assuming the ceiling, and why a platform polling a fleet has to budget per-scope, not per-process.

Every row from the 2026-08-04 restart/resize/restriction matrix is n=1 (the 2026-10-10 and 2026-10-23 restart re-checks are n=5, the 2026-10-23 matrix n=2 at most per cell), so it rests on the ordering of the paths rather than on the seconds; the rows from other runs carry their own n. Module ids resolve in the platform-downtime RUNLOG, the lifecycle-ops RUNLOG, the restore-paths RUNLOG, the orioledb RUNLOG, the compute-disk RUNLOG and the instance-sizing RUNLOG.

PracticeEvidenceModule
Budget a Medium to Large resize at up to 6 minutes on every path, reads included.MS07 (2026-09-30), up: Auth 35 s, Storage 34 s, shared 6543 30 s, shared 5432 36 s, dedicated 6543 34 s, direct 35 s; down: 42 / 44 / 39 / 39 / 55 / 41 s; its REST and Realtime 0 failed samples both ways were HTTP-tier probes. On 2026-10-23 two other Medium projects lost REST reads for 113 s up (B; C’s up leg is void) and 217 s and at least 101 s down, and the MS07 re-run on two more took every data path down 118 to 310 s across eight legs (Auth 128 to 221 s), so the 2026-09-30 figures are the outlier, unexplained by region or age since create. Give pooled clients a retry budget past the dedicated pooler’s cached login failure: it answered server login has been failing, cached error: connect failed (server_login_retry) for about 14 s after Postgres was back in MS07. n=1 per project each way.medium-serverless MS07 (2026-10-23); platform-downtime D03, D04, 2026-10-23
Size a maintenance window off the longer of the restart and resize figures.Auth 131 s and the pooler 207 s resizing up, against 75 s and 158 s restarting (2026-08-04); on 2026-10-23 Auth was down 247 and 192 s restarting against 171 and 218 s resizing Micro to Small.D03 vs D01, 2026-08-04
Do not count on PostgREST or Realtime to ride out a restart or resize.Row reads and Postgres Changes joins failed for about as long as Auth and Storage: restart 247 and 194 s, Micro to Small 171 and 218 s, Medium to Large 113 s for the read (C void) (2026-10-23). The 2026-08-04 “zero failed samples” came from an anon GET /rest/v1/ the gateway answers with 401 and a WebSocket handshake.platform-downtime D01, D03, D04, 2026-10-23
Give clients on every path a retry budget of about 5 minutes across a planned resize.The pooler was down 207 s resizing up and 196 s resizing down (2026-08-04). On 2026-10-23 the longest path per resize ran 115 to 249 s (one Large to Medium pooler figure, 200 s, a lower bound), and REST reads failed alongside, so failing reads over to REST does not cover the window.D03, D04; 2026-10-23 re-run
Read (EADDRNOTALLOWED) from the pooler as a restriction; the other strings do not name the operation.On 2026-08-04 {:error, :timeout} was a restart, {:error, :econnrefused} a resize up, terminating connection due to administrator command a resize down, (EADDRNOTALLOWED) address not in tenant allow_list a restriction just applied; on 2026-10-23 restarts and resizes up both answered terminating connection due to administrator command, and one resize down timeout expired.pooler-mode table
When applying a network restriction, include your pooler clients’ egress addresses.Supavisor enforces the allow-list against the client address, so 6543 is covered and the refusal bites 1 s after sampling starts.D02, two runs
Space adjacent compute-resize PATCHes by at least 2 minutes.The second inside the window answered 429 We are still processing addon changes, please try again in 1-2 minutes. On 2026-10-23 none of three follow-on PATCHes was refused when each went out after every data path had been up for 15 s (D09, n=3 PATCHes).compute-disk D09, 2026-08-19
When a script returns a project to Micro, poll the addon GET for null, not for ci_micro.Micro is the absence of a compute addon, and returning to it removes the addon.D04
Retry the first write after ACTIVE_HEALTHY.5 of 5 fresh projects refused the first POST /auth/v1/admin/users with 500 unexpected_failure and accepted it one poll tick later.placement reference, Verified row ‘Create -> healthy, and healthy is not writable’ (2026-08-04, n=5, bash run not in the lab repo); supabase-lab AGENTS.md provisioning note (2026-08-03, 2 of 2, 500 "Database error checking email")
Budget about 75 s of HTTP 521 / HTTP 525 before moving a production project into a Free organization, and accept Nano compute afterwards.The exact window is in the transfer table above; it is the arrival-side resize running as part of the transfer, not the transfer itself.transfer table above (2026-08-03 probe in the org-consolidation guide; no lab RUNLOG)
Do not wake a legacy free-era paused project on a paid org unless you accept it can never be re-paused.The restore was accepted, ACTIVE_HEALTHY arrived about 30 minutes later, and POST /v1/projects/{ref}/pause then answered 400 {"message":"Project is not free-tier. Please downgrade it to free-tier first and try again."}. Pause eligibility follows the org’s current plan, and a Pro organization has no pause at all: on 2026-10-10 a fresh Pro project answered the same 400 Project is not free-tier to POST /pause, with entitlement project_pausing false on the Pro organization (consistent with RP03, where pause and unpause could only be run on a Free project).instance-sizing I03, 2026-08-18; restore-paths RP01, RP03, 2026-10-10
Budget 120 requests per minute per user per scope and read x-ratelimit-remaining off the wire.Two PATs from one user share one counter, so PAT sharding does not multiply the budget. Treat a non-JSON 429 body and the JSON ThrottlerException as the same back-off signal.rate-limits L01a (1:1 decrement on the scoped read), L01c (429 at request 118 with retry-after: 60 and a JSON ThrottlerException body), L01b (two PATs from one user share one counter), 2026-08-17/18; L02 (JSON throttler with retry-after 59-60 in all 15 burst cells, 2xx again after 65 s), 2026-10-13
Check a parameter’s “Restart” column before changing it with the CLI.Per the docs, CLI v2 applies a reloadable (sighup) setting with a reload and restarts the database and any read replicas for a “Restart: Yes” parameter; --no-restart defers that restart and can leave a read replica out of step. Confirm the value took with SHOW <parameter>;. Documented, not measured.none (docs12)
Expect no restart from a database-password PATCH on a project like D05’s (one Micro project, no credential-syncing integration).pg_postmaster_start_time() unchanged across a 420 s window and zero failed samples on the HTTP-tier REST probe, Auth, Storage and the Realtime handshake (2026-10-01, n=1); re-run 2026-10-23 with a row read and a Realtime join, two passes on one Micro project: postmaster_start unchanged, 0 failed rest_read and realtime_join samples. The pooler was not probed.platform-downtime D05, 2026-10-01; D05 2026-10-23
Wait on the pooler path, not ACTIVE_HEALTHY, before sending traffic back after a restart.The status read ACTIVE_HEALTHY again at 40-71 s on a 10 s poll while the data paths had failed at 2-3 s; in run 2 it read healthy at 41 s with the pooler window running to 151 s. n=5, one Micro project.lifecycle-ops LO05, 2026-10-10
Do not send a restart to a project minutes old; read the 400 as “wait”.A restart about 70 s after the create was refused 400 Project restarts are only allowed ten minutes after the creation process has completed; one 300 s after the first ACTIVE_HEALTHY read was accepted on another project. Boundary unresolved.lifecycle-ops LO05f, 2026-10-10
After a create whose response was lost, list by name before retrying.An abandoned create made the project; the identical re-send answered 400 Project with name "<name>" already exists in your organization. n=1.lifecycle-ops LO04, 2026-10-10
Budget 30-81 s for a small PITR restore on Small, with Data API reads and Storage down for part of it.ACTIVE_HEALTHY in 30 s (10 MB, n=3), median 40 s for a target after a rotation (30-41 s, n=5), 81 s at 574 MB (n=1); the HTTP-tier REST probe, Auth and the Realtime handshake no failed 1 s sample, Storage a 12 s window in 8 of 9, pooler 5-13 s and 41 s at 574 MB. A Data API read in two later small restores answered 503 PGRST002 for 15 and 14 s.restore-paths RP02, 2026-10-10; RP06 R24, R25, 2026-10-23 (RUNLOG)
Budget about 3.5 minutes for a Free unpause and retry Auth, Storage and the pooler past it.Unpause to ACTIVE_HEALTHY 183-212 s in 5 of 6 cycles (444 s once), the HTTP-tier REST probe answering at 6-20 s, the Realtime handshake 16-18 s (445 s once), no row read, Auth 152-175 s, Storage 181-217 s, pooler 178-211 s.restore-paths RP03, 2026-10-10
Poll a wrong password at most once a minute against the pooler.A known-wrong password every 4 s opened the circuit breaker within about a minute, and every login then answered (ECIRCUITBREAKER) before the password was checked.restore-paths RP02, aborted first run

Not measured, so not a row: whether the restriction leaving reads alone is a topology property (inferred); direct 5432 during the 2026-08-04 operations (IPv6-only from that vantage); any upgrade window (duration_estimate_hours: 1 is an API value); a restart on any size above Micro, a resize above Large, or a vantage other than Singapore. Read replicas kept GETs answering, stale, while the primary restarted, but not while the replica did: the restart took both nodes down one after the other and the load balancer sent none of the failed reads to the primary (replica-routing RR02i, 2026-10-23, two runs; read replicas).

ClaimHow it was checked
Restart: REST (HTTP-tier probe) and Realtime (handshake) zero failed samples; Auth 75 s, Storage 78 s, pooler 158 sMeasured, 500 ms sampling except the pooler mode (a timeout, resampled at the 5 s timeout’s cadence), one Micro project, 2026-08-04
Restart re-check: REST (HTTP-tier probe) never failed; Auth 54 s, Storage 58 s, pooler 54 s, direct 50 s p50 (maxes 60, 59, 151, 56 s); first failure 2-3 s; ACTIVE_HEALTHY at 40-71 sMeasured, LO05, 0.5 s sampling, n=5 sequential restarts of one Micro project, ap-southeast-1, 2026-10-10; Realtime not sampled
Second POST /restart while RESTARTING returned 200Measured, LO05c, one observation, 2026-10-10; whether it queued was not sampled
Resize up 131/127/207 s, down 99/100/196 sMeasured, same probe set, same project, 2026-08-04
REST and Realtime “never failed” under restart and resizeWithdrawn 2026-10-23: the REST probe was an anon GET /rest/v1/ the API gateway answers with 401 before PostgREST or the database, and the Realtime probe a WebSocket handshake
Data path under restart and resize: REST read 247/194 s restart, 171/218 s Micro to Small, 54 s Small to Micro, 113 s Medium to Large (C void), 217 s and at least 101 s Large to Medium; Realtime join within 6 s of the read in each run except Small to Micro (67 s against 54 s)Measured, D01-D04 with rest_read and realtime_join, 500 ms sampling, three throwaway projects (Micro A two passes, Medium B and C one run each), ap-southeast-1, 2026-10-23
Data path under restart, n=5: REST read and Realtime join failed 5 of 5, p50 150 s and 146 s, max 181 s and 179 sMeasured, LO05 re-run, one Micro project, ap-southeast-1, 2026-10-23; unreconciled with the 2026-10-10 run’s p50s
Restriction leaves the HTTP tier servingMeasured twice, zero failed samples on four paths, 2026-08-04; the row read and the Realtime join also zero failed samples, two runs, 2026-10-23
Restriction bites in ~1 s, recovers in ~2 s, inside a chosen 60 s dwell (62 s total)Measured; the dwell was chosen, not measured
Per-operation pooler error stringsCaptured verbatim from the failing probe
Compute size is an addon mutationRead from the published OpenAPI document6
A fresh project is at the latest version and cannot be created older through the version fieldsRead from the eligibility endpoint and the create schema6; nothing was upgraded
postgres_engine typed deprecated null on the create body yet applied (17-oriole, PostgreSQL 17.6, aarch64, OrioleDB public beta 14; control 17.11 x86_64)Schema read6 against a create and read-back, OR01a and OR01b, one Small project per engine, 2026-10-10
duration_estimate_hours of 1 on three projectsRead from the eligibility endpoint; not an observed outage
Provisioning: create -> ACTIVE_HEALTHY 131-159 s, delete ~2 sMeasured, n=5, 2026-08-04, against a live organization
First admin/users write after healthy fails 500, retry succeedsMeasured, 5 of 5 projects, 2026-08-04
Create -> ACTIVE_HEALTHY at the first poll twice and 145 s onceMeasured, LO03, 10 s poll, n=3, 2026-10-10; unreconciled with the 131-159 s above
A project healthy at the first poll answered on REST, Auth, Storage, pooler and directMeasured, LO05a, one project, 5 s after the poll, 2026-10-10; no write was recorded
Restart 70 s after a create refused 400; restart 300 s after the first healthy read acceptedMeasured, LO05f, two projects, 2026-10-10; boundary unresolved
Invalid create bodies fail fast and create nothing; an abandoned create still creates and the re-send is 400 already existsMeasured, LO03a (4 requests) and LO04 (n=1), 2026-10-10
PITR restore on Small: 30 s (10 MB, n=3), median 40 s after a rotation (n=5), 81 s at 574 MB (n=1)Measured, RP02, 10 s status polling, 2026-10-10
PITR restore per path: REST (HTTP-tier probe), Auth, Realtime (handshake) no failed sample; Storage 12 s; pooler 5-13 s and 41 s at 574 MBMeasured, RP02, 1 s probes (5 s pooler login poll), 9 clean restores, 2026-10-10; no Data API read
PITR restore, Data API read: 503 PGRST002 for 15 and 14 s (two restores, 30 s and 41 s to healthy), 192 to 544 s (seven restores at 3.2 to 10.5 GB; the test machine slept during these runs and the lab has not yet checked which samples that removed, lab #211)Measured, RP06 (R24, R25) and RP07 (R18, R19, R22, R23), service_role read up only on 200, 1 s probes, 2026-10-22 and 2026-10-23
Free pause -> INACTIVE 62-63 s; unpause -> healthy 183-212 s, Auth 152-175 s, Storage 181-217 sMeasured, RP03, 6 cycles (one outlier at 276 s and 444 s), 2026-10-10
Storage HTTP 500 on the first failing sample while a Free project pausedMeasured, RP03, 2 of 3 runs, 2026-10-10
Pro has no pause (400 Project is not free-tier, project_pausing false)Measured, RP01, one Pro organization, 2026-10-10
Restore to a new project, backups/restoreNot run (Dashboard route refuses a PAT; backups/restore answered 400 unavailable)
Current credentials reapplied after a physical restoreDocumented10; the stored verifier differing from both values is an inference from fingerprints
Transfer: 2.3 s paid to paid, no outage above noise floorMeasured, polling /auth/v1/health every 250 ms, 2026-08-03
Transfer: 75.2 s continuous paid to FreeMeasured, same method, 2026-08-03; the cost is the arrival resize to Nano, not the transfer
Direct port 5432Not tested - IPv6-only, unreachable from this vantage
Upgrade windowNot tested - not constructible on a throwaway project
Why the restriction leaves reads aloneNot tested - inferred: the services reach Postgres from inside the platform
Database-password PATCH: pg_postmaster_start_time() unchanged, zero failed samples on the HTTP-tier REST probe, Auth, Storage, the Realtime handshakeMeasured, D05, 500 ms sampling over a 420 s window, one Micro project, n=1, 2026-10-01; the pooler was not probed
Database-password PATCH, data path: postmaster_start unchanged, 0 failed rest_read (0 of 767, 0 of 154) and realtime_join (0 of 662, 0 of 132) samplesMeasured, D05 re-run, two passes on one Micro project (420 s and about 83 s sampled), ap-southeast-1, 2026-10-23
Medium to Large and back, data path: every path down 118 to 310 s in eight legs, Auth 128 to 221 s, REST read within 1 s of AuthMeasured, MS07 re-run, two Pro Medium projects (ap-southeast-2, ap-southeast-1), about 4 min and 1 h after create, n=1 per leg, 2026-10-23
Resize Micro -> Small -> Large -> Small -> Micro, data path: REST read max outage 231.465 / 223.28 / 152.466 / 254.886 s; ACTIVE_HEALTHY at 218 / 216 / 136 / 245 sMeasured, compute-disk D09 re-run, 250 ms sampling, one Pro project, n=1 per op, 2026-10-23; the 2026-08-19 D09 figures are withdrawn
CLI config change: reload for a sighup setting, restart for a “Restart: Yes” parameterDocumented12; not measured
Platform restart and Postgres cumulative statisticsNot tested on the platform

Sample every path continuously while triggering the operation out of band, and record the failure mode beside the duration.

Recovery has to require sustained success rather than one passing sample, and the reported recovery time is the first sample of that sustained run, not the end of it. 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.

A probe timeout that is comparable to the sampling interval coarsens the resolution for whichever failure mode times out rather than fails fast - know which modes do which before quoting a duration as sub-second.

ModuleExperimentTestArtifact
D01platform-downtimed01-restart.tsnone published
D02platform-downtimed02-restriction-flip.tsnone published
D03platform-downtimed03-resize-up.tsnone published
D04platform-downtimed04-resize-down.tsnone published
D01-D04, 2026-10-23platform-downtimerun from the working tree at lab commit 3a2f9bd (as recorded in the artifacts); tests and probes as committed at 4141477, probes in real-probes.tsout/2026-10-23
D05platform-downtimed05-db-password-restart.tsout/2026-10-01
D05, 2026-10-23platform-downtimed05-db-password-restart.tsrun-2026-10-23T07-54-24-330Z, run-2026-10-23T08-03-36-243Z
D09compute-diskd09-compute-resize-timing.ts (2026-08-19); 2026-10-23 versionrun-2026-10-23T07-56-13-302Z
I03instance-sizingi03-legacy-pause-lifecycle.tsnone published
L01arate-limitsr01-rate-limit-surface.tsnone published
L01brate-limitsr01-rate-limit-surface.tsnone published
L01crate-limitsr01-rate-limit-surface.tsnone published
L02rate-limitsl02-burst-shapes.tsrun-2026-10-13T05-09-36-511Z
MS07medium-serverlessms07-resize-medium-large.tsout/2026-09-30
MS07, 2026-10-23medium-serverlessms07-resize-medium-large.tsrun-2026-10-23T07-57-23-944Z, run-2026-10-23T08-53-30-971Z, run-2026-10-23T07-57-57-816Z, run-2026-10-23T08-54-10-992Z
W23edge-resiliencew23-cron-restart.tsout/2026-08-16, out/2026-08-17
LO01lifecycle-opslo01-status-gate.tsrun-2026-10-10T00-43-34-355Z
LO03, LO03alifecycle-opslo03-create-fallback.tsrun-2026-10-10T00-36-15-870Z
LO04lifecycle-opslo04-duplicate-post.tsrun-2026-10-10T00-43-31-013Z
LO05, LO05a, LO05c, LO05flifecycle-opslo05-restart-envelope.ts; LO05a, LO05c and LO05f are sub-results of the same test, written up in the RUNLOGrun-2026-10-10T00-43-31-013Z, run-2026-10-10T00-46-09-369Z
LO05, 2026-10-23lifecycle-opslo05-restart-envelope.tsrun-2026-10-23T02-47-19-611Z
RP01restore-pathsrp01-pro-surface.tsrun-2026-10-10T01-27-07-341Z
RP02restore-pathsrp02-pitr-restore.tsrun-2026-10-10T01-30-06-076Z, run-2026-10-10T01-42-15-008Z, run-2026-10-10T01-42-15-009Z, run-2026-10-10T01-57-31-964Z, run-2026-10-10T02-11-13-334Z
RP03restore-pathsrp03-free-pause-unpause.tsrun-2026-10-10T01-30-06-076Z-rp03, run-2026-10-10T01-43-58-645Z, run-2026-10-10T02-19-59-081Z
RP06, RP07restore-pathswritten up in the RUNLOGout/2026-10-22, out/2026-10-23
OR01a, OR01borioledbor01-environment.tsout/2026-10-10
RR02replica-routingrr02-replica-rerouting.tsrun-2026-10-23T07-57-32-539Z, run-2026-10-23T08-22-50-864Z
  1. supabase-lab, “Data-path probes rest_read and realtime_join,” supabase-lab harness source (2026-10-23). https://github.com/erfianugrah/supabase-lab/blob/4141477/harness/src/real-probes.ts ↩ ↩2

  2. supabase-lab, “Platform-downtime experiment,” supabase-lab runlog, section “2026-10-23 - data-path re-measure” (2026-10-23). https://github.com/erfianugrah/supabase-lab/blob/4141477/experiments/platform-downtime/RUNLOG.md ↩

  3. supabase-lab, “Lifecycle-ops LO05 re-run with data-path probes,” supabase-lab run artifact (2026-10-23). https://github.com/erfianugrah/supabase-lab/blob/4141477/experiments/lifecycle-ops/out/2026-10-23/run-2026-10-23T02-47-19-611Z.facts.md ↩

  4. supabase-lab, “Medium-serverless experiment,” supabase-lab runlog, section “2026-10-23 - MS02 and MS07 re-run with the data-path probes”. https://github.com/erfianugrah/supabase-lab/blob/fb257e4/experiments/medium-serverless/RUNLOG.md ↩

  5. supabase-lab, “Compute-disk experiment,” supabase-lab runlog, section “2026-10-23 - D09 outage accounting fixed; a resize is minutes down, not seconds”. https://github.com/erfianugrah/supabase-lab/blob/fb257e4/experiments/compute-disk/RUNLOG.md ↩

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

  7. Supabase, “Pricing,” Supabase. https://supabase.com/pricing ↩

  8. Supabase, “Database backups,” Supabase Docs. https://supabase.com/docs/guides/platform/backups ↩

  9. supabase-lab, “Restore-paths experiment,” supabase-lab runlog, “A correction (2026-10-23) to the rest path in RP02” and the RP07 table (2026-10-23). https://github.com/erfianugrah/supabase-lab/blob/4141477/experiments/restore-paths/RUNLOG.md ↩

  10. Supabase, “Fixed stale database credentials left behind after a restore,” Supabase Changelog, 2026-07-30. https://supabase.com/changelog/restore-credential-resync ↩ ↩2 ↩3

  11. supabase-lab, “Platform-downtime experiment,” supabase-lab runlog, section “2026-10-23 - D05 re-run with the data-path probes”. https://github.com/erfianugrah/supabase-lab/blob/fb257e4/experiments/platform-downtime/RUNLOG.md ↩

  12. Supabase, “Customizing Postgres configs,” Supabase Docs. https://supabase.com/docs/guides/database/custom-postgres-config ↩ ↩2 ↩3