Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.helix-db.com/llms.txt

Use this file to discover all available pages before exploring further.

For the complete documentation index optimized for AI agents, see llms.txt.
Multi-tenant architectures generally fall into one of three isolation levels.

Isolation Levels

LevelDescriptionIsolationResource efficiency
Infrastructure-levelSeparate database instance per tenant. Fully independent compute, storage, and networking.Strongest. No shared resources.Lowest. Each tenant pays the full cost of an idle cluster.
Namespace-levelShared infrastructure, separate logical partitions (databases, schemas, or namespaces) per tenant.Strong. Logical separation with shared compute.Moderate. Shared compute, but metadata and indexes scale per tenant.
Row-levelShared infrastructure, shared data structures. Tenants distinguished by a property on every record, enforced at query time.Application-enforced. Relies on consistent query-time filtering.Highest. All tenants share indexes, caches, and storage.

Row-Level Isolation in Helix Cloud

Helix Cloud focuses on row-level isolation. This is the fundamental building block for multi-tenancy. It provides full flexibility to implement any tenancy model at the application layer without imposing structural constraints on the database. Assign a tenant identifier as a property on every node and edge. Index that property with an equality index. All queries filter on the tenant property, ensuring that each request only accesses data belonging to the requesting tenant. This is the same mechanism used for any property-based filtering: secondary indexes make the lookup fast, and snapshot isolation guarantees that concurrent tenants do not interfere with each other. Row-level isolation keeps the architecture simple. The main model is still shared infrastructure: one writer fleet, one reader fleet, shared caches, and shared secondary indexes. Vector and text search can additionally use tenant-partitioned physical indexes when search isolation is needed. The tenant property in an index definition is the property name Helix reads from each node or edge to decide which tenant partition to use. When that property name is tenant_id, Helix maintains separate vector and text search structures for each distinct tenant_id value. This keeps tenant-scoped search working sets smaller and allows cache residency to track active tenants more closely. Tenants are still a data concern, not an infrastructure concern. Adding a new tenant is a write, not a provisioning event.

In Practice

The pattern is the same regardless of node label: declare an equality index on the tenant property once, attach tenant_id to every node and edge that gets written, and filter every read by tenant_id. Declare the index once, as part of your schema setup write batch:
g().create_index_if_not_exists(
    IndexSpec::node_equality("Doc", "tenant_id"),
)
Attach tenant_id to every node when it is written:
g().add_n(
    "Doc",
    vec![
        ("tenant_id", PropertyInput::from("acme")),
        ("title", PropertyInput::from("Acme doc")),
    ],
)
Scope every read by the requesting tenant. Use the source-predicate form so the equality index on tenant_id is hit directly:
g().n_with_label_where(
    "Doc",
    SourcePredicate::eq("tenant_id", "acme"),
)
When the tenant predicate sits mid-traversal — for example after walking edges from a known starting node — use .where_(Predicate::eq("tenant_id", "acme")). The same scope rule applies at every hop: edges that fan out across the graph remain tenant-scoped as long as every step filters on the tenant property.

What This Provides

  • Data isolation. Queries scoped to a tenant ID never observe another tenant’s data. Isolation is enforced by the query layer, not by network or infrastructure boundaries.
  • Shared infrastructure. All tenants share the same writer, readers, caches, and object storage. No per-tenant provisioning, no per-tenant scaling configuration.
  • Uniform scaling. Reader auto-scaling responds to aggregate query load across all tenants. A spike from one tenant benefits from the same scaling that serves all others.
  • Index efficiency. Equality indexes on the tenant property resolve tenant-scoped queries without scanning unrelated data.
  • Full flexibility. The application layer decides how tenancy is modeled, scoped, and enforced. Helix Cloud provides the primitives; the application owns the policy.

Tenant-Partitioned Search Indexes

Tenant-partitioned search indexes are optional. They supplement row-level filtering with separate physical search structures per tenant value.

Vector Indexes

Vector indexes can optionally partition by a configured tenant property name. Helix reads that property from each record and maintains a separate vector index for each distinct property value. For example, if the tenant property name is tenant_id, records with different tenant_id values land in different vector indexes. This keeps tenant-scoped search working sets smaller and allows vector caches to warm, retain, and evict data at tenant granularity. Tenant-partitioned vector indexes are the preferred way to isolate vector search results when that behavior is required. The DSL surface for tenant-partitioned vector indexes:
// Declare the index. The third argument is the property name Helix
// reads from each record to choose the tenant partition.
g().create_vector_index_nodes("Doc", "embedding", Some("tenant_id"))

// Insert a node with both the tenant property and the embedding.
g().add_n(
    "Doc",
    vec![
        ("tenant_id", PropertyInput::from("acme")),
        ("title", PropertyInput::from("Acme doc")),
        ("embedding", PropertyInput::from(vec![1.0f32, 0.0, 0.0])),
    ],
)

// kNN search scoped to a tenant. The tenant value selects the partition;
// no additional filtering is needed.
g().vector_search_nodes(
    "Doc",
    "embedding",
    vec![1.0f32, 0.0, 0.0],
    5,
    Some(PropertyValue::from("acme")),
)

Text Indexes

Text indexes provide the same model for full-text search. The tenant property still means the property name used to read the partition value from each record. Current text index validation requires that property name to be tenant_id, so Helix maintains a separate text index for each distinct tenant_id value. This keeps tenant-scoped full-text search working sets smaller and allows local text-search caches to track active tenants more closely. Tenant-partitioned text indexes are the preferred way to isolate full-text search results when that behavior is required. The DSL surface for tenant-partitioned text indexes:
// Declare the text index. Text indexes currently require the partition
// property name to be `tenant_id`.
g().create_text_index_nodes("Doc", "body", Some("tenant_id"))

// Full-text search scoped to a tenant. The tenant value selects the
// partition; the search runs only against that tenant's text index.
g().text_search_nodes(
    "Doc",
    "body",
    "search query string",
    5,
    Some(PropertyValue::from("acme")),
)

Considerations

  • Noisy neighbors. Tenants share compute and cache resources. A tenant issuing a high volume of queries or large writes affects cache residency and query latency for other tenants. Monitoring per-tenant query volume and applying rate limiting on your infrastructure mitigates this.
  • Shared search semantics. Secondary indexes remain shared. Tenant-scoped vector and text searches require an explicit tenant value for the configured tenant property, and the system remains shared infrastructure even when search indexes are partitioned by tenant.

Stricter Isolation Requirements

For workloads that require namespace-level or infrastructure-level tenant isolation (regulatory compliance, data residency, or dedicated-resource SLAs), contact us at founders@helix-db.com to discuss options.