Estratégias de isolamento de tenants em infraestrutura compartilhada

1. Fundamentos do modelo multitenant

Em arquiteturas multitenant, um tenant representa um cliente ou unidade organizacional que compartilha a mesma infraestrutura de software, hardware e dados com outros inquilinos. O desafio central é garantir que cada tenant opere como se estivesse em um ambiente dedicado, sem comprometer a eficiência de recursos.

Os trade-offs fundamentais são claros: isolamento máximo (menor densidade, maior custo) versus eficiência de recursos (maior densidade, menor isolamento). A classificação típica inclui:

  • Isolamento hard: recursos completamente dedicados (VMs, bancos separados)
  • Isolamento soft: recursos compartilhados com mecanismos lógicos de separação (tenant_id, namespaces)
  • Isolamento híbrido: combinação de ambos, como banco dedicado para tenants premium e compartilhado para os demais

A escolha correta depende de requisitos de segurança, compliance e orçamento.

2. Isolamento por camada de banco de dados

Bancos de dados separados por tenant

Cada tenant possui seu próprio banco de dados. Exemplo de configuração:

# Configuração de conexão por tenant (exemplo conceitual)
tenants:
  tenant_a:
    host: db-tenant-a.internal
    database: erp_tenant_a
    user: app_tenant_a
  tenant_b:
    host: db-tenant-b.internal
    database: erp_tenant_b
    user: app_tenant_b

Schema por tenant

Um banco compartilhado com schemas separados:

CREATE SCHEMA tenant_123;
CREATE SCHEMA tenant_456;

CREATE TABLE tenant_123.orders (
    id SERIAL PRIMARY KEY,
    product TEXT,
    amount DECIMAL
);

CREATE TABLE tenant_456.orders (
    id SERIAL PRIMARY KEY,
    product TEXT,
    amount DECIMAL
);

Tabelas compartilhadas com Row Level Security (RLS)

O PostgreSQL RLS filtra automaticamente por tenant_id:

-- Habilitar RLS na tabela
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- Criar política de isolamento
CREATE POLICY tenant_isolation ON orders
    USING (tenant_id = current_setting('app.current_tenant')::INT);

-- Configurar o tenant na sessão
SET app.current_tenant = '123';
SELECT * FROM orders;  -- Retorna apenas registros do tenant 123

3. Isolamento em nível de aplicação

Middleware de roteamento baseado em tenant

Em frameworks web, um middleware intercepta requisições e injeta o contexto do tenant:

# Exemplo de middleware (pseudocódigo)
class TenantMiddleware:
    def process_request(self, request):
        tenant_id = self.extract_from_header(request, 'X-Tenant-ID')
        if not tenant_id:
            return error_response("Tenant não identificado", 400)

        # Configurar contexto global da requisição
        request.tenant = TenantContext(tenant_id)

        # Configurar conexão de banco específica
        request.db_connection = self.get_db_for_tenant(tenant_id)

        # Configurar cache isolado
        request.cache_prefix = f"tenant:{tenant_id}:"

Contextos de execução isolados

Para evitar que um tenant consuma recursos de outro, use thread pools ou corrotinas dedicadas:

# Pool de threads por tenant
tenant_pools = {
    "premium": ThreadPoolExecutor(max_workers=10),
    "standard": ThreadPoolExecutor(max_workers=4),
    "basic": ThreadPoolExecutor(max_workers=1)
}

def process_request(tenant_id, task):
    pool = tenant_pools.get(tenant_id, tenant_pools["basic"])
    future = pool.submit(task)
    return future.result()

Cache e sessões segregados

Prefixos de chave no Redis garantem isolamento:

# Redis com prefixo por tenant
SET tenant:123:session:abc456 "dados_sessao"
SET tenant:456:session:def789 "dados_sessao"

# Cache de dados
SET tenant:123:cache:produtos "[...]"
SET tenant:456:cache:produtos "[...]"

4. Isolamento em infraestrutura de mensageria e filas

Tópicos/streams dedicados por tenant

No NATS JetStream, crie streams separados:

# Criação de streams por tenant
nats stream add orders_tenant_a --subjects "tenant.a.orders.*"
nats stream add orders_tenant_b --subjects "tenant.b.orders.*"

# Políticas de retenção distintas
nats stream update orders_tenant_a --max-age=30d
nats stream update orders_tenant_b --max-age=7d

Controle de throughput e backpressure

Configure consumidores com limites específicos:

# Consumidor com backpressure por tenant
nats consumer add orders_tenant_a worker \
    --max-deliver=3 \
    --max-pending=100 \
    --ack-wait=30s

5. Isolamento em armazenamento de objetos e arquivos

Buckets separados por tenant

No S3 ou compatíveis:

# Buckets dedicados
s3://tenant-a-uploads/
s3://tenant-b-uploads/

# Políticas IAM restritivas
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::tenant-a-uploads/*",
      "Condition": {
        "StringEquals": {"aws:PrincipalTag/tenant": "tenant-a"}
      }
    }
  ]
}

Prefixos com políticas de acesso

Alternativa com um bucket único:

# Prefixos organizados
s3://shared-bucket/tenant-a/files/
s3://shared-bucket/tenant-b/files/

# Política baseada em prefixo
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "arn:aws:s3:::shared-bucket/tenant-a/*"
}

Criptografia com chaves por tenant

Use AWS KMS com chaves gerenciadas pelo cliente:

# Criptografia no upload
aws s3 cp documento.pdf s3://tenant-a-uploads/ \
    --sse aws:kms \
    --sse-kms-key-id arn:aws:kms:us-east-1:123456789012:key/tenant-a-key

6. Monitoramento e observabilidade multitenant

Métricas segregadas

Use labels ou tags para identificar o tenant:

# Prometheus metrics com label tenant
http_requests_total{tenant="tenant_a", status="200"} 1500
http_requests_total{tenant="tenant_b", status="200"} 2300
http_request_duration_seconds{tenant="tenant_a"} 0.045

# Alertas específicos
ALERT HighErrorRateTenantA
  IF rate(http_requests_total{tenant="tenant_a", status="5xx"}[5m]) > 0.05
  LABELS { severity = "critical" }

Logs estruturados

Inclua o tenant_id em todos os logs:

{
  "timestamp": "2025-01-15T10:30:00Z",
  "level": "ERROR",
  "tenant_id": "tenant_a",
  "service": "checkout",
  "message": "Falha no pagamento",
  "request_id": "req-abc-123"
}

7. Governança e operações em ambientes compartilhados

Rate limiting e quotas

Implemente limites por tenant:

# Configuração de rate limit
rate_limits:
  tenant_a:
    requests_per_second: 1000
    concurrent_connections: 50
    storage_gb: 100
  tenant_b:
    requests_per_second: 100
    concurrent_connections: 10
    storage_gb: 20

Backup com isolamento

Estratégias de backup que respeitam os limites:

# Backup por tenant
pg_dump -h db-host -d erp_tenant_a > backup_tenant_a.sql
pg_dump -h db-host -d erp_tenant_b > backup_tenant_b.sql

# Restauração seletiva
pg_restore -d erp_tenant_a_new backup_tenant_a.sql

8. Segurança e conformidade no modelo multitenant

Prevenção de vazamento de dados

Validação de tenant_id em todas as consultas:

# Consulta segura (sempre filtrar por tenant)
SELECT * FROM orders
WHERE tenant_id = $1 AND id = $2;

# Evitar consultas sem filtro de tenant
SELECT * FROM orders;  # PERIGOSO - retorna dados de todos os tenants

Auditoria e rastreabilidade

Registre todas as operações por tenant:

CREATE TABLE audit_log (
    id SERIAL PRIMARY KEY,
    tenant_id INT NOT NULL,
    user_id INT,
    action TEXT,
    resource TEXT,
    timestamp TIMESTAMPTZ DEFAULT NOW()
);

-- Índice para consultas por tenant
CREATE INDEX idx_audit_tenant ON audit_log(tenant_id, timestamp);

Compliance com regulamentações

Para LGPD, GDPR e HIPAA, o isolamento é crítico:

  • LGPD: Direito de exclusão de dados requer capacidade de deletar todos os dados de um tenant sem afetar outros
  • GDPR: Portabilidade exige exportação isolada dos dados do tenant
  • HIPAA: Logs de acesso devem ser segregados e auditáveis por tenant

Implemente funções de gerenciamento de ciclo de vida:

-- Função para exclusão completa de tenant (LGPD/GDPR)
CREATE OR REPLACE FUNCTION delete_tenant_data(p_tenant_id INT)
RETURNS VOID AS $$
BEGIN
    DELETE FROM orders WHERE tenant_id = p_tenant_id;
    DELETE FROM users WHERE tenant_id = p_tenant_id;
    DELETE FROM audit_log WHERE tenant_id = p_tenant_id;
    -- Limpar cache e armazenamento externo
    PERFORM clean_tenant_storage(p_tenant_id);
END;
$$ LANGUAGE plpgsql;

Referências