Skip to content

Multi-Tenancy (CAP MTX)

cds-data-federation and cds-data-pipeline support SaaS multi-tenancy when CAP multitenancy is active (@sap/cds-mtxs). Tenant isolation is enforced for the entity cache (one SQLite file per tenant), scheduled replication (fan-out per subscribed tenant), and pipeline run history.

Entity cache — per-tenant SQLite files

With cache.strategy: 'entity', declaring a dedicated SQLite datastore switches the entity cache from the primary db to one SQLite file per tenant. Declaring the service is the trigger; entityCache options only tune naming/location:

json
{
  "cds": {
    "requires": {
      "data-federation-cache": { "kind": "sqlite" },
      "data-federation": {
        "entityCache": {
          "urlTemplate": "data-federation-cache-{tenant}.sqlite",
          "defaultTenant": "default",
          "baseDir": "."
        }
      }
    }
  }
}

For the full storage-selection rules (default db vs dedicated files, custom service names, and why cache.service doesn't apply here), see Caching → Where the entity cache is stored.

OptionDefaultPurpose
entityCache.urlTemplatedata-federation-cache-{tenant}.sqliteFile name pattern per tenant
entityCache.defaultTenantdefaultFallback when no request tenant (single-tenant dev)
entityCache.baseDirproject rootDirectory for SQLite files

Each tenant gets an isolated file. There is no tenantId column in cache tables (breaking change vs. earlier previews — flush old cache files on upgrade).

@federation.replicate → main db

Under MTX, CAP routes the primary db service to per-tenant containers automatically. The plugin's job is to run pipelines inside tenant context so reads and writes hit the correct tenant database.

Scheduled cron / Job Scheduler runs fan out to all subscribed tenants when multitenancy is active:

json
{
  "data-pipeline": {
    "impl": "cds-data-pipeline",
    "multitenancy": {
      "active": true,
      "fanOutScheduledRuns": true
    }
  }
}

DataPipelineService.executeForTenant(tenant, name, opts) and execute(name, { tenant, ... }) pass the tenant id through to entity-cache targets without mutating the HTTP request context.

Response cache tenant tags

When cds.requires.multitenancy === true or data-federation.multitenancy.active === true, delegate response caches automatically include a tenant--entity- tag (opt out with cache.tenantScoped: false on the annotation).

MTX subscribe / unsubscribe hooks (optional)

Wire in your mtx sidecar (mtx/sidecar/server.js):

javascript
const { registerMtxDeploymentHooks } = require('cds-data-federation/srv/multitenancy/mtx-hooks')
registerMtxDeploymentHooks()
Config flagHookAction
multitenancy.syncOnSubscribe: trueDeploymentService.subscribe (after)Run all @federation.replicate pipelines for the new tenant
multitenancy.flushOnUnsubscribe: trueDeploymentService.unsubscribe (after)Delete the tenant's entity-cache SQLite file

Per-tenant replicate overrides (4.15.2)

Override mode or trigger per tenant under cds.env:

json
{
  "data-federation": {
    "multitenancy": {
      "tenants": {
        "t1": { "replicate": { "Movies": { "mode": "full" } } },
        "t2": { "replicate": { "Movies": { "mode": "delta" } } }
      }
    }
  }
}

Keys under replicate match the pipeline / entity short name. Schedule overrides use the same structure with a schedule field (consumed by TenantRunCoordinator.resolveTenantExecuteOpts).

External Job Scheduler (JSS)

For BTP Job Scheduler, emit one job per tenant or call POST /pipeline/execute with { "name": "...", "tenant": "t1" } for each subscribed tenant. See Pipeline → External scheduling.

See also

Released under the MIT License.