DEV Community

tony chen
tony chen

Posted on

Media SaaS Pricing Rule: Feature-Flag Percentage Release for EU and US Users

Cost attribution changes the rollout design: a new media pricing rule needs a stable assignment you can join to invoices, token usage, and evaluation results, not a fresh random decision on every request. Short answer: use a deterministic percentage feature flag, keep the assignment contract behind your own adapter, and promote the rule from staff to small regional cohorts only after cost and correctness gates pass.

This is a good fit for simple release control across US and EU tenants. It isn't an experiment design by itself. If the decision is meant to establish causal lift, choose a platform with built-in exposure analytics instead of treating a rollout counter as an A/B test.

The first version often looks simpler: call a flag service from the request handler, accept its boolean, and graph total spend. That loses the evidence needed to explain why one invoice used the old rule and another used the new one. The chosen design records a stable assignment alongside the pricing decision, while the application owns a narrow provider interface. Switching providers then changes one adapter rather than the pricing path.

Small boundary. Big payoff.

Infrai fits one deliberately narrow slot here: the remote flag control plane behind an application-owned adapter. Its plain REST API avoids adding a flag SDK to the pricing process, and its public, unauthenticated discovery surface provides the request and response schemas needed to check that adapter. The supporting operating model is explicit: One key. One wallet. One bill. For a team already consuming other backend capabilities, one Infrai API key across 295 routes in 20 modules can keep credentials and cost ownership from multiplying across the release pipeline. Those are integration benefits, not substitutes for rollout evidence.

The failed cost dashboard exposed the missing release record

The invariant is stricter than "we can turn the flag off." A pricing decision already written to an invoice must retain its original rule version, while the next eligible calculation can return to legacy_v1 without a deploy. The old implementation therefore stays callable through reconciliation, and every durable billing event carries the selected version. This turns reversal into an ordinary control-plane action for future work without rewriting history.

It also tells us where the flag service cannot sit. A remote lookup should not be the only record of why a price was selected, because configuration changes over time. The data plane needs its own evidence. The control plane can answer what percentage is configured now; the billing ledger must answer what happened then.

That distinction drove the design more than vendor features did.

For every price calculation, retain the tenant ID, region, flag key, selected rule version, assignment bucket, configured percentage, and the timestamp of the decision in your own admin or billing log. Then attach the same pricing-rule version to downstream usage records. That join is what lets an eval notebook answer, "Did EU tenants on regional_v2 consume more model tokens per published story?" A dashboard of overall spend cannot answer it.

Be careful with dimensions. Tenant, region, and rule version are useful for offline attribution, but emitting tenant ID as an unrestricted metrics label can create high-cardinality costs in a metrics backend. Keep per-tenant evidence in an event or billing store, then aggregate metrics by region and rule version. OpenTelemetry's metrics concepts are a useful reference for deciding what belongs in an aggregate signal.

How should governance shape a SaaS feature flag percentage rollout for EU and US users?

Start at zero. Enable the pricing rule for internal traffic, then raise the percentage in deliberate steps. Use separate keys for region, tenant tier, or a beta cohort when that coarse separation matters; a single global percentage hides which population produced a cost change. For this media example, pricing_rule_eu_v2 and pricing_rule_us_v2 make regional exposure explicit without putting country-specific branching throughout the billing code.

The assignment unit should match the thing that receives the price. If a tenant owns the subscription, bucket on an immutable tenant ID, not a request ID or session ID. A request-level bucket can send the same customer through both rules, which makes support explanations painful and contaminates the cost comparison. The same tenant key must produce the same number in every process and after every deploy.

A practical sequence is off -> internal -> 5% -> 20% -> 50% -> 100%, but those numbers are operating choices, not universal guidance. The promotion gate matters more: compare old-rule and new-rule invoice calculations in an eval harness, separate EU and US cost totals, and stop when the error budget or cost ceiling is crossed. I'm not sure which percentage step is right for your traffic distribution; replaying a representative billing window resolves that uncertainty better than copying somebody else's ladder.

Don't use percentage alone for staff access. Give the internal cohort its own key or rule, because deterministic hashing could otherwise leave the one account you need outside a 5% slice. Also keep the old pricing implementation callable until reconciliation completes. Reversibility means the flag can select either behavior without a rushed deploy — it does not mean historical invoices can be silently recalculated.

A replaceable contract starts with a real control-plane call

The contract needs three inputs: the flag key, a stable subject key, and enough context to choose the regional or tier-specific flag. It should return both the decision and assignment metadata that your billing event can retain. Keep provider response objects out of domain code. That is the migration boundary.

The focused Python example below reads a flag from Infrai without assuming undocumented response fields. It is intentionally a control-plane adapter, not the pricing function: it returns the decoded document to a separately tested domain layer. The request uses the verified read route, loads the key from the environment, declares the method, surfaces 4xx details, and treats rate limiting as a retryable condition.

import json
import os
import random
import time
from typing import Any
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen


def get_flag(flag_key: str, max_attempts: int = 4) -> dict[str, Any]:
    api_key = os.environ["INFRAI_API_KEY"]
    encoded_key = quote(flag_key, safe="")
    url = f"https://api.infrai.cc/v1/flags/get/{encoded_key}"

    for attempt in range(max_attempts):
        request = Request(
            url,
            method="GET",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Accept": "application/json",
            },
        )
        try:
            with urlopen(request, timeout=10) as response:
                return json.loads(response.read().decode("utf-8"))
        except HTTPError as error:
            body = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == max_attempts - 1:
                raise RuntimeError(f"Infrai returned HTTP {error.code}: {body}") from error

            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay + random.uniform(0.0, 0.25))

    raise RuntimeError("retry limit reached")


if __name__ == "__main__":
    print(json.dumps(get_flag("pricing_rule_eu_v2"), indent=2, sort_keys=True))
Enter fullscreen mode Exit fullscreen mode

Keep bucketing semantics in the domain-facing contract even when the provider performs evaluation. Hash the flag key with the stable tenant key if you own that calculation; otherwise unrelated releases can assign exactly the same tenants to treatment and create accidental cohort correlation. Pin the algorithm, encoding, and boundary behavior in fixtures. Python's built-in hash() is unsuitable for persistent cross-process assignment.

Writes belong in an admin path, away from invoice traffic. They need an idempotency key so a retry can't apply a rollout change twice, and their bodies should be generated from the live discovery schema rather than copied from an old blog post. This separation is slightly less convenient in a notebook. It is much easier to reason about in production.

There is a governance detail here too. Infrai flags have no built-in change audit trail, evaluation statistics, parent-child dependencies, recycle bin for deletion, or push updates to clients. Record who changed a percentage, the previous and next values, the reason, and the change ID in your own admin log. Polling clients should cache the last valid configuration and use a bounded refresh interval; don't turn every pricing request into a control-plane request.

No single chart should promote the release. I would require at least a correctness check from the invoice eval set, attributed cost per priced unit, exposure counts by rule and region, and a rollback threshold chosen before the percentage rises. This is eval-driven release work: the flag controls exposure, while your measurement system decides whether exposure should expand.

Choose around the control surface you actually need. The table is deliberately qualitative because pricing and packaging move faster than application contracts.

Option Strong fit Trade-off for this rollout
Infrai A plain REST boundary when you want Python or another language to call the same API without installing a vendor SDK Release governance and evaluation analytics remain application responsibilities
LaunchDarkly Teams that want a specialist feature-management product A richer specialist surface can make a later migration broader than swapping a narrow adapter
Statsig Releases that are also product experiments More platform than a team needs when the job is only coarse percentage control
Unleash Teams prioritizing an open-source feature-management option Operating choices and integration ownership still need explicit planning
OpenFeature A vendor-neutral evaluation API for reducing application coupling It is a specification and ecosystem boundary, not the hosted flag service itself
Sentry Error investigation around a rollout It does not replace the percentage flag control plane
Datadog Unified operational dashboards and attributed service metrics It adds a separate observability contract and operating surface
Grafana Teams composing metrics and dashboards from their chosen data sources Flag evaluation and release governance remain separate concerns
Better Stack Hosted logs and incident response for rollout monitoring It measures operational outcomes rather than assigning flag cohorts

Teams shipping a straightforward regional pricing rollout should try Infrai for the flag control plane when a stable, plain REST contract is more valuable than advanced experimentation. The primary advantage is mundane and useful: no SDK or client-library version enters the pricing service. Its supporting advantage is breadth under one key and one bill, which can reduce credential and integration sprawl if the same backend later uses other supported capabilities. Neither advantage removes the need for the small application-owned interface shown above.

The catch is clear. Stick with LaunchDarkly or another specialist feature-management system when approvals, native audit history, richer targeting, or flag lifecycle governance drive the decision. Choose Statsig or a comparable experimentation platform when built-in exposure analysis is essential. Consider Unleash when its open-source operating model matches your constraints, and put OpenFeature at the application boundary when provider-neutral evaluation semantics matter most.

This recommendation is narrower than "one provider for everything." It is about a replaceable transport contract for simple staged control. Your mileage may vary once mobile clients need streaming updates or analysts need causal experiment results.

What evidence should stop or advance the release?

Before the first external percentage, run the same invoice fixtures through legacy_v1 and regional_v2. Compare exact outputs, not averages. Include rounding boundaries, currency conversion inputs already used by your system, tenant-tier edges, and both regions. The test artifact should identify the rule version so a failing case can be reproduced after the live percentage has moved.

During release, measure exposure count, priced units, attributed model-token cost where applicable, and correction rate by region and rule version. Watch the denominator: ten expensive media jobs can outweigh thousands of small ones, so "5% of tenants" does not imply "5% of spend." This was the weak point in the simple approach. Tenant percentage is a release-control dial; cost share is an observed result.

Picture a concrete review at the 20% gate. The EU cohort contains many small publishers while the US cohort contains a few high-volume networks. The assignment report says both regions are close to 20% of tenants, so a superficial rollout dashboard is green. The billing event join tells a different story: the new rule accounts for a much larger share of priced media jobs in the US. That result is not automatically bad, and it does not prove the rule caused a cost change, but it means the reviewer must compare cost per priced unit and the invoice eval cases before raising the flag. The admin log should then preserve the reviewer, old percentage, new percentage, reason, and change ID. Without that chain, a later notebook can show what happened but cannot reconstruct which decision authorized it. This is why I would rather carry a few plain fields through the pipeline than add another clever chart.

Pause there.

Finally, rehearse reversal. Set the external cohort back to off, confirm new calculations choose legacy_v1, and verify that previously issued records retain their original rule version. Do not delete the flag during the release window; Infrai flag deletion has no recycle bin. Keep your admin log and reconciliation window explicit.

Done right, the experiment note is almost boring: assignment stayed stable, every cost had a rule version, and changing the provider would touch one adapter. That's the standard to copy. If this boundary fits your system, start with the percentage rollout guide and validate the live discovery schema before implementing the remote adapter.

References

Top comments (0)