Multi-Tenancy in Geode

Multi-tenancy enables multiple customers (tenants) to share a single Geode deployment while maintaining strict data isolation. This architecture is fundamental for SaaS applications, reducing operational complexity and infrastructure costs.

Multi-Tenancy Strategies

Row-Level Security (RLS)

The preferred approach uses RLS policies to automatically filter data by tenant:

-- Add tenant_id to all tenant-scoped labels
CREATE CONSTRAINT tenant_required ON (n:Customer) ASSERT EXISTS(n.tenant_id);

-- Create RLS policy for tenant isolation
-- Customer.tenant_id must equal the caller's 'tenant' principal attribute
CALL geode.rls.add_attribute_policy('app', 'Customer', 'tenant_id', 'tenant');

-- Enable RLS
-- Geode has no ALTER LABEL / ALTER GRAPH / property DEFAULT DDL. Labels and
-- properties are schema-free: write the new property with SET, supplying the
-- default yourself.
MATCH (u:User) WHERE u.verified IS NULL SET u.verified = false;

Setting tenant context:

from geode_client import Client

async def query_as_tenant(tenant_id: str):
    client = Client(host="localhost", port=3141)
    async with client.connection() as conn:
        # Set tenant context for this session
        await conn.execute(
            "SET app.tenant_id = $tenant_id",
            {'tenant_id': tenant_id}
        )

        # All queries automatically filtered to this tenant
        result, _ = await conn.query("""
            MATCH (c:Customer)
            RETURN c.name, c.email
        """)
        # Only returns customers belonging to tenant_id
        return result

Schema-Based Isolation

For stronger isolation, use separate schemas per tenant:

-- Create tenant-specific schema
CREATE SCHEMA tenant_acme;
CREATE SCHEMA tenant_globex;

-- Queries target specific schema
USE tenant_acme;
MATCH (c:Customer) RETURN c;

Database-Level Isolation

Maximum isolation with separate databases:

# Create per-tenant databases
# There is no `geode admin` subcommand. Geode's CLI is: serve, shell, query,
# lsp, backup, restore, migrate, auth, login, logout, user, role, tenant,
# policy, grant, revoke, completion, mcp.
# There is no `geode admin` subcommand. Geode's CLI is: serve, shell, query,
# lsp, backup, restore, migrate, auth, login, logout, user, role, tenant,
# policy, grant, revoke, completion, mcp.

Choosing an Approach

ApproachIsolationEfficiencyComplexity
RLSLogicalHighLow
SchemaModerateMediumMedium
DatabasePhysicalLowerHigher

RLS is recommended for most SaaS applications, offering the best balance of isolation, efficiency, and operational simplicity.

Implementation Patterns

Tenant-aware connection middleware:

class TenantMiddleware:
    def __init__(self, client):
        self.client = client

    async def get_connection(self, tenant_id: str):
        conn = await self.client.connect()
        await conn.execute(
            "SET app.tenant_id = $tenant_id",
            {'tenant_id': tenant_id}
        )
        return conn

Cross-tenant queries (admin only):

-- Bypass RLS for administrative queries
SET ROLE admin_bypass_rls;
MATCH (c:Customer)
RETURN c.tenant_id, count(*) AS customer_count
GROUP BY c.tenant_id;

Best Practices

Always include tenant_id: Use constraints to ensure all tenant-scoped data includes the tenant identifier.

Audit cross-tenant access: Log any queries that bypass tenant isolation.

Test isolation thoroughly: Verify RLS policies prevent cross-tenant data access.

Consider tenant-specific indexes: For large tenants, partition indexes may improve performance.


Related Articles

No articles found with this tag yet.

Back to Home