Skip to content
GitLab

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

Two SaaS customers (tenants) use the same Akili deployment:

TenantSlugUse CaseClassificationSLA
Acme Corporationacme-corpE-commerce order analyticsconfidential24h freshness
Globex Incglobex-incFinancial transaction summariesrestricted1h freshness

Each tenant has independent:

  • Data products and connections
  • Quality checks and SLAs
  • Governance classification policies
  • Resource quotas
  • StarRocks analytics catalogs
Terminal window
# Admin creates both tenants
akili tenant create acme-corp --display-name "Acme Corporation"
akili tenant create globex-inc --display-name "Globex Inc"
# Verify both are active
akili tenant list

Each tenant registers its own connections. Connections are tenant-scoped and invisible to other tenants.

Acme Corporation:

Terminal window
# Switch to acme-corp profile
akili 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-db

Globex Inc:

Terminal window
# Switch to globex-inc profile
akili 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-db
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
- financial
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_cost
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_value
FROM {{ ref('orders') }} o
LEFT JOIN {{ ref('products') }} p ON o.product_id = p.product_id
GROUP BY 1, 2, 3
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 revenue
serving:
modes:
- intent: analytics
config:
engine: starrocks
materialized: true
refresh_interval: "1h"
indexes:
- columns: [date, region]
type: sort_key
apiVersion: akili/v1
kind: Governance
classification: confidential
access:
- team: sales-analytics
role: viewer
- team: data-engineering
role: owner
steward: cfo@acme-corp.com
compliance:
- sox
compute:
engine: dagster
schedule: "0 6 * * *"
resources:
cpu: "1"
memory: 2Gi
timeout: 1800
retries: 2
tags:
- daily
- financial
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
- regulated
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
- status
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_counterparties
FROM {{ ref('transactions') }} t
WHERE t.status = 'completed'
GROUP BY 1, 2, 3
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 pass
serving:
modes:
- intent: lookup
config:
engine: postgresql
cache_ttl: "10m"
apiVersion: akili/v1
kind: Governance
classification: restricted
access:
- team: compliance
role: viewer
- team: finance-engineering
role: owner
steward: ciso@globex-inc.com
compliance:
- sox
- gdpr
- pci-dss
compute:
engine: dagster
schedule: "0 2 * * 1"
resources:
cpu: "2"
memory: 4Gi
timeout: 3600
retries: 3
tags:
- weekly
- regulated
- high-priority

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/

Each tenant gets its own topic namespace:

acme-corp.daily_revenue.data.available
acme-corp.daily_revenue.execution.events
globex-inc.weekly_transaction_summary.data.available
globex-inc.weekly_transaction_summary.execution.events

Each tenant has a separate StarRocks catalog:

-- Acme queries through their catalog
SET CATALOG tenant_acme_corp_catalog;
SELECT * FROM sales.daily_revenue WHERE date >= '2026-03-01';
-- Globex queries through their catalog
SET CATALOG tenant_globex_inc_catalog;
SELECT * FROM finance.weekly_transaction_summary WHERE week_start >= '2026-03-01';

Classification and access policies are tenant-scoped:

AspectAcme CorporationGlobex Inc
Classificationconfidentialrestricted
Access teamssales-analytics, data-engineeringcompliance, finance-engineering
ComplianceSOXSOX, GDPR, PCI-DSS
StewardCFOCISO

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.

Acme Corporation (profile: acme):

Terminal window
akili product create --name daily_revenue --namespace sales --profile acme
akili product deploy daily_revenue --version 1.0.0 --profile acme

Globex Inc (profile: globex):

Terminal window
akili product create --name weekly_transaction_summary --namespace finance --profile globex
akili product deploy weekly_transaction_summary --version 1.0.0 --profile globex

Each tenant monitors their own products independently:

Terminal window
# Acme checks their SLA
akili governance sla daily_revenue --profile acme
# Globex checks their quality
akili governance quality weekly_transaction_summary --profile globex
# Neither can see the other's data
akili product list --profile acme # Only sees Acme's products
akili product list --profile globex # Only sees Globex's products