PostgreSQL Rules Overview
This section reads like a list of arbitrary instructions: use snake_case, run EXPLAIN before merging, set lock_timeout in migrations, never connect the application as superuser.
Search across all documentation pages
This section reads like a list of arbitrary instructions: use snake_case, run EXPLAIN before merging, set lock_timeout in migrations, never connect the application as superuser.
Each one is really the compressed residue of something that already went wrong once, generalized into an instruction cheap enough to follow without relearning the original incident.
Every durable rule in this section starts from the same place: a query took down a table, a migration hung behind a lock, or a schema hit an ambiguity nobody had settled in advance.
The instruction that follows, "run EXPLAIN before merging a new query" or "set lock_timeout in every migration," is cheaper to state and follow than the incident was to live through, which is the entire point of writing rules down at all.
That framing separates a rule from a style preference, even though both can live in the same document and look similarly terse on the page.
A style preference, like singular versus plural table names, has no failure mode behind it at all; nothing breaks either way, so the only cost of inconsistency is readability and search friction across the codebase.
A rule, in the sense this section uses the word, exists because getting it wrong has a real, describable cost, an outage, a security gap, or a multi-hour migration that should have taken seconds.
A useful analogy: a rules document is a codebase's scar tissue made legible, each scar marking a place the team was actually injured, still protecting against a repeat long after the original cut is forgotten.
Not every rule deserves the same amount of enforcement force, and matching enforcement to actual cost is most of what separates a useful rules document from a list nobody reads.
Rules in this section sit on a rough spectrum from documented convention, expected in review but not mechanically checked, to code-review-enforced, where a human is expected to catch it, to CI-enforced, where a lint rule or test fails the build automatically.
incident happens
│
▼
lesson written down ── documented convention
│ (pattern recurs, is checkable)
▼
reviewers watch for it ── code-review-enforced
│ (a tool can express it)
▼
mechanical gate blocks it ── CI-enforcedA rule generally starts at the top of that chain, because someone just learned the lesson and wrote it down, and earns its way down toward a CI gate only once the team confirms two things: the pattern genuinely recurs, and it is the kind of thing a mechanical check can detect reliably.
Forcing a judgment-dependent rule straight into a CI gate before it is reliably checkable produces false positives, and false positives teach engineers to route around the gate rather than trust it.
The Postgres Project Rules Checklist is the concrete, tiered version of this idea applied at project scale: its 25 items are grouped by how launch-blocking they are, which is itself an application of "not every rule deserves the same urgency."
An exception process is what keeps a mechanically-enforced rule from becoming brittle bureaucracy the first time it meets a legitimate edge case it did not anticipate.
Without one, a team either breaks the rule silently, undermining it for everyone who trusted the gate, or blocks a genuinely valid change, undermining trust in the process itself.
The ADR Template for Postgres exists for exactly this situation: an Architecture Decision Record makes a deviation from the default rule visible and deliberate instead of invisible.
Rules also accumulate a specific kind of decay worth naming directly, rule rot, where an instruction keeps being enforced well after the situation that justified it has changed.
A hand-checked index-usage rule written before pg_stat_statements was standard in every environment might no longer earn its keep once automated query review runs on every PR, not because the rule was wrong, but because better tooling now covers the same underlying goal.
Rules that name the specific failure they prevent age better than rules stated as pure instruction, because a team revisiting "why do we do this" can actually judge whether that failure is still a live risk before deciding to keep, relax, or automate the rule further.
| Enforcement tier | Strength | Weakness | Best Fit |
|---|---|---|---|
| Documented convention | Cheap to write; preserves judgment for genuine edge cases | Depends on memory and culture; erodes under deadline pressure | Context-dependent decisions a linter cannot evaluate |
| Code-review-enforced | Catches nuance a mechanical check would miss | Inconsistent across reviewers and available review time | Judgment-heavy rules with high enough stakes to need a second reviewer |
| CI-enforced gate | Applies uniformly to every contributor, every time | Only works for genuinely checkable patterns; false positives erode trust | High-cost, clearly-detectable violations like missing indexes or superuser app roles |
A style preference, like singular versus plural table names, has no failure mode behind it; a rule exists specifically because getting it wrong has a real, describable cost such as an outage or a security gap.
A rule stated only as required behavior gets followed or silently dropped without anyone able to judge whether it still applies; naming the failure it prevents lets a future engineer actually evaluate that.
It earns that promotion once the pattern genuinely recurs and a mechanical check can detect it reliably without false positives; rules that still need real judgment stay at code-review enforcement.
Because CI gates only work well for patterns a tool can check reliably, and forcing a judgment-dependent rule into a gate produces false positives that teach engineers to bypass or distrust it.
It gives a legitimate edge case a visible, deliberate path around the default instead of forcing a silent violation or blocking a genuinely valid change outright.
It is when a rule keeps being enforced after the situation that justified it has changed, such as a manual index-review rule superseded by automated pg_stat_statements monitoring, and it accumulates as friction without a matching benefit.
Not necessarily - the Postgres Project Rules Checklist groups rules by how launch-blocking they are, since a security rule and an operational-maturity rule do not carry equal risk if left unaddressed.
In practice, whoever owns the incident or the pattern's recurring cost, but the decision should be documented and visible rather than left in one engineer's memory.
Naming is a pure style choice with no failure mode; "index before merge" exists because an unindexed production query has directly caused outages, which is why one belongs to a linter and the other to review.
Only if each rule still names a real, current failure and has enforcement matched to its actual cost; a document that only grows and is never revisited is the bureaucratic version of the same artifact.
Yes, and it should be revisited rather than left in place indefinitely - a rule that has been superseded by better automated tooling is a candidate for retirement, not permanent enforcement.
They generally sit at the highest enforcement urgency, since a violated security rule (like a superuser application role) tends to carry outsized, hard-to-reverse cost compared to a style or naming inconsistency.
Stack versions: This page is conceptual and not tied to a specific stack version.
Reviewed by Chris St. John·Last updated Jul 15, 2026