Rejoice Software logo

Laravel · · 4 min de leitura

Multi-tenant Laravel: single-DB vs multi-DB in production

We've shipped both. Here are the failure modes, trade-offs, and migration paths we wish we'd known on day one.

Duas formas de separar os clientes

Em uma aplicação multi-tenant, todos os clientes (tenants) compartilham o mesmo código, mas cada um só pode ver os próprios dados. Há duas formas principais de conseguir isso no Laravel.

No modelo de banco único, cada tabela que pertence a um tenant tem uma coluna tenant_id, e cada consulta é filtrada pelo tenant atual. No modelo de vários bancos, cada tenant tem o seu próprio banco (ou schema), e a aplicação troca de conexão quando sabe qual tenant está ativo. Os dois funcionam bem em produção; eles falham de formas diferentes.

Banco único: pontos fortes e riscos

Um banco único é simples de operar. Há um único conjunto de migrações, um backup, um pool de conexões e relatórios fáceis com todos os clientes. Também é barato: mil tenants pequenos custam o mesmo que um.

O principal risco é o vazamento de dados. Uma única consulta que esqueça o filtro de tenant — uma consulta SQL crua, um relatório, um job que roda fora de uma requisição — pode retornar dados de outro cliente. Os outros riscos são os vizinhos barulhentos (um tenant grande deixando todos lentos) e as restaurações: recuperar os dados de um único tenant a partir de um backup compartilhado é complicado.

Vários bancos: pontos fortes e riscos

Bancos separados oferecem isolamento forte. É muito menos provável que um bug mostre a um tenant os dados de outro, e você pode fazer backup e restaurar um único cliente, colocar os dados dele em uma região específica e mover um tenant grande para um servidor próprio.

Os custos são operacionais. Cada migração roda uma vez por tenant, então os deploys demoram mais, e uma falha no meio do caminho deixa tenants em versões diferentes do schema. Os limites de conexão viram uma restrição real, e analytics entre tenants exigem um pipeline extra até um data warehouse.

Como acertar a tenancy em banco único no Laravel

  • Identifique o tenant uma única vez, logo no início — em um middleware, a partir do domínio, do subdomínio ou do usuário logado — e guarde-o em um só lugar.
  • Aplique um global scope e defina o tenant_id automaticamente na criação dos models, por meio de um trait compartilhado.
  • Comece os índices compostos com tenant_id e inclua o tenant_id nas restrições de unicidade (por exemplo, tenant_id + email).
  • Passe o ID do tenant para cada job na fila e restaure-o antes de o job rodar.
  • Prefixe as chaves de cache e os caminhos de armazenamento de arquivos com o tenant.
  • Escreva testes que criem dois tenants e garantam que um nunca consiga ler os registros do outro.
trait BelongsToTenant
{
    protected static function bootBelongsToTenant(): void
    {
        static::addGlobalScope('tenant', fn ($query) => $query->where('tenant_id', tenant()->id));

        static::creating(fn ($model) => $model->tenant_id ??= tenant()->id);
    }
}

Como acertar a tenancy com vários bancos

Mantenha um banco central para tenants, domínios, planos e cobrança, e um banco separado por tenant para os dados dele. Troque a conexão padrão no middleware e nos jobs da fila, e redefina-a depois, para que um worker de longa duração nunca fique com o tenant errado.

Rode as migrações dos tenants com um comando que percorra os tenants, registre o progresso e possa ser retomado após uma falha. Fique de olho no limite de conexões do servidor de banco de dados e use um pooler de conexões se rodar muitos workers. Pacotes como stancl/tenancy e spatie/laravel-multitenancy cuidam de boa parte disso e suportam os dois modelos.

O modelo híbrido

Muitos produtos maduros acabam no meio-termo: a maioria dos tenants compartilha um banco, enquanto clientes corporativos que precisam de isolamento, residência de dados ou performance dedicada ganham o seu próprio. Se todas as tabelas já têm tenant_id, mover um tenant para um banco dedicado é uma exportação e uma importação, não uma reescrita. Esse é mais um motivo para adicionar o tenant_id desde o primeiro dia, mesmo que você talvez passe para vários bancos depois.

Como decidir

  • Muitos clientes pequenos e uma equipe pequena: banco único.
  • Contratos que exigem isolamento, restauração por cliente ou locais específicos para os dados: vários bancos, ou híbrido.
  • Poucos clientes muito grandes com cargas pesadas: híbrido, com bancos dedicados para eles.
  • Na dúvida: banco único com tenant_id em todo lugar e testes de isolamento rigorosos — isso mantém todas as opções em aberto.

Quer que a gente analise o seu site?

Use o verificador de sites gratuito para ter um relatório na hora ou abra um chamado e um engenheiro responderá em até um dia útil.

#Laravel #SaaS #Multi-tenancy

Vamos conversar

Vamos construir algo que valha a pena.

Conversa estratégica gratuita de 30 minutos. Sem discurso de vendas, sem compromisso — só um plano.

Comece um projeto