Neon
Neon branches share storage with their parent, so creating one takes about as long for a hundred gigabytes as for a hundred rows. That is the reason to use it: with the Docker provider, branch time grows with the database, and with Neon it does not.
Configuration
Section titled “Configuration”database: provider: neon version: 17 project: dawn-river-12345678 api_key_env: NEON_API_KEY # the default; name a different variable if you use one max_branches: 10 # your plan's limitproject is the Neon project branches are created in. It is not a secret, so
it lives in the manifest. The API key is, so the manifest names the variable
that holds it and never the value. The key is looked up through the same chain
as everything else: an exported variable, then .env, then the local store.
This provider does not create projects. A project is a billing boundary, and creating one on your behalf is not a decision a tool should make.
Point it at a project that holds nothing else. Everything it creates is named
af-, and it ignores branches that are not, but a project shared with
production work is a project where somebody eventually reads the wrong branch
name.
What it creates
Section titled “What it creates”| Name | What it is |
|---|---|
af-cand-<version> | A golden being built. It exists for the minutes between creating the branch and publishing it. |
af-gv-<version> | A published golden: masked, scanned, and branchable. |
af-env-<environment> | One environment’s database. |
Publishing is the rename from af-cand- to af-gv-, and it happens only after
verification returns without an error. Nothing else marks a golden as
publishable, so a refresh that dies at any point leaves a candidate that
nothing will branch.
The reason it is a rename and not a flag: Neon accepts an annotation when a branch is created and ignores one sent afterwards, and the attestation does not exist until the candidate has been masked and scanned. A rename is the one atomic thing available at the right moment.
Where the attestation lives
Section titled “Where the attestation lives”Inside the golden, in a table:
SELECT version, rules_hash, created_at, attestationFROM _antifailure.golden;In the database rather than beside it, because a verification statement is about that data and should travel with it. A branch of a golden inherits the row, so anyone holding an environment can read what was scanned and what was found without asking the engine.
Direct and pooled connections
Section titled “Direct and pooled connections”Both are used. Services receive the pooled string; a service’s migrate
command receives the direct one, and so do golden refreshes and restores,
because a transaction pooler does not support the session level features
migrations and pg_restore use. Nothing has to be configured for that: the
engine asks for a pooled string whenever the provider declares it has one, and
uses the direct string for both when it does not.
Worth knowing if you call Neon’s API yourself: omitting the pooled parameter
does not mean direct. Neon defaults to the pooled host, so leaving it out hands
a pooled connection to something that needed a direct one, and the failure
looks like a restore that half worked. This provider sends it explicitly in
both directions.
Limits
Section titled “Limits”Neon’s branch ceiling is a property of your plan and the API does not report it
on a path this provider can rely on, so max_branches states it. Reaching
either that number or Neon’s own refusal fails with AF-DB-006, naming the
limit, rather than hanging or returning an unexplained 422.
Free tier projects also cap a branch at 512 MB and keep six hours of history. Both are fine for previews of a small application and neither is enough for a copy of a real production database.
Failure and retries
Section titled “Failure and retries”Everything Neon does is asynchronous: creating a branch returns immediately with operations that are still scheduling, and the branch is not usable until they finish. This provider waits for its own operations before returning, so a connection string it hands back is one you can connect to.
Reads and deletes are retried on a transport failure, a 429, or a 5xx. Creates
are never retried: one that timed out may have reached Neon, and sending it
again would make a second branch. Instead, Branch looks for an existing one
by annotation before creating, so a retried environment gets the branch it
already has.
Cleaning up after a killed run
Section titled “Cleaning up after a killed run”Environments and goldens are removed by af down and af golden gc, and
af env prune --older-than 24h does the first in bulk.
Candidates are the one thing removed without being asked. A candidate is a branch that exists for the minutes between starting a refresh and publishing it, and nothing ever branches from one, so a candidate older than two hours can only be the remains of a process that died. The next refresh removes it.
If a run was killed in a way that left an environment branch behind, it is
still named af-env-<environment>, so af env list and af down reach it.
Conformance
Section titled “Conformance”This provider passes the shared database conformance suite against the real Neon API, not a fake. To run it yourself against your own project:
export AF_NEON_API_KEY=napi_...export AF_NEON_PROJECT_ID=dawn-river-12345678go test ./engine/internal/db/neon -run TestConformance -v -timeout 40mIt creates and deletes branches in that project and asserts at the end that it
left nothing behind. If a run is killed, AF_NEON_SWEEP=1 go test ./engine/internal/db/neon -run TestSweepLeftovers removes what it made.