Skip to content

PATTERN Cited by 1 source

Tier-tagged query isolation

Pattern: in a SaaS application with differentiated subscription tiers, tag every database query with the authenticated user's tier (e.g. tier='FREE' / 'PRO' / 'ENTERPRISE') at authentication-middleware time, and define per-tier resource budgets so that lower-tier traffic cannot degrade higher-tier experience. Enforces the SLA differential at the database tier rather than relying solely on application-layer rate limits.

Shape (Go)

Three components (Source: sources/2026-04-21-planetscale-patterns-for-postgres-traffic-control):

(1) Tier type + context setter:

type Tier string
const (
    TierFree       Tier = "FREE"
    TierPro        Tier = "PRO"
    TierEnterprise Tier = "ENTERPRISE"
)

func WithUserTier(ctx context.Context, tier Tier) context.Context {
    tags := tagsFromContext(ctx)
    tags["tier"] = string(tier)
    return contextWithTags(ctx, tags)
}

(2) Authentication middleware that resolves the user and injects the tier:

func AuthMiddleware(users *UserService, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user, err := users.Authenticate(r)
        if err != nil { http.Error(w, "unauthorized", 401); return }
        ctx := WithUserTier(r.Context(), user.Tier)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

(3) Per-tier Traffic Control budgets:

  • tier='FREE' — conservative Server share + low max concurrent workers.
  • tier='PRO' — moderate limits.
  • tier='ENTERPRISE' — unbudgeted, or a high ceiling as a runaway-protection safety net.

The pattern rides on the context-threaded tag propagation substrate — tier joins whatever other tags other middlewares added.

Why database-tier enforcement

Application-layer rate limits (token buckets on request count) can't distinguish a cheap query from an expensive one. A free-tier user who triggers a slow 500-row JOIN consumes more database CPU than an enterprise user's fast indexed lookup — but both count as "one request" at the rate limiter.

Tier tags + Traffic Control budgets measure at the resource level (CPU, I/O, worker-process concurrency) rather than the request level. A free-tier user whose query is expensive is throttled on exactly the resource they're consuming excessively, and the throttle kicks in at the database extension layer — no application code change needed.

Compositions

The pattern is most valuable when composed with route tags (patterns/route-tagged-query-isolation). Canonical worked composition (Source: sources/2026-04-21-planetscale-patterns-for-postgres-traffic-control):

"A budget matching tier='free' AND route='api-export' can be stricter than a budget on tier='free' alone. Enterprise export requests get more headroom than free-tier export requests."

Three independent rule levels coexist:

  1. tier='FREE' — baseline free-tier budget.
  2. route='/api/export' — baseline expensive-endpoint budget.
  3. tier='FREE' AND route='/api/export' — the AND-combination is tighter than either alone.

All three apply simultaneously via AND-composition; a free-tier export request must satisfy all three caps.

Under spike: live-disable lower tiers

Paired with spike- survival shedding, the tier axis becomes the load-shedding lever: under a load spike, operators can disable the tier='FREE' budget entirely from the Traffic Control UI — free-tier traffic is blocked, enterprise and pro keep running. Same mechanism the priority-tier framing uses, specialised to subscription plans.

Operational notes

  • Auth middleware must run before DB access. The tier tag must be in context before the first query runs. Unauthenticated endpoints (login, signup) won't carry a tier tag; budgets targeting tier=* won't catch them — use a separate route budget or an application_name='web' budget for these paths.
  • Tier transitions during a session. If a user upgrades mid-session, the tier in context reflects the user object at authentication time. For mid-request upgrade semantics, re-authenticate or update the context explicitly.
  • Workspace / organisation-level tiering adds a dimension: a single user may be in multiple workspaces at different tiers. The tag should reflect the effective tier for the current request — typically the workspace's, not the user's.
  • Internal service callers (machine-to-machine) need their own tag (tier='INTERNAL' or app='internal') to avoid being caught by user-tier budgets.

Seen in

Last updated · 378 distilled / 1,213 read