Implement XaaS Resource Metering
This guide explains how to implement resource metering and hierarchical quota enforcement in Framework M. This is essential for building XaaS (X-as-a-Service) platforms where you need to track usage (e.g., AI tokens, API calls, storage) and enforce billing limits across complex tenant hierarchies.
1. Recording Usage via MeteringProtocol
The MeteringProtocol is the core "Writer" for usage facts. In your service logic, you don't worry about where the data goes; you simply emit a UsageEvent.
Example: Metering an AI Completion
from framework_m_core.interfaces.metering import MeteringProtocol, UsageEvent
from datetime import datetime
class AIService:
def __init__(self, meter: MeteringProtocol):
self.meter = meter
async def generate_text(self, tenant_id: str, prompt: str):
# 1. Perform the work
response = await self.llm.call(prompt)
tokens_used = len(response.split())
# 2. Record the usage fact
await self.meter.record_usage(UsageEvent(
tenant_id=tenant_id,
feature_name="llm_tokens",
quantity=tokens_used,
timestamp=datetime.utcnow(),
metadata={"model": "gpt-4"}
))
return response
2. Enforcing Hierarchical Quotas
Quotas leverage the TenantContext.lineage to check limits at multiple levels of the hierarchy (e.g., a "Project" quota inside an "Organization" quota).
The "Zero-Trust" Quota Check
Framework M includes a QuotaPolicyHandler that automatically checks metering facts before critical actions.
- Define the Quota: Set a limit in your Control Plane or configuration.
- The Check: When a user tries to consume more resources:
- The framework resolves the
lineage(e.g.,["org-1", "project-a"]). - It calls
get_usage()for bothorg-1andproject-a. - If either exceeds their respective quota, the action is blocked with a
QuotaExceededexception.
- The framework resolves the
3. Scaling to Hyperscale (Lagos/ClickHouse)
For "Indie" or "Production" scale, the Postgres Metering Adapter stores usage in a standard DocType table. For Hyperscale, you bridge these events to a specialized sink like Lagos or ClickHouse.
Pattern: The Async Event Bridge
Instead of writing directly to ClickHouse from your request thread, use the Webhook DocType or a specialized adapter to push events out-of-process.
To enable a Hyperscale Sink (e.g. Lagos):
- Configure the Adapter: Swap the default Postgres adapter for a Lagos-aware one.
- Set the Sink URL: Configure the endpoint where your ClickHouse/Lagos ingestion service lives.
# framework_config.toml
[metering]
adapter = "framework_m_standard.adapters.metering.LagosMeteringAdapter"
sink_url = "https://ingest.lagos.internal"
batch_size = 100
4. Best Practices
Use "Zero-Cap" for Internal Tenants
In your PermissionProtocol, you can assign a "Zero-Cap" policy to internal or admin tenants. This ensures the MeteringProtocol still records the usage (for auditability) but the Quota Handler never blocks them.
Decouple Writing from Reading
Always use the MeteringProtocol to write facts. Never try to calculate quotas by querying your primary business tables (e.g., SELECT count(*) FROM orders). This keeps your Data Plane fast and your accounting logic isolated.
Leverage the Cache
When using get_usage for real-time enforcement, ensure your MeteringProtocol adapter is configured with a CacheProtocol (Redis) to avoid hitting the database on every check.
Next Steps
- See the Tenancy Isolation Explanation for details on RLS.