Open-Source Wikis

/

Neon

/

How to contribute

/

Patterns and conventions

neondatabase/neon

Patterns and conventions

These are the conventions that show up across most of the Rust code. Following them keeps PRs reviewable and avoids inconsistency drift in a 300k-LOC codebase.

Async runtime

Everything async is on tokio. The workspace pins tokio = { version = "1.43.1", features = ["macros"] } and most binaries call #[tokio::main(flavor = "multi_thread")] directly.

Two locally-relevant points:

  1. tokio-epoll-uring is used in the pageserver hot path for low-overhead async file I/O on Linux. See pageserver/src/virtual_file.rs. Don't replace this with naive tokio::fs in performance-sensitive code.
  2. Cancellation tokens. tokio_util::sync::CancellationToken is the convention for plumbing shutdown through long-running tasks. The task_mgr in pageserver/src/task_mgr.rs is the central registry.

Error handling

The two top-level error crates are anyhow and thiserror.

  • anyhow::Error + ? is the default for binary code paths and most internal APIs. Cargo.toml enables the backtrace feature so panics and bail! calls capture useful traces.
  • thiserror::Error is used for typed errors at API boundaries — the pageserver's PageReconstructError, the safekeeper's WalServiceError, the proxy's Error. Reach for thiserror when callers need to match on the variant.

docs/error-handling.md is the authoritative document for this. The short version:

  • Internal helpers: anyhow::Result<T>.
  • Public API surfaces (HTTP routes, gRPC handlers): a typed error that maps to a status code or a gRPC Status.
  • Don't unwrap() or expect() outside of #[cfg(test)] and known-impossible cases. When you do, leave a comment.

Logging and tracing

tracing::info!, warn!, error!, debug!, trace! are the only logging macros used. The log crate appears only as a transitive dependency or in third-party adapters.

Span discipline:

  • Most subsystems wrap top-level work in a span with stable fields, e.g. tracing::info_span!("tenant", id = %tenant_id, shard = %shard_id). These propagate through Future execution via .instrument(span).
  • Long-running tasks set Span::current().record(...) to add fields as they discover more context.
  • The pageserver convention is to log a single line per request at info with structured fields (request_id, tenant_id, timeline_id, lsn, key).

libs/tracing-utils/ provides the JSON formatter, OTel exporter, and a Tracer builder used by every binary.

Metrics

The metrics crate (libs/metrics/) is the only Prometheus client allowed in workspace code. Convention:

  • Define metrics as static Lazy<Histogram> / Lazy<IntCounter> etc. in metrics.rs per crate.
  • Names use <service>_<noun>_<unit> form, e.g. pageserver_layer_count, safekeeper_flush_lsn.
  • Histograms have explicit bucket lists tuned for the metric range; don't reach for the default buckets.
  • Avoid high-cardinality labels (no per-tenant labels on hot-path counters; the pageserver_smgr_query_seconds family is an exception that's been carefully reviewed).

HTTP

axum is the framework for new HTTP servers. Older code uses routerify or raw hyper. New surfaces should be axum.

OpenAPI specs live alongside the code as openapi_spec.yaml (e.g. pageserver/src/http/openapi_spec.yml, compute_tools/src/openapi_spec.yml). They are linted in CI by the lint-openapi-spec make target via redocly.

gRPC

tonic is the gRPC stack; prost for protobuf codegen. Each service that exposes gRPC has a proto/ directory or generates from .proto files at build time using tonic-build. The pageserver's gRPC page API is in pageserver/page_api/ and the storage broker is storage_broker/proto/.

Postgres FFI

libs/postgres_ffi/ wraps Postgres struct definitions, magic numbers, and WAL record layouts. There is one source of truth (the vXX/ submodule) per supported Postgres version. When you add a Postgres-side struct, update postgres_ffi for every supported pg_version. The postgres_ffi/wal_craft/ test crate generates synthetic WAL streams used in unit tests.

Configuration

Most binaries take a TOML config file. Conventions:

  • Config structs live in config.rs per crate (pageserver/src/config.rs, safekeeper/src/config.rs, proxy/src/config.rs).
  • Use serde_with for things like humantime durations and base64 byte arrays.
  • All numeric tunables that affect behavior have a default in code and a comment naming the rationale.

docs/settings.md is the legacy human-facing tunable list. reference/configuration.md in this wiki has more.

Async-friendly locks

The codebase uses both parking_lot::Mutex (fast, blocking) and tokio::sync::Mutex (slower, async). The rule is: hold locks across .await only if you really need to, and prefer parking_lot for short critical sections that don't .await.

arc-swap, clashmap (a fork of dashmap), and papaya are used in performance-sensitive lookup tables. arc_swap::ArcSwap<T> is the normal pattern for "rarely-updated, frequently-read config".

Panics and process death

Panics on the hot path are escalated:

  • pageserver/src/lib.rs installs a panic hook that records a metric, logs an error!, and (in production) calls std::process::abort() after a short delay. Tests disable the abort.
  • The tracing-error crate captures panic backtraces into log records.

If you find yourself wanting to panic!() to indicate "this should never happen", prefer unreachable!() or a typed error variant. Reserve panic! for true invariant violations.

Naming

  • Snake-case file names; Rust's default in-module naming (CamelCase types, snake_case fns).
  • The crate dir matches the crate name in Cargo.toml (e.g. libs/pageserver_api/pageserver_api).
  • Public APIs use the singular tense: Tenant, not Tenants; Timeline, not Timelines.
  • Postgres-aligned terms keep their Postgres spelling: lsn (not seqno), relnode, forknum, etc.

Python style

For the integration tests in test_runner/:

  • Code style enforced by ruff format and ruff check. Config in pyproject.toml.
  • Type-checked with mypy. Run from the repo root (mypy reads pyproject.toml from there).
  • Pytest fixtures from test_runner/fixtures/ are the building block; new tests should compose fixtures rather than re-implement setup.

Comments and TODOs

The repo has ≈785 TODO|FIXME|XXX|HACK comments. New TODOs should be specific (link a GitHub issue if there is one). The cleanup-opportunities/ view in this wiki captures the standing list — see Cleanup opportunities / TODOs.

File reading checklist when starting in a new area

When you first land in an unfamiliar subsystem, the fastest way to acclimate is to read three files:

  1. The README.md next to the crate, if there is one.
  2. The crate's lib.rs or main.rs — these are kept short and serve as a tour.
  3. The largest *.rs file in the directory. In Neon that's almost always where the central state machine lives (e.g. tenant.rs, timeline.rs, service.rs).

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Neon wiki | Factory