# Figma: Sharding Horizontal de Postgres Sem Parar de Crescer

Em 2022, o Figma enfrentou limites físicos do seu banco de dados Postgres monolítico e executou uma migração de sharding horizontal com particionamento por chave, roteamento dinâmico e migração incremental — tudo isso sem downtime e sem travar o crescimento do produto. Este teardown reconstrói a arquitetura, analisa as decisões técnicas e aponta o que eu faria diferente.

- URL: https://fernando.moretes.com/studies/figma-postgres-sharding

- Markdown: https://fernando.moretes.com/studies/figma-postgres-sharding/study.md?lang=pt

- Type: Teardown

- Company: Figma

- Domain: Dados

- Date: 2024-03-14

- Tags: postgres, sharding, data-platform, database, scalability, partitioning, migration, figma

- Reading time: 7 min

---

Sharding de banco de dados é uma das operações mais arriscadas que uma engenharia pode executar em produção. O Figma fez isso com Postgres, em escala de produto SaaS global, sem downtime e sem interromper o crescimento — e depois contou como. Este teardown desmonta a arquitetura, avalia as decisões e diz o que eu mudaria.

## Ficha Técnica

- **Empresa:** Figma
- **Domínio:** Plataforma de design colaborativo SaaS
- **Período da migração:** 2021–2022 (publicado em 2022)
- **Banco de dados original:** PostgreSQL monolítico (instância única de escrita)
- **Banco de dados alvo:** PostgreSQL com sharding horizontal por chave de tenant/objeto
- **Stack principal:** PostgreSQL, PgBouncer, Ruby on Rails, infraestrutura AWS
- **Estratégia de roteamento:** Camada de roteamento customizada na aplicação (shard map)
- **Downtime:** Zero downtime para usuários finais
- **Impacto do problema:** CPU, WAL lag e capacidade de conexões próximos do limite físico da instância primária

## O Problema: Um Postgres Monolítico Engolindo Uma Empresa em Crescimento

O Figma cresceu rápido demais para a sua própria infraestrutura de dados. Durante anos, a arquitetura de banco de dados seguiu o padrão clássico de startups bem-sucedidas: um Postgres primário com réplicas de leitura, escalando verticalmente sempre que os sinais de alerta apareciam. Essa abordagem funcionou — até o momento em que deixou de funcionar.

O problema não foi um único evento catastrófico. Foi uma convergência de pressões: a instância primária acumulando CPU elevada de forma crônica, o WAL (Write-Ahead Log) apresentando lag crescente nas réplicas, e o pool de conexões operando perto do limite físico do PgBouncer. Em sistemas transacionais de alta frequência como o Figma — onde múltiplos usuários editam o mesmo documento em tempo real — qualquer degradação no banco de dados primário se propaga diretamente para a experiência do usuário.

A escala vertical havia chegado ao seu teto prático. Mover para uma instância maior resolveria o problema por meses, não por anos. O time precisava de uma solução estrutural, e a única saída era o sharding horizontal — distribuir os dados entre múltiplos nós Postgres independentes, cada um responsável por um subconjunto das entidades do sistema.

O que torna esse problema especialmente difícil é que o Figma não tinha o luxo de pausar o produto para fazer a migração. O crescimento do negócio continuava, novos usuários chegavam, novos arquivos eram criados. A migração precisava acontecer debaixo de um sistema em plena operação, sem que os usuários percebessem.

## A Arquitetura Reconstruída: Como o Sistema Funciona

A solução do Figma pode ser decomposta em três camadas interdependentes: **particionamento lógico**, **roteamento dinâmico** e **migração incremental com dupla escrita**.

**Particionamento lógico por chave de entidade**

O esquema de sharding escolhido foi baseado em chave — especificamente, no identificador da entidade raiz (tipicamente o `file_id` ou equivalente de tenant). Isso significa que todos os dados relacionados a um determinado arquivo ou usuário são colocalizados no mesmo shard físico. A colocação é crítica: queries que atravessam shards (cross-shard joins) são extremamente custosas e, se mal planejadas, eliminam qualquer ganho de escala. O Figma optou por uma abordagem de colocação agressiva para evitar esse antipadrão.

O número de shards lógicos foi definido como um múltiplo intencional dos shards físicos. Isso é uma decisão de engenharia sofisticada: ao separar shards lógicos de shards físicos, o sistema pode rebalancear dados movendo shards lógicos entre nós físicos sem precisar rehashear todas as chaves. É o mesmo princípio do consistent hashing, mas implementado de forma mais explícita com um mapa de shards controlado pela aplicação.

**Roteamento dinâmico na camada de aplicação**

Em vez de adotar um proxy de banco de dados externo (como Vitess ou Citus), o Figma implementou o roteamento dentro da própria aplicação Rails. Existe um componente central — o shard map — que mantém o mapeamento de `shard_id lógico → instância Postgres física`. Antes de qualquer query, a aplicação consulta esse mapa para determinar para qual conexão de banco de dados rotear a operação.

Essa escolha tem implicações profundas. Por um lado, elimina um hop de rede e uma camada de infraestrutura adicional para operar. Por outro, acopla a lógica de roteamento ao código da aplicação, o que significa que toda mudança no mapa de shards precisa ser coordenada com o deploy da aplicação — ou gerenciada via feature flags e configuração dinâmica.

**Migração incremental com dupla escrita e verificação**

A parte mais delicada de toda a operação foi a migração dos dados existentes sem downtime. O processo seguiu um padrão clássico de migração online: primeiro, ativar a dupla escrita (dual-write) para as entidades sendo migradas, onde cada escrita vai tanto para o shard de origem quanto para o shard de destino; segundo, executar o backfill dos dados históricos em background, com controle de taxa para não sobrecarregar o primário; terceiro, verificar a consistência entre origem e destino antes de cortar o tráfego de leitura; quarto, promover o shard de destino como autoritativo e remover a escrita duplicada.

Cada fase dessa migração foi executada de forma incremental, tabela por tabela, entidade por entidade. Não foi uma big-bang migration — foi uma série de migrações cirúrgicas, cada uma com rollback definido.

## Arquitetura de Sharding Horizontal do Figma

Fluxo de leitura/escrita com roteamento por shard map, dupla escrita durante migração e réplicas por shard físico.

### 🌐 Client Layer

- Figma Client (Browser / Desktop) (user)

### ⚙️ Application Layer

- Rails App (API Servers) (compute)
- Shard Map (logical→physical) (compute)
- PgBouncer (connection pool) (network)

### 🗄️ Shard 0 (Physical)

- Postgres Primary Shard 0 (data)
- Postgres Replica Shard 0 (data)

### 🗄️ Shard 1 (Physical)

- Postgres Primary Shard 1 (data)
- Postgres Replica Shard 1 (data)

### 🗄️ Shard N (Physical)

- Postgres Primary Shard N (data)
- Postgres Replica Shard N (data)

### 🔄 Migration Layer

- Backfill Worker (rate-limited) (compute)
- Consistency Verifier (diff checker) (compute)

### Fluxos

- client -> rails: requisição
- rails -> shardmap: resolve shard
- shardmap -> pgbouncer: roteia conexão
- pgbouncer -> pg0_primary: escrita shard 0
- pgbouncer -> pg1_primary: escrita shard 1
- pgbouncer -> pgN_primary: escrita shard N
- pg0_primary -> pg0_replica: WAL replication
- pg1_primary -> pg1_replica: WAL replication
- pgN_primary -> pgN_replica: WAL replication
- rails -> pg0_primary: dual-write (migração)
- rails -> pg1_primary: dual-write (migração)
- backfill -> pg0_primary: backfill histórico
- backfill -> pg1_primary: backfill histórico
- verifier -> pg0_primary: verifica consistência
- verifier -> pg1_primary: verifica consistência

## A Complexidade Oculta: O Que Não Aparece no Diagrama

Qualquer diagrama de sharding parece razoável no papel. A brutalidade está nos detalhes operacionais que o diagrama não captura.

**O problema das transações distribuídas**

O Postgres não tem suporte nativo a transações distribuídas entre instâncias independentes. Isso significa que qualquer operação que precise de atomicidade entre dois shards diferentes precisa ser redesenhada. O Figma resolveu isso de duas formas: primeiro, através da colocação agressiva de entidades relacionadas no mesmo shard (evitando o problema na origem); segundo, aceitando consistência eventual em casos onde a atomicidade cross-shard era inviável e redesenhando o modelo de dados para eliminar dependências entre shards sempre que possível.

Essa é uma decisão de design que tem impacto direto no produto. Alguns padrões de query que eram triviais no monolito — um JOIN entre tabelas de entidades diferentes — se tornam operações proibidas ou muito custosas no mundo shardado. O time de engenharia precisou auditar todas as queries existentes e classificá-las por padrão de acesso antes de definir a estratégia de particionamento.

**O custo humano da dupla escrita**

A fase de dual-write é conceitualmente simples, mas operacionalmente cara. Durante o período de migração, cada escrita relevante precisa ir para dois destinos. Isso aumenta a latência de escrita, dobra a pressão sobre o pool de conexões, e introduz um novo vetor de falha: o que acontece se a escrita no shard de destino falhar? O Figma precisou definir políticas explícitas de tratamento de falha para cada fase da migração — e essas políticas precisavam ser conservadoras o suficiente para não corromper dados, mas agressivas o suficiente para não travar o produto.

**Gerenciamento do shard map como infraestrutura crítica**

O shard map não é um arquivo de configuração estático. É um componente de infraestrutura que precisa ser lido em alta frequência (toda query passa por ele), ter baixíssima latência, e ser atualizado de forma atômica durante rebalanceamentos. O Figma precisou tratar o shard map como um serviço de primeira classe — com cache na aplicação, invalidação controlada e monitoramento dedicado. Uma falha no shard map é uma falha global: se a aplicação não consegue resolver para qual shard rotear uma query, ela não consegue servir nenhuma requisição.

**Schema migrations em um mundo shardado**

No monolito, uma migration de schema é um evento único e controlado. Com N shards físicos, cada migration precisa ser executada N vezes, de forma coordenada, sem causar inconsistência entre shards durante o período de transição. O Figma adotou a prática de migrations compatíveis com versões anteriores (backward-compatible schema changes) — adicionando colunas como nullable antes de torná-las obrigatórias, mantendo colunas antigas durante períodos de transição — para garantir que diferentes shards pudessem estar em estados de schema ligeiramente diferentes durante o rollout.

## Matriz de Decisões: Opções de Sharding Consideradas

### Roteamento na aplicação (escolha do Figma)

**Pros**
- Sem hop adicional de rede
- Controle total sobre lógica de roteamento e failover
- Sem dependência de produto externo para o caminho crítico
- Integração nativa com feature flags e rollout incremental

**Cons**
- Lógica de sharding acoplada ao código da aplicação
- Cada linguagem/serviço precisa implementar o roteamento
- Shard map vira infraestrutura crítica gerenciada internamente

**Verdict:** Correto para o contexto: stack Rails homogênea, equipe com controle total do código

### Vitess (proxy MySQL-compatible)

**Pros**
- Sharding transparente para a aplicação
- Gerenciamento de conexões centralizado
- Amplamente testado em produção (YouTube, PlanetScale)

**Cons**
- Incompatível com Postgres — requereria migração de banco de dados também
- Adiciona camada de infraestrutura complexa para operar
- Latência adicional de proxy no caminho crítico

**Verdict:** Descartado: incompatível com Postgres e introduz risco duplo

### Citus (extensão Postgres para sharding)

**Pros**
- Sharding nativo dentro do ecossistema Postgres
- SQL distribuído com suporte a joins cross-shard
- Compatível com ferramentas existentes do ecossistema

**Cons**
- Adiciona complexidade operacional de extensão crítica
- Migração de schema e dados para modelo Citus seria big-bang
- Vendor lock-in para uma extensão específica

**Verdict:** Viável, mas o custo de migração e o lock-in foram desfavoráveis no contexto

### Escala vertical contínua

**Pros**
- Zero mudança no código da aplicação
- Operacionalmente simples

**Cons**
- Teto físico de hardware já próximo
- Custo exponencial por ganho linear de capacidade
- Não resolve o problema de conexões (PgBouncer tem limite)

**Verdict:** Solução paliativa — compra tempo mas não resolve o problema estrutural

## Leitura pelo AWS Well-Architected Framework

- **security**: **Adequado para o contexto, mas não destacado.** O post não detalha a postura de segurança, mas a arquitetura implica múltiplos endpoints de banco de dados, cada um precisando de controle de acesso independente. Em ambientes regulados (o Figma não é financeiro, mas serve empresas enterprise), a proliferação de endpoints de banco de dados aumenta a superfície de ataque e a complexidade de auditoria. Credenciais rotacionadas por shard, network policies por instância, e auditoria de acesso por shard são requisitos que escalam linearmente com o número de shards.
- **reliability**: **Forte.** A migração incremental com dual-write e verificação de consistência é um padrão de alta confiabilidade. Cada fase tem rollback definido. A separação entre shards lógicos e físicos permite rebalanceamento sem rehashing completo. O risco principal é o shard map como single point of failure lógico — se não for tratado com redundância e cache adequados, uma falha nele é uma falha global. O Figma demonstra consciência disso ao tratar o shard map como infraestrutura crítica.
- **performance**: **Forte.** O objetivo central do projeto era performance, e a arquitetura endereça isso diretamente: distribuição de carga de escrita entre múltiplos primários, redução de contenção de locks, e pool de conexões por shard em vez de um pool global. A colocação de entidades relacionadas no mesmo shard minimiza o custo de queries. O risco de degradação de performance está nas queries cross-shard não antecipadas — que, sem auditoria rigorosa, podem aparecer em produção.
- **sustainability**: **Neutro.** Mais instâncias significa mais consumo de energia. Não há informação pública sobre estratégias de eficiência energética específicas para essa arquitetura. A consolidação de shards lógicos em menos shards físicos quando o crescimento estabilizar seria a alavanca de sustentabilidade mais direta.

> **O Que Eu Faria Diferente:** A execução do Figma é tecnicamente sólida e o post é um dos mais honestos sobre sharding que já li. Mas há três pontos onde eu tomaria decisões diferentes — ou pelo menos questionaria mais cedo.

**1. Shard map como serviço explícito, não como biblioteca embutida.**
Implementar o roteamento dentro da aplicação Rails é pragmático para uma stack homogênea, mas cria um problema de longo prazo: quando o Figma adicionar serviços em outras linguagens (Go, Python, o que for), cada um precisará reimplementar o cliente do shard map. Eu teria extraído o shard map como um serviço leve (um endpoint HTTP/gRPC com cache agressivo no cliente), garantindo que a lógica de roteamento fosse centralizada e versionada de forma independente do código da aplicação. O custo de latência de um cache local com TTL curto é negligenciável; o custo de manter N implementações do mesmo roteamento em N linguagens é alto.

**2. Auditoria de queries cross-shard como gate obrigatório antes do sharding.**
O maior risco em uma migração de sharding é descobrir, em produção, que uma query crítica faz join entre entidades que foram parar em shards diferentes. Eu teria investido mais tempo — antes de qualquer linha de código de migração — em instrumentar o banco de dados monolítico para identificar todos os padrões de join, classificar entidades por afinidade de acesso, e só então definir a estratégia de particionamento. Isso é trabalho chato e lento, mas é o que separa uma migração de sharding bem-sucedida de um incidente de produção seis meses depois.

**3. Consistência eventual explícita no contrato de API.**
Du

## Veredicto

O Figma executou uma das migrações de banco de dados mais complexas do ecossistema SaaS moderno e fez isso de forma incremental, reversível e sem downtime para o usuário final. A escolha de roteamento na aplicação em vez de um proxy externo é defensável e provavelmente correta para o contexto — stack homogênea, equipe com controle total do código, e necessidade de integração nativa com o ciclo de deploy.

O que mais me impressiona nesse caso não é a tecnologia — sharding por chave é um padrão conhecido. O que impressiona é a disciplina de execução: a separação entre shards lógicos e físicos para facilitar rebalanceamento futuro, o processo de verificação de consistência antes de cada cutover, e a abordagem incremental que permitiu rollback em qualquer ponto.

O post original do Figma é uma leitura obrigatória para qualquer engenheiro que trabalha com dados em escala. Ele não vende uma solução mágica — documenta o trabalho duro, os trade-offs reais, e os problemas que apareceram no caminho. Isso é raro e valioso.

Para engenheiros avaliando arquiteturas similares: o sharding horizontal de Postgres é viável sem ferramentas externas, mas o custo operacional é permanente e escala com o

## Referências

- [Figma — How Figma's databases team lived to tell the scale](https://www.figma.com/blog/how-figmas-databases-team-lived-to-tell-the-scale/)

## Fontes do caso

- [Figma — How Figma's databases team lived to tell the scale](https://www.figma.com/blog/how-figmas-databases-team-lived-to-tell-the-scale/)
