Skip to content
bitzorcas
中EN

Reference

Quotas and Rate Limiting: Host Token Bucket & Billing Quota

Tell apart the Host-level Redis token bucket limiter from the PlatformBilling quota check: where 429 belongs, how QuotaService.Check works, usage submission commands, and idempotent usage facts.

Last updated

A multi-tenant platform must answer two different questions: “will this request overwhelm the system?” and “has this customer paid for this call?” Two independent mechanisms answer them, and conflating them is a classic architecture mistake:

  • Noisy neighbors are the Host rate limiter’s job: a Redis token bucket short-circuits floods at the edge;
  • Plan overruns belong to PlatformBilling metered quotas: remaining allowance decides approval or rejection.

BitzOrcas has no cross-cutting “quota pipeline.” No quota behavior exists in the Mediator pipeline, and declared attributes such as [QuotaConsumer] do not exist either. There are exactly two enforcement points: Host middleware and explicit quota-service calls.

Division of responsibilities

tokens exhaustedallowedOverQuotaBehavior=RejectAllowAndRecord

Inbound request

Host limiter
RedisTokenBucket / sliding window

429 Too Many Requests

Business handler

Billing quota
QuotaService.Check(Plan, UsageMeter, ...)

quota error → 403 semantics

execute and record usage

ConcernOwnerTypical signal
Abuse and floodsHost RateLimitingHTTP 429
Plan call allowancesPlatformBilling quotaquota error in business result
Usage reconciliationUsageMeter.Record/api/platform-billing/usage

Step one: Host rate limiting

The API composition root enables rate limiting by default (RateLimiting:Enabled=true). RedisTokenBucketRateLimiter provides a Redis-backed token bucket consistent across instances, and partition policies also include sliding-window and fixed-window variants. An exhausted bucket returns 429 before the request ever reaches the Mediator pipeline.

Step two: billing quota checks

Plan quotas are not attached to pipelines; they are business semantics checked explicitly in the application layer. QuotaService.Check compares the active plan against UsageMeter balance:

Check plan balance before executing
// Plan is the tenant's effective plan; UsageMeter is the authoritative usage aggregate.
var check = quotaService.Check(plan, usageMeter, meterCode: "reporting.advanced_export", quantity: 1);
// An exhausted quota is not an exception; it is an expected business rejection with a stable code.
if (check.IsFailure)
return Result.Failure<string>(check.Error);
Build a usage record
// usageId is client-assigned; resubmitting the same usageId never creates a second fact.
var record = RecordUsageCommand.Create(
tenantId: currentTenant.Tenant.EffectiveTenantId,
usageId: usageId,
meterCode: "reporting.advanced_export",
quantity: 1,
occurredAt: clock.UtcNow);

RecordUsageCommand is exposed via [GenerateEndpoint(HttpRoute.Post, "/api/platform-billing/usage")], requires the platform-billing.usage.create permission, and lands through UsageMeter.Record(usageId, quantity, occurredAt).

Summary

  • 429 always originates in the Host limiter; plan overruns surface as business-layer quota errors. Front ends branch retry logic on that distinction instead of treating both as server faults;
  • quota atomicity lives in the database (conditional updates on the usage aggregate), not in a pipeline — keeping usage facts independently auditable from business transactions;
  • if you need Feature-level runtime switches, that is License Feature or Tenant Feature Entitlement territory, not this page’s machinery.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%