Multi-Tenant SaaS Analytics
A complete working example showing how multiple tenants share the Akili platform with full data isolation. Each tenant has its own data products, connections, quality SLAs, and governance policies — all running on shared infrastructure with per-tenant resource quotas.
%%{init: {'flowchart': {'curve': 'basis'}}}%%
flowchart TB
subgraph Tenant_A["Tenant: acme-corp"]
A_SRC[Raw Orders\nCDC from PostgreSQL] --> A_AGG[Daily Revenue\nSQL Aggregation]
A_AGG --> A_SERVE[Analytics Dashboard]
end
subgraph Tenant_B["Tenant: globex-inc"]
B_SRC[Raw Transactions\nIncremental Sync] --> B_AGG[Weekly Summary\nSQL Aggregation]
B_AGG --> B_SERVE[Lookup API]
end
subgraph Platform["Shared Platform Infrastructure"]
PG[(PostgreSQL\nRLS per tenant)]
SR[(StarRocks\nCatalog per tenant)]
CEPH[(Ceph RGW\nPrefix per tenant)]
RP[(Redpanda\nTopics per tenant)]
end
A_SERVE --> SR
B_SERVE --> PG
A_SRC --> CEPH
B_SRC --> CEPH
A_AGG --> RP
B_AGG --> RP
Scenario
Section titled “Scenario”Two SaaS customers (tenants) use the same Akili deployment:
| Tenant | Slug | Use Case | Classification | SLA |
|---|---|---|---|---|
| Acme Corporation | acme-corp | E-commerce order analytics | confidential | 24h freshness |
| Globex Inc | globex-inc | Financial transaction summaries | restricted | 1h freshness |
Each tenant has independent:
- Data products and connections
- Quality checks and SLAs
- Governance classification policies
- Resource quotas
- StarRocks analytics catalogs
Tenant Setup
Section titled “Tenant Setup”Creating the Tenants
Section titled “Creating the Tenants”# Admin creates both tenantsakili tenant create acme-corp --display-name "Acme Corporation"akili tenant create globex-inc --display-name "Globex Inc"
# Verify both are activeakili tenant listSetting Up Connections (Per Tenant)
Section titled “Setting Up Connections (Per Tenant)”Each tenant registers its own connections. Connections are tenant-scoped and invisible to other tenants.
Acme Corporation:
# Switch to acme-corp profileakili connection create \ --name acme-orders-db \ --connector-type postgres \ --config '{ "host": "orders.acme-corp.com", "port": 5432, "database": "production", "ssl_mode": "require" }'
akili connection test acme-orders-dbGlobex Inc:
# Switch to globex-inc profileakili connection create \ --name globex-finance-db \ --connector-type postgres \ --config '{ "host": "finance.globex-inc.com", "port": 5432, "database": "transactions", "ssl_mode": "require" }'
akili connection test globex-finance-dbAcme Corporation: Daily Revenue Pipeline
Section titled “Acme Corporation: Daily Revenue Pipeline”product.yaml
Section titled “product.yaml”product: name: daily_revenue namespace: sales version: "1.0.0" archetype: source description: > Daily revenue aggregation from production order database. Classified as confidential due to financial data. owner: data-team@acme-corp.com tags: - revenue - daily - financialinputs.yaml
Section titled “inputs.yaml”inputs: - name: orders kind: source connection: acme-orders-db resource: public.orders strategy: cdc cursor_field: updated_at schedule: "0 */4 * * *" columns: - order_id - customer_id - product_id - amount - currency - region - order_date - created_at
- name: products kind: source connection: acme-orders-db resource: public.products strategy: incremental cursor_field: updated_at schedule: "0 6 * * *" columns: - product_id - product_name - category - unit_costtransform.sql
Section titled “transform.sql”SELECT DATE_TRUNC('day', o.order_date) AS date, o.region, p.category AS product_category, COUNT(DISTINCT o.order_id) AS order_count, SUM(o.amount) AS gross_revenue, SUM(o.amount - p.unit_cost) AS net_revenue, AVG(o.amount) AS avg_order_valueFROM {{ ref('orders') }} oLEFT JOIN {{ ref('products') }} p ON o.product_id = p.product_idGROUP BY 1, 2, 3quality.yaml
Section titled “quality.yaml”quality: checks: - name: no_null_dates sql: "SELECT COUNT(*) FROM {{ this }} WHERE date IS NULL" severity: critical threshold: 0 description: Date must never be null
- name: positive_revenue sql: "SELECT COUNT(*) FROM {{ this }} WHERE gross_revenue < 0" severity: critical threshold: 0 description: Gross revenue must be non-negative
- name: freshness sql: "SELECT DATEDIFF('hour', MAX(date), CURRENT_DATE()) FROM {{ this }}" severity: critical threshold: 24 description: Data must be no more than 24 hours stale
- name: net_lte_gross sql: "SELECT COUNT(*) FROM {{ this }} WHERE net_revenue > gross_revenue" severity: warning threshold: 0 description: Net revenue should not exceed gross revenueserving.yaml
Section titled “serving.yaml”serving: modes: - intent: analytics config: engine: starrocks materialized: true refresh_interval: "1h" indexes: - columns: [date, region] type: sort_keygovernance.yaml
Section titled “governance.yaml”apiVersion: akili/v1kind: Governanceclassification: confidentialaccess: - team: sales-analytics role: viewer - team: data-engineering role: ownersteward: cfo@acme-corp.comcompliance: - soxcompute.yaml
Section titled “compute.yaml”compute: engine: dagster schedule: "0 6 * * *" resources: cpu: "1" memory: 2Gi timeout: 1800 retries: 2 tags: - daily - financialGlobex Inc: Weekly Transaction Summary
Section titled “Globex Inc: Weekly Transaction Summary”product.yaml
Section titled “product.yaml”product: name: weekly_transaction_summary namespace: finance version: "1.0.0" archetype: source description: > Weekly aggregation of financial transactions. Classified as restricted -- contains regulated financial data. owner: compliance-team@globex-inc.com tags: - financial - weekly - regulatedinputs.yaml
Section titled “inputs.yaml”inputs: - name: transactions kind: source connection: globex-finance-db resource: public.transactions strategy: incremental cursor_field: processed_at schedule: "0 */2 * * *" columns: - transaction_id - account_id - amount - currency - transaction_type - counterparty - processed_at - statustransform.sql
Section titled “transform.sql”SELECT DATE_TRUNC('week', t.processed_at) AS week_start, t.transaction_type, t.currency, COUNT(*) AS transaction_count, SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END) AS total_credits, SUM(CASE WHEN t.amount < 0 THEN ABS(t.amount) ELSE 0 END) AS total_debits, SUM(t.amount) AS net_flow, COUNT(DISTINCT t.account_id) AS unique_accounts, COUNT(DISTINCT t.counterparty) AS unique_counterpartiesFROM {{ ref('transactions') }} tWHERE t.status = 'completed'GROUP BY 1, 2, 3quality.yaml
Section titled “quality.yaml”quality: checks: - name: no_null_weeks sql: "SELECT COUNT(*) FROM {{ this }} WHERE week_start IS NULL" severity: critical threshold: 0 description: Week start must never be null
- name: balanced_flow sql: > SELECT CASE WHEN ABS(SUM(net_flow)) > 1000000000 THEN 1 ELSE 0 END FROM {{ this }} WHERE week_start >= DATE_TRUNC('week', CURRENT_DATE()) severity: warning threshold: 0 description: Extreme net flow may indicate data quality issue
- name: freshness sql: "SELECT DATEDIFF('hour', MAX(week_start), CURRENT_DATE()) FROM {{ this }}" severity: critical threshold: 168 description: Data must be no more than 1 week stale
- name: completed_only sql: "SELECT 0" severity: critical threshold: 0 description: Transform filters to completed status -- should always passserving.yaml
Section titled “serving.yaml”serving: modes: - intent: lookup config: engine: postgresql cache_ttl: "10m"governance.yaml
Section titled “governance.yaml”apiVersion: akili/v1kind: Governanceclassification: restrictedaccess: - team: compliance role: viewer - team: finance-engineering role: ownersteward: ciso@globex-inc.comcompliance: - sox - gdpr - pci-dsscompute.yaml
Section titled “compute.yaml”compute: engine: dagster schedule: "0 2 * * 1" resources: cpu: "2" memory: 4Gi timeout: 3600 retries: 3 tags: - weekly - regulated - high-priorityTenant Isolation in Action
Section titled “Tenant Isolation in Action”Storage Isolation
Section titled “Storage Isolation”Each tenant’s data lands in its own S3 prefix:
s3://akili-data/tenant-{acme-id}/products/daily_revenue/data/s3://akili-data/tenant-{globex-id}/products/weekly_transaction_summary/data/Event Bus Isolation
Section titled “Event Bus Isolation”Each tenant gets its own topic namespace:
acme-corp.daily_revenue.data.availableacme-corp.daily_revenue.execution.eventsglobex-inc.weekly_transaction_summary.data.availableglobex-inc.weekly_transaction_summary.execution.eventsAnalytics Isolation
Section titled “Analytics Isolation”Each tenant has a separate StarRocks catalog:
-- Acme queries through their catalogSET CATALOG tenant_acme_corp_catalog;SELECT * FROM sales.daily_revenue WHERE date >= '2026-03-01';
-- Globex queries through their catalogSET CATALOG tenant_globex_inc_catalog;SELECT * FROM finance.weekly_transaction_summary WHERE week_start >= '2026-03-01';Governance Isolation
Section titled “Governance Isolation”Classification and access policies are tenant-scoped:
| Aspect | Acme Corporation | Globex Inc |
|---|---|---|
| Classification | confidential | restricted |
| Access teams | sales-analytics, data-engineering | compliance, finance-engineering |
| Compliance | SOX | SOX, GDPR, PCI-DSS |
| Steward | CFO | CISO |
A user authenticated to Acme’s tenant cannot see, query, or reference Globex’s data products, and vice versa. This is enforced at the JWT claims level — the tenant_id in the token determines which tenant’s data is accessible.
Deploy Both Tenants
Section titled “Deploy Both Tenants”Acme Corporation (profile: acme):
akili product create --name daily_revenue --namespace sales --profile acmeakili product deploy daily_revenue --version 1.0.0 --profile acmeGlobex Inc (profile: globex):
akili product create --name weekly_transaction_summary --namespace finance --profile globexakili product deploy weekly_transaction_summary --version 1.0.0 --profile globexMonitoring Per Tenant
Section titled “Monitoring Per Tenant”Each tenant monitors their own products independently:
# Acme checks their SLAakili governance sla daily_revenue --profile acme
# Globex checks their qualityakili governance quality weekly_transaction_summary --profile globex
# Neither can see the other's dataakili product list --profile acme # Only sees Acme's productsakili product list --profile globex # Only sees Globex's productsRelated
Section titled “Related”- Multi-Tenancy — isolation architecture
- Multi-Tenant Setup — tenant creation and configuration
- Governance — classification and access control
- Security Architecture — authentication and authorization