Native Multi-Tenancy with Row-Level Security

HeliosDB Lite isolates the tenants of a shared database with row-level security: each tenant gets its own role, a policy ties the rows of a shared table to current_user, and the server applies the policy to every PostgreSQL wire session and to every REST request, which runs as the role named in its token, so a tenant filter missing from application code does not expose another tenant's rows.

Superusers bypass row-level security; give each tenant its own non-superuser role and start the server with SCRAM authentication so clients cannot claim another role. Read the documentation.

Executive Summary

This embedded multi-tenancy architecture enables bootstrapped SaaS startups to build secure, scalable B2B platforms with predictable costs, allows enterprises to consolidate departmental data silos while maintaining strict access controls, and empowers ISVs to deploy white-label solutions with complete customer data separation in resource-constrained edge environments.


Problem Being Solved

Core Problem Statement

Teams need zero-trust tenant isolation built into the database layer that works transparently for developers, scales from 10 to 10,000 customers without architectural changes, and operates in resource-constrained environments without external dependencies.


HeliosDB Lite Solution

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                    HeliosDB-Lite Multi-Tenant Layer              │
├─────────────────────────────────────────────────────────────────┤
│  Tenant Session Context (Current Tenant ID)                      │
│  ┌─────────────────┬─────────────────┬─────────────────┐        │
│  │ Tenant A Context│ Tenant B Context│ Tenant C Context│        │
│  │ (UUID: aaa...)  │ (UUID: bbb...)  │ (UUID: ccc...)  │        │
│  └─────────────────┴─────────────────┴─────────────────┘        │
├─────────────────────────────────────────────────────────────────┤
│  Query Rewriter (Automatic Tenant Filtering)                     │
│  • Inject: AND tenant_id = <session_tenant> to WHERE clauses     │
│  • Apply to: SELECT, UPDATE, DELETE, JOIN conditions             │
│  • Enforce: Deny queries without tenant context                  │
├─────────────────────────────────────────────────────────────────┤
│  Resource Quota Enforcer                                         │
│  ┌──────────────────┬──────────────────┬──────────────────┐     │
│  │ Storage Quota    │ Connection Limit │ QPS Rate Limiter │     │
│  │ (100GB per tenant│ (50 per tenant)  │ (1000 per tenant)│     │
│  └──────────────────┴──────────────────┴──────────────────┘     │
├─────────────────────────────────────────────────────────────────┤
│  Isolation Mode Manager                                          │
│  • Shared Schema (RLS): Single table, WHERE tenant_id filtering  │
│  • DB-per-Tenant: Separate RocksDB ColumnFamilies               │
│  • Schema-per-Tenant: Namespace prefixing (tenant_A.table)      │
├─────────────────────────────────────────────────────────────────┤
│  SQL Query Engine (Tenant-Aware Execution)                       │
├─────────────────────────────────────────────────────────────────┤
│  Storage Engine (RocksDB with Tenant Isolation)                  │
│  • RLS Mode: Keys include tenant_id prefix                       │
│  • DB-per-Tenant: Separate ColumnFamilies                        │
│  • Schema-per-Tenant: Namespace-based keys                       │
└─────────────────────────────────────────────────────────────────┘

Concrete Examples with Code, Config & Architecture

Example 1: SaaS B2B Platform (Shared Schema RLS) - Embedded Configuration

Architecture:

SaaS Application Server (Rust/Axum)
    ↓
HeliosDB-Lite Client Library (Embedded)
    ├── Tenant Session Context (current_tenant_id from JWT)
    ├── Query Rewriter (auto-inject WHERE tenant_id = ?)
    └── Resource Quota Enforcer (storage, connections, QPS)
    ↓
In-Process RocksDB Storage (Shared Schema Mode)
    ├── Table: projects (tenant_id, project_id, name, ...)
    ├── Table: tasks (tenant_id, task_id, project_id, ...)
    └── Index: idx_tenant_project (tenant_id, project_id)
    ↓
Local File System (/data/heliosdb/)

Configuration (heliosdb.toml):

# HeliosDB-Lite Multi-Tenant SaaS Configuration
[database]
path = "/data/heliosdb/saas-platform.db"
memory_limit_mb = 4096
enable_wal = true
page_size = 4096

[multi_tenancy]
enabled = true
isolation_mode = "shared_schema"  # Shared Schema with RLS
tenant_id_column = "tenant_id"    # Standard column name for filtering

# Default resource limits (can override per tenant)
[multi_tenancy.default_limits]
max_storage_bytes = 107374182400  # 100 GB per tenant
max_connections = 50              # 50 concurrent connections per tenant
max_qps = 1000                    # 1,000 queries/second per tenant

# Tenant provisioning
[multi_tenancy.provisioning]
auto_create_tenants = true        # Auto-provision on first access
require_explicit_context = true   # Reject queries without tenant context

# Security settings
[multi_tenancy.security]
enforce_row_level_security = true  # Enable RLS filtering
deny_cross_tenant_joins = true     # Prevent accidental cross-tenant queries
audit_tenant_access = true         # Log all tenant context switches

[monitoring]
metrics_enabled = true
tenant_metrics = true              # Track per-tenant query counts, latency
verbose_logging = false

Example 2: Enterprise Department Isolation (Database-per-Tenant) - Python Integration

Scenario: Fortune 500 enterprise with 200 departments (HR, Finance, Sales, Legal, etc.) needing absolute data isolation for compliance (HR payroll data must never be accessible to other departments), deployed on-premise with strict audit requirements, each department 50-500 users, 10-100GB data.

Architecture Pattern:

┌─────────────────────────────────────────────────────────────┐
│         Enterprise Application (Python/Flask)                │
├─────────────────────────────────────────────────────────────┤
│  Department Context (from SAML/LDAP authentication)          │
│  ├── HR Tenant Context                                       │
│  ├── Finance Tenant Context                                  │
│  ├── Sales Tenant Context                                    │
│  └── Legal Tenant Context                                    │
├─────────────────────────────────────────────────────────────┤
│  HeliosDB-Lite Python Bindings (FFI to Rust)                 │
├─────────────────────────────────────────────────────────────┤
│  Multi-Tenant Isolation Layer (Database-per-Tenant)          │
│  ├── HR Database (RocksDB ColumnFamily 1)                    │
│  ├── Finance Database (RocksDB ColumnFamily 2)               │
│  ├── Sales Database (RocksDB ColumnFamily 3)                 │
│  └── Legal Database (RocksDB ColumnFamily 4)                 │
├─────────────────────────────────────────────────────────────┤
│  In-Process RocksDB Storage Engine                           │
│  • Physical Isolation via ColumnFamilies                     │
│  • No shared data structures between departments             │
│  • Separate WAL logs per tenant                              │
└─────────────────────────────────────────────────────────────┘

Example 3: White-Label ISV Platform (Schema-per-Tenant) - Container Deployment

Scenario: Marketing automation platform sold to 500 agencies/resellers as white-labeled product, each reseller serves 50-200 end clients, requires complete branding isolation (Reseller A's clients cannot discover Reseller B exists), deployed in Docker containers on reseller-owned infrastructure (on-premise, private cloud).

Docker Deployment (Dockerfile):

FROM rust:latest as builder

WORKDIR /app

# Copy HeliosDB-Lite source
COPY . .

# Build white-label application with multi-tenancy
RUN cargo build --release --features multi-tenancy,encryption

# Runtime stage (minimal)
FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/whitelabel-saas /usr/local/bin/

# Create data directory for tenant databases
RUN mkdir -p /data/tenants

# Expose API port
EXPOSE 8080

# Health check endpoint
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

VOLUME ["/data"]

ENTRYPOINT ["whitelabel-saas"]
CMD ["--config", "/etc/heliosdb/config.toml", "--data-dir", "/data"]

Docker Compose (docker-compose.yml):

version: '3.8'

services:
  whitelabel-saas:
    build:
      context: .
      dockerfile: Dockerfile
    image: whitelabel-saas:latest
    container_name: whitelabel-prod

    ports:
      - "8080:8080"      # API server

    volumes:
      - ./data:/data                              # Persistent tenant data
      - ./config/whitelabel.toml:/etc/heliosdb/config.toml:ro

    environment:
      RUST_LOG: "info,whitelabel=debug"
      HELIOSDB_MULTI_TENANT: "true"
      HELIOSDB_ISOLATION_MODE: "schema_per_tenant"

    restart: unless-stopped

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3

    networks:
      - app-network

    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 2G

networks:
  app-network:
    driver: bridge

volumes:
  tenant_data:
    driver: local

Configuration for White-Label (whitelabel.toml):

[server]
host = "0.0.0.0"
port = 8080
workers = 4

[database]
path = "/data/whitelabel.db"
memory_limit_mb = 2048
enable_wal = true
page_size = 4096

[multi_tenancy]
enabled = true
isolation_mode = "schema_per_tenant"  # Balance of isolation + efficiency
tenant_id_column = "tenant_id"

# White-label specific limits per reseller
[multi_tenancy.default_limits]
max_storage_bytes = 53687091200   # 50 GB per reseller
max_connections = 75              # 75 concurrent connections
max_qps = 2000                    # 2,000 queries/second

[multi_tenancy.schema_isolation]
namespace_prefix = "tenant"       # Schema names: tenant_<uuid>
allow_cross_schema_queries = false  # Prevent reseller discovery
hide_other_schemas = true         # Don't list other tenants' schemas

[encryption]
enabled = true
key_source = "env"                # Read from HELIOSDB_ENCRYPTION_KEY
algorithm = "aes-256-gcm"

[audit]
enabled = true
log_tenant_provisioning = true
log_cross_tenant_attempts = true  # Alert on isolation violations

Example 4: Development Environments (Tenant-as-Branch) - Go Microservice

Scenario: Engineering team with 50 developers, each needs isolated dev environment (dev, staging, prod as tenants), shared schema for consistency, instant environment creation for feature branches, deployed as Go microservice.

Go Service Integration (using cgo bindings):

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"

    "github.com/google/uuid"
    "github.com/gorilla/mux"
    heliosdb "example.com/heliosdb-lite/go-bindings"
)

type Environment string

const (
    EnvDev     Environment = "dev"
    EnvStaging Environment = "staging"
    EnvProd    Environment = "prod"
)

// Map environments to tenant IDs
var envTenantMap = map[Environment]uuid.UUID{
    EnvDev:     uuid.MustParse("11111111-1111-1111-1111-111111111111"),
    EnvStaging: uuid.MustParse("22222222-2222-2222-2222-222222222222"),
    EnvProd:    uuid.MustParse("33333333-3333-3333-3333-333333333333"),
}

type AppContext struct {
    db *heliosdb.Connection
}

type Feature struct {
    ID          uuid.UUID `json:"id"`
    TenantID    uuid.UUID `json:"tenant_id"`
    Name        string    `json:"name"`
    Enabled     bool      `json:"enabled"`
    Environment string    `json:"environment"`
}

func main() {
    // Initialize HeliosDB with multi-tenancy
    config := heliosdb.Config{
        Path:          "./app-data.db",
        MemoryLimitMB: 1024,
        MultiTenancy: heliosdb.MultiTenancyConfig{
            Enabled:       true,
            IsolationMode: heliosdb.SharedSchema,
            DefaultLimits: heliosdb.ResourceLimits{
                MaxStorageBytes: 10 * 1024 * 1024 * 1024, // 10 GB per env
                MaxConnections:  50,
                MaxQPS:          1000,
            },
        },
    }

    db, err := heliosdb.Open(config)
    if err != nil {
        log.Fatalf("Failed to open database: %v", err)
    }
    defer db.Close()

    // Provision environments as tenants
    provisionEnvironments(db)

    // Initialize schema (shared across all environments)
    initSchema(db)

    // Setup HTTP server
    ctx := &AppContext{db: db}
    router := mux.NewRouter()

    router.HandleFunc("/features", ctx.createFeature).Methods("POST")
    router.HandleFunc("/features", ctx.listFeatures).Methods("GET")
    router.HandleFunc("/env/{env}/promote", ctx.promoteEnvironment).Methods("POST")

    log.Println("Microservice running on :8080")
    log.Fatal(http.ListenAndServe(":8080", router))
}

func provisionEnvironments(db *heliosdb.Connection) {
    for env, tenantID := range envTenantMap {
        err := db.ProvisionTenant(heliosdb.TenantConfig{
            ID:            tenantID,
            Name:          string(env),
            IsolationMode: heliosdb.SharedSchema,
        })
        if err != nil {
            log.Printf("Tenant %s already exists: %v", env, err)
        } else {
            log.Printf("Provisioned environment: %s", env)
        }
    }
}

func initSchema(db *heliosdb.Connection) {
    // Run in dev environment context for schema creation
    db.SetTenantContext(envTenantMap[EnvDev])

    _, err := db.Exec(`
        CREATE TABLE IF NOT EXISTS features (
            id UUID PRIMARY KEY,
            tenant_id UUID NOT NULL,
            name TEXT NOT NULL,
            enabled BOOLEAN DEFAULT false,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    `)
    if err != nil {
        log.Fatalf("Schema creation failed: %v", err)
    }

    // Index for tenant isolation
    _, err = db.Exec(`
        CREATE INDEX IF NOT EXISTS idx_features_tenant
        ON features(tenant_id, name)
    `)
    if err != nil {
        log.Fatalf("Index creation failed: %v", err)
    }
}

func (ctx *AppContext) createFeature(w http.ResponseWriter, r *http.Request) {
    // Extract environment from header
    env := Environment(r.Header.Get("X-Environment"))
    tenantID, ok := envTenantMap[env]
    if !ok {
        http.Error(w, "Invalid environment", http.StatusBadRequest)
        return
    }

    // Set tenant context
    ctx.db.SetTenantContext(tenantID)

    var req struct {
        Name    string `json:"name"`
        Enabled bool   `json:"enabled"`
    }
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    featureID := uuid.New()

    // Insert with automatic tenant_id injection
    _, err := ctx.db.Exec(
        `INSERT INTO features (id, tenant_id, name, enabled)
         VALUES ($1, CURRENT_TENANT_ID(), $2, $3)`,
        featureID, req.Name, req.Enabled,
    )
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    feature := Feature{
        ID:          featureID,
        TenantID:    tenantID,
        Name:        req.Name,
        Enabled:     req.Enabled,
        Environment: string(env),
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(feature)
}

func (ctx *AppContext) listFeatures(w http.ResponseWriter, r *http.Request) {
    env := Environment(r.Header.Get("X-Environment"))
    tenantID, ok := envTenantMap[env]
    if !ok {
        http.Error(w, "Invalid environment", http.StatusBadRequest)
        return
    }

    ctx.db.SetTenantContext(tenantID)

    // Query WITHOUT explicit WHERE tenant_id (auto-filtered)
    rows, err := ctx.db.Query(
        `SELECT id, tenant_id, name, enabled
         FROM features
         ORDER BY name`,
    )
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    var features []Feature
    for rows.Next() {
        var f Feature
        err := rows.Scan(&f.ID, &f.TenantID, &f.Name, &f.Enabled)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        f.Environment = string(env)
        features = append(features, f)
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(features)
}

func (ctx *AppContext) promoteEnvironment(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    sourceEnv := Environment(vars["env"])

    // Promote: dev -> staging -> prod
    var targetEnv Environment
    switch sourceEnv {
    case EnvDev:
        targetEnv = EnvStaging
    case EnvStaging:
        targetEnv = EnvProd
    default:
        http.Error(w, "Cannot promote production", http.StatusBadRequest)
        return
    }

    sourceTenant := envTenantMap[sourceEnv]
    targetTenant := envTenantMap[targetEnv]

    // Copy features from source to target environment
    ctx.db.SetTenantContext(sourceTenant)

    rows, err := ctx.db.Query(`SELECT id, name, enabled FROM features`)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    ctx.db.SetTenantContext(targetTenant)
    promoted := 0

    for rows.Next() {
        var id uuid.UUID
        var name string
        var enabled bool
        rows.Scan(&id, &name, &enabled)

        // Insert into target environment
        _, err := ctx.db.Exec(
            `INSERT INTO features (id, tenant_id, name, enabled)
             VALUES ($1, CURRENT_TENANT_ID(), $2, $3)
             ON CONFLICT (id) DO UPDATE SET enabled = $3`,
            id, name, enabled,
        )
        if err == nil {
            promoted++
        }
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "message":  fmt.Sprintf("Promoted %s to %s", sourceEnv, targetEnv),
        "promoted": promoted,
    })
}

Example 5: Geographic Compliance Isolation (Data Residency) - Edge Deployment

Scenario: Global SaaS serving EU, US, and APAC customers with GDPR/data residency requirements, each region deployed as separate tenant in edge locations (EU data must stay in EU), handling 100K users per region, requires audit trail of cross-region access attempts.

Edge Configuration (per region):

[database]
path = "/var/lib/heliosdb/region-eu.db"  # Separate DB per region
memory_limit_mb = 2048
enable_wal = true
page_size = 4096

[multi_tenancy]
enabled = true
isolation_mode = "database_per_tenant"  # Strongest isolation for compliance
tenant_id_column = "tenant_id"

# Region-specific tenant (EU, US, APAC)
[multi_tenancy.region]
region_id = "eu-west-1"
data_residency_enforcement = true  # Prevent cross-region queries
audit_cross_region_access = true   # Log any attempts

[multi_tenancy.default_limits]
max_storage_bytes = 1099511627776  # 1 TB per region
max_connections = 500
max_qps = 10000

[encryption]
enabled = true
key_source = { Environment = "HELIOSDB_ENCRYPTION_KEY" }  # per-region key from your secret manager; cloud KMS key sources are planned

[audit]
enabled = true
log_data_access = true
log_cross_region_attempts = true
retention_days = 2555  # 7 years (GDPR requirement)

[compliance]
gdpr_mode = true
data_residency_region = "EU"
allow_cross_border_transfer = false

Adoption Strategy

Phase 1: Proof of Concept (Weeks 1-4)

Target: Validate multi-tenancy in development environment with 10-50 test tenants

Tactics:

  • Deploy HeliosDB Lite with shared schema RLS mode in staging environment
  • Create 10 test tenants representing real customer segments
  • Implement tenant context extraction from JWT/session
  • Run isolation tests: verify tenant A cannot access tenant B's data
  • Measure provisioning speed, query performance with tenant filtering
  • Test resource quota enforcement (storage, connection limits)

Phase 2: Pilot Deployment (Weeks 5-12)

Target: Limited production deployment with 50-200 real customers

Tactics:

  • Run parallel systems (old single-tenant + new multi-tenant) for validation
  • Monitor per-tenant metrics: query latency, error rates, resource usage
  • Implement tenant-aware dashboards and alerting
  • Collect customer feedback on isolation, performance, reliability
  • Test failure scenarios: tenant quota exceeded, rapid tenant provisioning spikes

Phase 3: Full Rollout

Target: Organization-wide deployment with 1,000-10,000 tenants

Tactics:

  • Gradual migration of remaining customers to multi-tenant platform
  • Automated tenant provisioning integrated with signup/onboarding flow
  • Implement tenant lifecycle management: suspend, archive, delete
  • Deploy comprehensive monitoring: per-tenant dashboards, quota alerts, isolation audits
  • Establish runbooks for common scenarios: quota increase, tenant migration, isolation incident response
  • Optimize for scale: cache tenant metadata, batch quota enforcement, index tuning

Conclusion

Next Steps: Start with a 4-week proof-of-concept deploying shared schema RLS mode with 10 test tenants, validate isolation through automated leak tests, measure provisioning speed and query performance, then expand to pilot deployment with 50-200 real customers over 8 weeks before full production rollout.

Performance depends on workload and hardware. Published figures are measured on representative fixtures; see the benchmarks page and reproduce them on your own workload.