Est.

Feature Flags Backed by Database Tables vs Dedicated Flag Services

Storage choice for feature flags depends on team size, coordination needs, and operational demands.

· 8 min read
Cover illustration for “Feature Flags Backed by Database Tables vs Dedicated Flag Services”
Engineering Productivity · October 23, 2026 · 8 min read · 1,875 words

A database table is a legitimate feature flag backend. The choice between them turns on who operates flags, where they must agree, and what evidence or experiments the team needs.

What feature flags actually do and why storage is a real engineering decision

Feature flags separate deployment from release and provide an off switch without a revert or redeploy. But an off switch has to work. Its usefulness depends on how quickly a changed value reaches the application, who can change it, and what happens when the flag layer disappears. Storage is therefore a release-control decision.

A team may need little more than a reliable read, or it may need people and services to agree on when to release. The fork is between a database table in infrastructure the team already owns and a dedicated flag service.

What a database-backed flag system actually looks like and what it costs to build

The core can be a table with a handful of columns and a cached read. A minimum schema includes a key, an enabled boolean, an optional rollout_percent, an updated_at, and an updated_by. The intended scope is kill switches, staged rollouts, and per-customer enablement. It is a small system with a narrow job.

Hashing the flag key and user ID together gives each user a stable bucket, so repeated evaluations do not reshuffle the rollout. Every service evaluating that flag must calculate the assignment identically. Otherwise, a staged release turns into a revolving door.

For a table with a dozen records, a primary-key lookup is effectively free. The application can load the collection at the start of each request, or use a short-lived cache if profiling shows a benefit. This covers most real usage for a single-service team with fewer than roughly ten live flags. Targeting rules, multi-service consistency, non-engineer access, historical audit trails, and client-side delivery sit outside that initial scope.

How a simple flag table grows into shared infrastructure through a series of reasonable requests

The first request sounds harmless enough. A second service needs the same flag, so the team must decide how to share configuration, how often consumers refresh it, and what they do when the source is unreachable. Then a second language arrives. Each integration in Swift, Java, or Python needs polling, caching, error handling, and future updates, tested independently.

Percentage rollouts make those separate implementations answerable to one another. If they calculate buckets differently, the feature turns on and off for one customer across different parts of the product. Next, someone who is a non-engineer needs to flip a flag. The table now needs a UI that someone must build, secure, and maintain, or engineering remains the gatekeeper at 2am.

After an incident, someone asks who changed that flag, and when. An updated_by field does not reconstruct the history, so audit logging joins the work. Each request was reasonable on its own. Together, they turn a small configuration store into shared infrastructure coordinating engineering, product, support, security, and operations. Most of the complexity lives in that coordination.

The on-path query problem and what caching actually fixes

Organizational growth is not the only pressure on the table. A flag check in high-frequency middleware can turn a cheap lookup into a query on every request. Each read is small, but the total is not. Where evaluation happens and how often the database must participate matter.

Would a cache settle it? A secondary cache such as Redis reduces database reads, but it gives the team two systems to maintain. Changes must also propagate reliably and quickly enough for a rollback to work. A stale kill switch is an awkward kind of safety equipment.

PostHog illustrates the narrower problem of evaluating flag conditions inside database queries at scale. It is not evidence against database-backed flags for ordinary workloads. Dedicated-service SDKs reduce load on the request path through local evaluation: they download definitions upfront and evaluate them in-process without a request-time network call. Once the process has initialized, it can evaluate flags even when the remote service is down. The last qualification deserves its own section.

The failure mode both approaches share: what happens when the flag layer is unreachable

With a dedicated service, the team still has to design the fallback. If SDK initialization times out at boot, every flag returns its fallback value simultaneously. In many codebases, those defaults were typed as false without much thought. If false disables a rate limiter, the vendor’s outage becomes the application’s outage.

Most dedicated-service SDKs keep their last known ruleset in memory, but a process starting during an outage has not necessarily acquired one. During a rolling deploy with a readiness probe, an initialization timeout can disguise a flag problem as a failed deploy. That disguise delays diagnosis.

A flag table in the application’s primary database often has a simpler failure boundary because it fails with the application’s existing dependency. That is easier to reason about and still requires fallback design. For either backend, the lesson is to use a short, explicit initialization timeout and a boot sequence that continues whether flag initialization succeeds or not. Pick fallback values deliberately and commit them in a defaults file so they become reviewed decisions.

The four operational needs that genuinely tip the decision toward a dedicated service

The clearest signal is a person. When support, product, or another non-engineer needs to toggle flags on their own, the interface becomes part of how the team runs releases. A dedicated service supplies the UI as its core product, while a table requires the team to build and secure one. The kill switch should not depend on finding the engineer who wrote the feature.

A second reason to buy is when the same user must get the same rollout assignment across services and languages. The API, web frontend, and background worker cannot each have their own interpretation of the bucket. Homegrown integrations can achieve this, but every implementation must agree. Errors are subtle and hard to detect in testing.

The third signal is a requirement to reconstruct changes. Who changed which flag, when, and from what value? Compliance may call for that record, or an incident may show that it is missing. Either way, a current value and its last editor are not a historical audit trail.

The fourth signal is genuine statistical experimentation. A percentage rollout assigns users to buckets, while an experiment asks which variant produced which outcome with proper significance testing. That is a different product. Dedicated experimentation platforms offer depth that homegrown flag systems rarely reproduce.

These are workflow and governance needs as much as technical ones. Client-side and mobile delivery add to the work because flags need a public, cacheable, low-latency endpoint that keeps targeting rules private. Without these demands, a table with a cached read remains the more rational choice.

What the dedicated service landscape offers and what it costs

LaunchDarkly restructured pricing in 2025 into Developer, Foundation, Enterprise, and Guardian tiers. Foundation includes scalable feature management and Experimentation, with unlimited projects and environments. A mid-sized engineering team can pay tens of thousands a year, depending on its tier and overages.

Split.io, now Harness FME, emphasizes experimentation through an included statistical analysis engine. It is sold only as a contact-sales Enterprise module under Harness, with no other paid tiers. That can improve total cost for teams that would otherwise buy a separate experimentation tool. Unleash offers a free open-source core and a paid Pay-As-You-Go plan, with the lowest total cost of ownership among the commercial options described here, at the price of owning hosting, upgrades, monitoring, and incidents.

Self-hosting also has an OpenFeature-native option in GO Feature Flag. Grafana adopted it in 2025, choosing an open-source backend rather than a homegrown table or commercial SaaS product. Services on the market may charge for seats, monthly active contexts, or requests for edge evaluation. Usage shape changes the bill. For a small team with heavy anonymous traffic, the bill works out differently under per-context pricing than under per-seat pricing.

OpenFeature and what it changes about the build-vs-buy calculus

OpenFeature puts a provider interface between application code and the flag backend. Instead of calling a vendor SDK directly, the application calls the OpenFeature API. Switching vendors, or returning to a homegrown backend, can then happen without changing application code. The decision becomes less permanent than the integration.

The same interface can front a database-backed provider. A team can start with a table and later move to a dedicated service without an application-code migration. There is still a trade-off. For one service evaluating a small number of flags through one vendor’s SDK, the abstraction adds ceremony without proportionate immediate value. OpenFeature lowers switching costs, but it does not erase the operational criteria that justify switching.

The flag debt problem that neither approach solves on its own

Buying flag infrastructure does not buy code cleanup. A flag that has been fully enabled for months can leave dead branches and associated tests behind. Two interacting stale flags create states that nobody has tested and that are hard to reason about during a failure. The platform may be tidy while the codebase grows a thicket.

This debt accumulates through an ordinary sequence. The feature ships, the team moves on, and the conditional stays because nobody is sure the off path is still unreachable. LaunchDarkly’s Code References CLI helps find flags in code, shows extinction events, and supports archive checks. Harness FME tracks flag usage metrics. Neither automatically removes the conditional logic.

Every flag needs an owner and an expiry at creation, regular review, and deletion as part of finishing the feature. Otherwise, cleanup turns into a task of its own that nobody makes time for. A release is not quite finished while its temporary scaffolding remains in the code.

A decision framework: which signals point to each path

A database-backed system is the right default when only engineers change flags, requirements are boolean or simple percentage rollouts, and services are few. That choice makes more sense with fewer than roughly ten live flags, one language, and no need for audit history to meet rules or follow up on an incident. Query frequency belongs in this assessment too. If checks are not on extremely high-frequency paths, database load is not a reason to buy complexity in advance.

A dedicated service is worth its cost when non-engineers need to control flags on their own, assignments must agree across services and languages, or the team needs a historical audit trail. Statistical experiments provide another clear reason, distinct from simple staged release. Browser and mobile delivery can also make the move worthwhile when targeting rules must stay private.

Unleash and GO Feature Flag suit teams that have reached the organizational limits of a table but want infrastructure ownership and to avoid per-seat pricing at scale. That saves some work on building the product, while leaving the team to host and run it. It does not eliminate ownership.

To keep the choice reversible, implement the OpenFeature interface from the start, accepting its extra ceremony where the immediate need is small. Then choose the backend that meets the team’s operating needs, while keeping the first storage choice open to change. The team needs to identify which responsibility it can no longer sensibly leave attached to a table.

More in Engineering Productivity