neondatabase/neon
Storage controller API
The storage controller's HTTP API has four logically separate surfaces, all served from the same axum app in storage_controller/src/http.rs (~91 KB). They are partitioned by URL prefix.
The four prefixes
/v1/... — pageserver-compatible
Mirrors the pageserver's /v1/ API so that callers (e.g. the cloud control plane) can talk to the controller as if it were a pageserver. The controller fans the call out to the right set of pageservers (multiple shards, possibly on multiple nodes).
Examples:
| Method | Path | Effect |
|---|---|---|
POST |
/v1/tenant |
Create a tenant; the controller chooses pageservers to attach shards on. |
DELETE |
/v1/tenant/{tenant_id} |
Delete a tenant on every pageserver that owns a shard. |
POST |
/v1/tenant/{tenant_id}/timeline |
Create a timeline on every shard. |
/control/v1/... — controller-specific
Operations that don't make sense on a single pageserver:
| Method | Path | Effect |
|---|---|---|
POST |
/control/v1/node |
Register a new pageserver. |
PUT |
/control/v1/node/{id} |
Reconfigure a node (online/offline, drain, schedulable, ...). |
POST |
/control/v1/tenant/{tenant_id}/shard_split |
Split shards. |
POST |
/control/v1/tenant/{tenant_id}/migrate |
Migrate a shard. |
GET |
/control/v1/tenant/{tenant_id} |
Describe a tenant: shards, generations, attached/secondary pageservers. |
storcon-cli (in control_plane/storcon_cli/) is the operator-facing CLI for this surface.
/debug/v1/... — for tests and operators
Endpoints that are intentionally not part of the stable API. Used by integration tests to coerce the controller into specific states, and by operators during incidents. Examples include forcing a reconcile, replaying a placement decision, or dumping internal state machines.
/upcall/v1/... — called by pageservers
Pageservers call up to the controller, primarily for the generation handshake:
| Method | Path | Purpose |
|---|---|---|
POST |
/upcall/v1/re-attach |
At pageserver start, register and ask which tenants/generations to attach. |
POST |
/upcall/v1/validate |
Periodically confirm that a tenant's generation is still current. |
Authentication uses the same JWT scheme as the pageserver, with scope pageserverapi.
Compute notification
Although not a request flow per se, the controller also pushes attachment changes to compute nodes via the compute_hook (storage_controller/src/compute_hook.rs, ~46 KB). When the controller migrates a shard or splits, it issues an HTTP request to the affected compute's compute_ctl to update its routing table. The compute then re-establishes connections to the new pageserver.
Database persistence
Everything that survives a controller restart goes through storage_controller/src/persistence.rs (~108 KB) using diesel. Migrations under storage_controller/migrations/ are run automatically at startup.
The set of persisted things is intentionally minimal: tenants, nodes, and a few flags. Tenant shard placement (which node hosts which shard) is rebuilt from observation at startup by querying every registered pageserver. This avoids the complexity of keeping placement durable when the source of truth (the pageservers themselves) is what actually matters.
Reconciliation API contract
When you call /v1/tenant to create a tenant:
- The controller writes a row to the metadata DB (durable).
- It marks the tenant's intent state as "create on N pageservers."
- The reconciler picks this up and calls each pageserver's
/v1/tenantroute. - The HTTP response to the original
/v1/tenantcall is delayed until the reconciler reports success — or returns immediately with a "pending" status, depending on the request mode.
Most write-side endpoints accept a mode=sync|async query parameter or default to a service-specific behavior. See the route handlers in storage_controller/src/http.rs for specifics.
Spec source
Unlike the pageserver and safekeeper, the storage controller does not check in a YAML OpenAPI spec — its routes are large and evolve more rapidly. The most reliable spec is storage_controller/src/http.rs itself plus the test fixtures in storage_controller/src/.
Authentication
pageserverapi JWT scope. Same key configuration as the pageserver. Multiple controller instances share the same JWT verification config.
Multiple instances
Multiple storage controllers can run; one is leader (elected via Postgres advisory locks in storage_controller/src/leadership.rs). Followers serve read-only requests and stand by to take over.
Clients
| Crate | Notes |
|---|---|
storage_controller_client (storage_controller/client/) |
Rust client for the HTTP API. Used by the pageserver (for upcalls), storcon-cli, and tests. |
storcon-cli (control_plane/storcon_cli/) |
CLI wrapping the client. |
See also
- Storage controller — the service overview.
- Pageserver API — what the controller wraps.
docs/storage_controller.md— long-form documentation.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.