Feature flags for teams running on Postgres

Postgres can hold feature configuration and experiment data, but querying it on every request is rarely the safe architecture.
A boolean column looks like a tempting flag system. The complexity begins when 20 services need the same value, users require deterministic percentage rollout, a product manager changes a production rule, and the database or network becomes unavailable.
A mature design separates control, evaluation, and measurement. A control plane stores versioned definitions and governance. Applications evaluate from a local cache or SDK with explicit fallbacks. Postgres can remain a source of configuration for a small internal system, but it should usually be off the hot path. Exposure and outcome data can then be analyzed in Postgres or replicated to a warehouse.
Decide whether to build or adopt a flag control plane
A small team may start with a table:
This is configuration storage, not yet a safe service. You still need environment isolation, typed values, rule ordering, percentage bucketing, validation, approvals, an audit trail, SDK parity, cache refresh, emergency rollback, and stale-flag cleanup.
Martin Fowler's feature toggle taxonomy distinguishes release, experiment, operational, and permission toggles. Model their owners and lifetimes differently. A short-lived rollout flag should not quietly become a permanent entitlement system.
Adopt a dedicated platform when multiple runtimes, self-service targeting, governance, experimentation, or reliability requirements exceed the cost of operating those components yourself. GrowthBook's feature flag platform provides typed flags, targeting, percentage rollouts, approvals, and experiment integration.
Keep Postgres out of the per-request path
Load a versioned snapshot into each process and evaluate locally. Refresh by polling, a streaming channel, or an invalidation signal. The cached definition is the runtime input; Postgres is not consulted synchronously for every decision.
If you build a lightweight internal system, PostgreSQL 0 and 1 can tell connected clients that configuration changed. The payload should identify a revision or key; clients then fetch authoritative state.
Notifications are not a durable queue. They arrive after transaction commit, reconnects can miss messages, and payloads are intentionally small. Add periodic version reconciliation, reconnect logic, exponential backoff, and metrics for configuration age.
Define startup and outage behavior
Test these cases:
- the process starts while Postgres is unavailable;
- the cached snapshot is older than its freshness target;
- a definition is malformed or uses an unknown rule;
- notifications stop or arrive out of order;
- credentials expire;
- the database recovers after a long interruption.
Fallbacks should be feature-specific. New UI may default to control; a protective circuit breaker may default to the safe constrained path. Persist a last-known-good snapshot when the risk model permits it.
GrowthBook's SDKs use cached feature definitions and local evaluation. The OpenFeature specification offers a vendor-neutral API and provider abstraction if portability is a priority.
Prevent feature flag debt
Add ownership, lifecycle, rollout, and cleanup rules before flags become permanent branches in your codebase.
Read the Scaling GuideMake evaluation deterministic across services
Define one schema for evaluation context: account ID, user ID, plan, region, application version, and other pre-decision attributes. Standardize types and missing-value behavior.
Percentage rollout requires deterministic hashing over a stable attribute, flag key or seed, and allocation range. Never use SQL random() per request. A user should not switch variants because another service evaluates the flag or a process restarts.
Write shared fixtures:
Run the fixtures against every production SDK. Record rule and revision in debug output without logging sensitive context.
Record exposure and outcomes separately
Do not derive enrollment from conversion. Log exposure at the first equal opportunity for the assigned value to affect the unit.
Keep outcome facts such as orders, activations, errors, and sessions in separate models. GrowthBook fact tables can turn those into reusable metrics.
If Postgres is also the transactional primary, send high-volume exposure events through an asynchronous pipeline or separate analytical store. Do not add synchronous analytical writes that extend customer transactions.
Ship in measured stages
Use an explicit progression: internal accounts, a small named cohort, stable percentage rollout, measured experiment where appropriate, broader ramp, then cleanup.
For each stage, define owner, duration, rollback condition, and monitoring. Operational telemetry should detect application errors and configuration freshness quickly. Postgres analysis or a warehouse can measure deeper conversion, revenue, retention, and support outcomes.
Community accounts of building a Postgres-backed flag service highlight the real engineering tradeoffs: cache consistency, partial failure, and the complexity added by each optimization. Use those questions in a design review, then verify them in your own failure tests.
Turn a rollout into a valid experiment
A percentage split is not sufficient. For causal analysis, assignment must be randomized and stable, control and treatment must run concurrently, exposure must be symmetric, and metrics must be defined before results are read.
GrowthBook's feature flag experiments combine flag delivery with experiment configuration. Its warehouse-native architecture can query exposure and outcome models in Postgres.
Build one row per randomization unit before estimating effects:
Use bound parameters, a mature outcome window, and one row per independently assigned unit. GrowthBook's experiment metrics provide a governed layer for definitions and statistical analysis.
Protect database reliability and access
Run sustained analysis on a read replica or analytical Postgres instance when possible. Set connection limits, statement_timeout, and lock_timeout, and inspect plans. Materialize or incrementally build unit-level facts if repeated raw-event joins become expensive.
Create a dedicated reader with CONNECT, schema USAGE, and SELECT on curated views. PostgreSQL's privilege documentation supports granular access. If row-level security is used, test as the final role; RLS policies behave differently for owners and bypass roles.
Views can hide unnecessary columns, but review invoker and owner behavior. PostgreSQL's view documentation explains the security_invoker option and interaction with underlying permissions.
Validate the release evidence
Before interpreting lift, check expected allocation, multiple variation exposure, null IDs, phase boundaries, join rates by arm, pre-treatment metrics, and replica or model freshness. GrowthBook's sample ratio mismatch check helps detect allocation anomalies.
Run an A/A phase for a new flag-to-data path. It can validate bucketing, exposure, joins, and analysis under live traffic, but not a future treatment's product behavior.
When the rollout ends, remove the dead branch and tests, verify permanent behavior, and archive the flag. Retain the definition revisions, metric versions, result, decision, and cleanup reference.
Postgres is a useful part of the system when its role is deliberate. It may store configuration for a small service and can analyze modest experiment data. The application should still evaluate from a reliable local snapshot, and transactional reliability should set the boundary for analytical work.
Operate flags without turning Postgres into a bottleneck
If Postgres stores flag definitions for a small internal control plane, publish versioned snapshots to application instances or a local proxy. Watch propagation time and include the definition version in diagnostics. A change is complete only when the intended fleet has acknowledged or fetched it, not when the database transaction commits.
Protect the database with a bounded connection pool, short control-plane transactions, indexes on lookup and version keys, and conservative timeouts. Administrative list pages should paginate. Audit-history writes should be append-only and asynchronous where the workflow allows. A burst of SDK refreshes after an outage must not create a thundering herd against the primary.
Use a separate analytical connection and preferably a replica for experiment queries. Set application_name, statement_timeout, and concurrency limits. Monitor temporary files, shared-buffer reads, cancellations, lock waits, connection saturation, and replica lag. The analysis system should cache verified summaries instead of rerunning a long join on every dashboard view.
Make configuration and exposure versions joinable
Each exposure should record the flag key, variation, stable unit, environment, definition version, evaluation time, and request or trace identifier when appropriate. Preserve the attributes that explain eligibility only when privacy policy permits; do not serialize an entire user profile for convenience.
When allocation changes, retain phase boundaries. A unit exposed under a 5% internal ramp and later under a 50/50 experiment may have mixed treatment history. The primary analysis should use the first qualifying experiment exposure and report earlier rollout exposure as an exclusion or diagnostic.
Reconcile the control plane with observed exposure counts after every phase change. If the expected and observed ratios differ, examine stale snapshots, targeting, ID normalization, bot or test traffic, and missing ingestion before interpreting product outcomes.
Rehearse removal, not just rollback
A kill switch must have a safe default, an owner, and a tested incident path. A temporary release flag also needs an expected removal date and a code-cleanup issue when it is created. Otherwise, the system accumulates permanent conditional branches whose interactions are difficult to reason about.
After the winning behavior is permanent, remove the losing branch and branch-specific tests, deploy the simplified path, verify exposure logging is no longer required, and then archive the flag. Keep the decision and metric record in the analytical system. The application should retain the product behavior, not the historical experiment machinery.
Know when to move analysis elsewhere
Postgres is appropriate while bounded queries on a replica or analytical instance remain predictable. Replicate data to a warehouse when raw events grow faster than operational retention, experiment refreshes create sustained replica lag, cohort joins spill or time out, or metrics increasingly depend on billing, CRM, and support systems.
Preserve semantics during the move. Run the Postgres and warehouse pipelines against the same frozen experiments and reconcile units, phase boundaries, outcomes, sums, and variances. A database migration must not silently change exposure or attribution rules.
The flag control plane and analysis store can migrate independently. Applications should keep the same typed evaluation contract and safe defaults while the measurement layer changes. That separation limits risk and gives the team a clean rollback path.
Keep a compact operating record
For every flag, retain its type, owner, creation date, expected removal date, environments, allowed value schema, default behavior, and incident contact. For each change, record actor, review, old and new configuration versions, intended traffic, reason, and rollback condition.
Join that record to observed first exposures and release health. A control-plane audit shows what was configured; exposure data shows what users actually received. The difference between them is a diagnostic signal. Review it after every material ramp and before interpreting an experiment.
This record also makes cleanup tractable. Teams can find temporary flags past their expected lifetime, locate the owning code paths, and preserve the decision evidence without preserving dead branches.
Review the record at a regular flag council or engineering maintenance cadence. Escalate flags with no owner, no observed exposure, incompatible value schemas, expired removal dates, or permanently enabled temporary paths. Measure cleanup work as part of the release, not optional debt after success.
When configuration storage or analysis moves away from Postgres, export this history in a documented schema and reconcile active definitions before cutover. The migration should preserve runtime defaults, rollout state, exposure identifiers, and audit context.
Connect Postgres flags to outcomes
Use local flag evaluation and transparent database-backed metrics to control releases and measure their impact.
Start Building FreeRelated Articles
Ready to ship faster?
No credit card required. Start with feature flags, experimentation, and product analytics—free.


