Pular para o conteúdo

Arquitetura real · Sênior

Figma e Notion: sharding horizontal de Postgres

Arquitetura real em produção. Pontos centrais: migração de dados online.

17 min de leitura · Conteúdo revisado em 23 de outubro de 2026

Resposta curta

Arquitetura real em produção. Pontos centrais: migração de dados online. Notion e Figma chegaram ao limite de um único Postgres. Este caso compara como cada um decidiu, desenhou e executou o sharding horizontal, com foco em como mover os dados sem parar o produto.

01 · CONTEXTO

Quando um Postgres só deixa de bastar

Notion e Figma chegaram ao limite de um único Postgres. Este caso compara como cada um decidiu, desenhou e executou o sharding horizontal, com foco em como mover os dados sem parar o produto.

No Notion, o uso do produto ia superar o monolito de Postgres, que tinha atravessado cinco anos e quatro ordens de magnitude de crescimento. Quem estava de plantão acordava com picos de CPU no banco.

O ponto de inflexão foi o VACUUM do Postgres, que passou a travar de forma constante e deixou de recuperar o espaço ocupado por tuplas mortas. O risco maior era o wraparound do ID de transação, mecanismo de segurança em que o Postgres para de processar todas as escritas; o time o tratou como ameaça existencial ao produto.

Subir a instância na vertical não bastava: o desempenho das consultas e dos processos de manutenção costuma degradar bem antes de a tabela atingir o tamanho máximo que o hardware suporta.

No Figma, a base de dados cresceu quase cem vezes desde 2020. Em 2020 era um único Postgres na maior instância física da AWS; até o fim de 2022 já havia cache, réplicas de leitura e uma dúzia de bancos particionados na vertical.

A partição vertical também tinha teto, porque a menor unidade que ela separa é uma tabela. Algumas tabelas, com vários terabytes e bilhões de linhas, já eram grandes demais para um único banco, e o time via impacto de confiabilidade durante os vacuums. As tabelas de maior escrita logo passariam o máximo de IOPS suportado pelo RDS da Amazon.

02 · ESCOLHAS

Construir o roteamento ou adotar um cluster pronto

O Notion optou por sharding no nível da aplicação: definir o próprio esquema de partição e rotear as consultas pela lógica da aplicação. Considerou soluções prontas, como Citus para Postgres e Vitess para MySQL, mas a lógica de cluster delas é opaca e o time queria controle sobre a distribuição dos dados.

O Figma avaliou CockroachDB, TiDB, Spanner e Vitess. Trocar para qualquer um deles exigiria uma migração de dados complexa entre dois armazenamentos, e o time já tinha muita experiência em operar RDS Postgres internamente. Uma alternativa NoSQL também ficou de fora: o modelo relacional do Figma é muito complexo e as APIs NoSQL não oferecem essa versatilidade.

Em vez de reimplementar um banco relacional shardado genérico, o que não fazia sentido para uma equipe pequena, o Figma construiu um conjunto de recursos menor e sob medida. Um exemplo: optou por não suportar transações atômicas entre shards, porque as falhas dessas transações podiam ser contornadas.

Dividir uma tabela entre vários bancos físicos tem preço. Segundo o Figma, chaves estrangeiras e índices únicos globais deixam de ser garantidos pelo Postgres, e escritas em alguns bancos podem ter sucesso enquanto outras falham, o que exige lógica de produto resiliente a essas falhas parciais.

DecisãoNotionFigma
Quais tabelasTodas as tabelas alcançáveis a partir de block por chave estrangeiraGrupos de tabelas relacionadas, as colos, com a mesma chave de shard
Chave de shardO ID do workspaceUm punhado de chaves, como UserID, FileID e OrgID
DistribuiçãoO espaço de UUIDs dividido em baldes uniformesO hash da chave de shard
JoinsA maioria evitada, pois se consulta um workspace por vezSó entre tabelas da mesma colo e pela chave de shard

03 · CHAVES E ROTEAMENTO

Como cada consulta encontra o seu shard

O Notion shardou todas as tabelas alcançáveis a partir de block por chave estrangeira. Nem todas precisavam ser divididas, mas, se um registro ficasse no banco principal e o bloco relacionado em outro shard, a falta de transação entre bancos poderia gerar inconsistências: a exclusão do bloco poderia ter sucesso enquanto a atualização dos comentários falharia.

Como cada bloco pertence a exatamente um workspace, o ID do workspace virou a chave de partição. Os usuários consultam em geral um workspace por vez, o que evita a maioria dos joins entre shards.

O time fixou limites de 500 GB por tabela e 10 TB por banco físico e chegou a 480 shards lógicos distribuídos de modo uniforme em 32 bancos físicos. O número 480 foi escolhido por ter muitos divisores, o que permite adicionar ou remover hosts mantendo a distribuição uniforme; com qualquer potência de dois, seria preciso dobrar o número de hosts para escalar.

  1. Uma requisição chega à aplicação; em geral ela consulta dados de um único workspace.

    Diagrama da arquitetura: Notion: roteamento por workspace
    CLCliente
    CPAplicação com roteamento
    DBBanco físico A
    DBBanco físico B
    DBBanco físico C
  2. A aplicação usa o ID do workspace, a chave de partição, para achar o shard lógico. Todo o roteamento acontece na própria aplicação, com uma única fonte de verdade.

    Diagrama da arquitetura: Notion: roteamento por workspace
    CLCliente
    CPAplicação com roteamentoroteia
    DBBanco físico A
    DBBanco físico B
    DBBanco físico C
  3. A consulta vai direto ao banco físico que guarda aquele shard lógico, sem uma segunda camada de roteamento dentro do Postgres, como haveria com tabelas nativamente particionadas.

    Diagrama da arquitetura: Notion: roteamento por workspace
    CLCliente
    CPAplicação com roteamento
    DBBanco físico A
    DBBanco físico Bshard do workspace
    DBBanco físico C
  4. Como a maioria das consultas fica dentro de um único workspace, evita-se a maior parte dos joins entre shards.

    Diagrama da arquitetura: Notion: roteamento por workspace
    CLCliente
    CPAplicação com roteamento
    DBBanco físico A
    DBBanco físico B
    DBBanco físico C

No Figma não havia uma chave de shard única boa. Uma chave unificada exigiria chave composta, nova coluna em toda tabela, backfills caros e grande refatoração da lógica de produto; por isso o time escolheu um punhado de chaves, como UserID, FileID e OrgID, e agrupou tabelas relacionadas em colos. Dentro de uma colo há joins entre tabelas e transações completas, desde que restritos a uma única chave de shard.

Muitas dessas chaves usavam IDs autoincrementais ou prefixados por timestamp, o que geraria hotspots. Migrar para IDs mais aleatórios exigia uma migração de dados cara e demorada, então o Figma passou a rotear pelo hash da chave de shard. O custo é que varreduras por intervalo ficam menos eficientes, pois chaves sequenciais caem em shards diferentes; esse padrão não é comum no código do Figma.

Para rotear, o Figma construiu o DBProxy, um serviço em Go entre a aplicação e o PgBouncer. Consultas sem a chave de shard exigem scatter-gather, que espalha a consulta por todos os shards e junta as respostas, e cada uma gera a mesma carga que teria num banco sem shards. Por isso o time restringiu a linguagem de consulta: joins só entre tabelas da mesma colo e pela chave de shard. Para escolher esse subconjunto, rodou o planejamento lógico em sombra sobre tráfego de produção real.

04 · MIGRAÇÃO NO NOTION

Escrita dupla, backfill, verificação e troca

O roteiro do Notion para qualquer migração tem quatro etapas. Na escrita dupla, as escritas chegam ao banco antigo e ao novo. No backfill, com a escrita dupla ligada, migra-se o dado antigo. Na verificação, confere-se a integridade do banco novo. Na troca, passa-se de fato para o banco novo. É a base de uma migração online de dados.

Opção de escrita duplaComo funcionaAvaliação do Notion
Escrita direta nos dois bancosCada escrita é aplicada ao banco antigo e ao novoQualquer problema numa das escritas gera inconsistência; instável demais para datastores críticos
Replicação lógicaRecurso nativo do Postgres, com modelo de publicação e assinatura para difundir comandos a vários bancosPouca capacidade de modificar dados entre origem e destino; não acompanhou a escrita da tabela block no snapshot inicial
Log de auditoria e catch-upUma tabela registra as escritas das tabelas em migração e um processo aplica cada atualização nos bancos novos, com as modificações necessáriasFoi a opção escolhida
  1. Um log de auditoria registra todas as escritas das tabelas em migração; é dele que o catch-up vai ler.

    Diagrama da arquitetura: Notion: migração com log de auditoria
    CLCliente
    CPAplicação
    DBMonolito Postgresorigem dos dados
    CPScript de catch-up
    CPScript de backfill
    DBBancos shardados
    CPVerificação
  2. O processo de catch-up percorre o log e aplica cada atualização nos bancos novos, fazendo as modificações necessárias.

    Diagrama da arquitetura: Notion: migração com log de auditoria
    CLCliente
    CPAplicação
    DBMonolito Postgres
    CPScript de catch-upaplica o log
    CPScript de backfill
    DBBancos shardadosrecebe as escritas
    CPVerificação
  3. Com as escritas já propagando, o backfill migra os dados antigos. Ele compara versões antes de gravar e ignora registros com atualização mais recente.

    Diagrama da arquitetura: Notion: migração com log de auditoria
    CLCliente
    CPAplicação
    DBMonolito Postgres
    CPScript de catch-up
    CPScript de backfillcompara versões
    DBBancos shardadosrecebe o histórico
    CPVerificação
  4. Catch-up e backfill podem rodar em qualquer ordem: os bancos novos acabam convergindo para uma réplica do monolito.

    Diagrama da arquitetura: Notion: migração com log de auditoria
    CLCliente
    CPAplicação
    DBMonolito Postgres
    CPScript de catch-up
    CPScript de backfill
    DBBancos shardadosconverge
    CPVerificação
  5. Na verificação, um script amostra UUIDs aleatórios e confere as faixas adjacentes. Nas leituras sombra, a aplicação lê dos dois bancos, compara, descarta a cópia shardada e registra as divergências.

    Diagrama da arquitetura: Notion: migração com log de auditoria
    CLCliente
    CPAplicaçãoleitura sombra
    DBMonolito Postgres
    CPScript de catch-up
    CPScript de backfill
    DBBancos shardados
    CPVerificaçãoamostragem
  6. Na troca, o servidor foi parado para o catch-up terminar de propagar as escritas aos shards, e o produto ficou fora do ar por cinco minutos de manutenção programada.

    Diagrama da arquitetura: Notion: migração com log de auditoria
    CLCliente
    CPAplicaçãomanutenção programada
    DBMonolito Postgres
    CPScript de catch-uptermina de propagar
    CPScript de backfill
    DBBancos shardados
    CPVerificação

O Notion escolheu o log de auditoria porque a replicação lógica não acompanhou o volume de escrita da tabela block durante o snapshot inicial. O script final levou cerca de três dias para fazer o backfill de produção. O time também preparou e testou um log de auditoria reverso, para voltar dos shards ao monolito se fosse preciso; no fim, não precisou reverter.

A verificação teve custos e cuidados. As leituras sombra aumentaram a latência da API, mas deram confiança de que a troca seria suave. A migração e a verificação foram implementadas por pessoas diferentes: com a mesma pessoa nas duas etapas, haveria mais chance de repetir o mesmo erro e enfraquecer a verificação.

05 · MIGRAÇÃO NO FIGMA

Sharding lógico primeiro, divisão física depois

O Figma partiu de outras metas. Quis evitar backfills de tabelas grandes, manter a capacidade de reverter mesmo depois de concluída uma operação de sharding físico e fugir da escrita dupla, difícil de implementar sem downtime ou sem abrir mão da consistência.

A ideia central foi separar o sharding lógico, na camada de aplicação, do sharding físico, no Postgres. Reverter o sharding lógico era uma mudança simples de configuração; reverter uma operação física é possível, mas exige coordenação mais complexa para manter a consistência dos dados.

  1. As consultas da aplicação passam pelo DBProxy, um serviço em Go entre a aplicação e o PgBouncer.

    Diagrama da arquitetura: Figma: sharding lógico com DBProxy
    CLCliente
    CPServiços de aplicação
    CPDBProxynovo serviço
    CPPool de conexões
    DBPostgres com views
  2. No DBProxy, o parser transforma o SQL em árvore sintática e o planejador lógico extrai o tipo da consulta e os IDs dos shards lógicos.

    Diagrama da arquitetura: Figma: sharding lógico com DBProxy
    CLCliente
    CPServiços de aplicação
    CPDBProxyparser e planejador lógico
    CPPool de conexões
    DBPostgres com views
  3. O planejador físico mapeia os shards lógicos para bancos físicos e reescreve a consulta para o shard certo.

    Diagrama da arquitetura: Figma: sharding lógico com DBProxy
    CLCliente
    CPServiços de aplicação
    CPDBProxyplanejador físico
    CPPool de conexões
    DBPostgres com views
  4. Antes da divisão física, cada shard lógico é uma view do Postgres e toda leitura e escrita passa por elas. O sistema parece shardado, mesmo com os dados num único host.

    Diagrama da arquitetura: Figma: sharding lógico com DBProxy
    CLCliente
    CPServiços de aplicação
    CPDBProxy
    CPPool de conexões
    DBPostgres com viewsviews
  5. Se surgia um bug, bastava redirecionar o tráfego de volta para a tabela principal, em segundos.

    Diagrama da arquitetura: Figma: sharding lógico com DBProxy
    CLCliente
    CPServiços de aplicação
    CPDBProxy
    CPPool de conexões
    DBPostgres com viewstabela principal

As views tinham um risco: em alguns casos poderiam mudar fundamentalmente como o planejador do Postgres otimiza as consultas. Para validar, o Figma coletou um corpus de consultas de produção sanitizadas, rodou testes de carga com e sem views e confirmou sobrecarga mínima na maioria dos casos e menor que dez por cento no pior. Também montou um framework de leituras sombra, que enviava o tráfego de leitura pelas views para comparar desempenho e correção.

  1. Com o sharding lógico validado, vem a operação física: copiar os dados de um banco, distribuí-los em vários e redirecionar o tráfego de leitura e escrita.

    Diagrama da arquitetura: Figma: divisão física dos shards
    CPServiços de aplicação
    CPDBProxy
    DBPostgres original
    DBBanco destino A
    DBBanco destino B
  2. Em vez de replicação lógica filtrada, cada destino recebe o conjunto inteiro de dados e só pode ler e escrever o subconjunto que pertence aos seus shards.

    Diagrama da arquitetura: Figma: divisão física dos shards
    CPServiços de aplicação
    CPDBProxy
    DBPostgres original
    DBBanco destino Acópia completa
    DBBanco destino Bcópia completa
  3. A operação deixa de ir de um banco para um e passa a ir de um para N, e precisa tolerar o caso em que só parte dos bancos tem sucesso.

    Diagrama da arquitetura: Figma: divisão física dos shards
    CPServiços de aplicação
    CPDBProxy
    DBPostgres original
    DBBanco destino A
    DBBanco destino B
  4. O DBProxy passa a rotear para os bancos novos e atualiza o seu estado de topologia para não mandar consultas ao banco errado durante as divisões.

    Diagrama da arquitetura: Figma: divisão física dos shards
    CPServiços de aplicação
    CPDBProxyatualiza a topologia
    DBPostgres original
    DBBanco destino A
    DBBanco destino B
  5. Na primeira tabela, o failover teve só dez segundos de disponibilidade parcial nos primários e nenhum impacto de disponibilidade nas réplicas.

    Diagrama da arquitetura: Figma: divisão física dos shards
    CPServiços de aplicação
    CPDBProxyfailover
    DBPostgres original
    DBBanco destino A
    DBBanco destino B

A primeira tabela horizontalmente shardada foi para produção em setembro de 2023. De ponta a ponta, shardar essa primeira tabela levou cerca de nove meses. O Figma não viu regressões de latência ou disponibilidade após o sharding.

06 · LIÇÕES E TRADE-OFFS

O que cada caminho custou e o que ficou por fazer

AspectoNotionFigma
Como os dados chegam ao destinoLog de auditoria, catch-up e backfillCópia do conjunto inteiro de dados, com leitura e escrita restritas ao subconjunto de cada shard
ReversãoLog de auditoria reverso preparado e testadoSharding lógico revertido por configuração; a operação física exige mais coordenação
VerificaçãoAmostragem de UUIDs e leituras sombraLeituras sombra pelas views, comparando desempenho e correção
TrocaServidor parado até o catch-up terminarSegundos de disponibilidade parcial nos primários

Em retrospectiva, o Notion diria que deveria ter shardado mais cedo. Esperou o banco ficar muito pressionado e teve de ser econômico com migrações para não aumentar a carga, o que o impediu de usar replicação lógica na escrita dupla. A chave de partição ainda não estava preenchida no banco antigo, e preenchê-la teria agravado a carga do monolito; por isso cada linha foi preenchida na hora de gravar nos shards, com um script de catch-up personalizado.

O Notion também apontou que a vazão da escrita dupla foi o gargalo da troca. Com mais uma semana otimizando o script para que o catch-up levasse menos de 30 segundos na troca, talvez fosse possível trocar no balanceador, sem downtime. E teria combinado a chave primária e a chave de partição numa única coluna.

O Figma, por sua vez, lista o que ainda falta: transações atômicas entre shards para casos críticos e índices únicos distribuídos, já que hoje só há suporte a índices que incluem a chave de shard. Também pretende reavaliar a abordagem própria frente a uma solução de código aberto ou gerenciada.

Perguntas frequentes

O que é Figma e Notion: sharding horizontal de Postgres?

Arquitetura real em produção. Pontos centrais: migração de dados online.

O que o design de Figma e Notion: sharding horizontal de Postgres cobre?

Ele está organizado nestas seções: Quando um Postgres só deixa de bastar, Construir o roteamento ou adotar um cluster pronto, Como cada consulta encontra o seu shard, Escrita dupla, backfill, verificação e troca, Sharding lógico primeiro, divisão física depois e O que cada caminho custou e o que ficou por fazer.

Quanto tempo leva Figma e Notion: sharding horizontal de Postgres e qual é o nível?

Leva cerca de 17 minutos para ler e tem nível Sênior.

Agora projete você mesmo

Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.