Pular para o conteúdo
fernando.moretes.com
BlogEstudosCursosE-booksOpen SourcePodcasts
Loading…
Fernando Azevedo

Arquiteto de TI Especialista

Arquitetura, AWS, IA em produção, sistemas financeiros e FinOps, escritos a partir do que foi operado, com os números.

  • LinkedIn
  • GitHub
  • E-mail

Conteúdo

  • Blog
  • Estudos de arquitetura
  • Cursos
  • E-books
  • Open Source
  • Podcasts
  • Assuntos
  • Trilhas

Ferramentas

  • Well-Architected self-check
  • Arquitetura deste site
  • Números públicos
  • Histórico de estudo

Sobre

  • Comunidade
  • IA Builder
  • Perfil
  • Currículo (CV)
  • Trabalhe comigo
  • Media kit
  • RSS do blog
  • RSS dos estudos
  • Artigos narrados (podcast)
  • llms.txt
  • Termos, privacidade e uso de IA

(c) 2026 Fernando Francisco Azevedo

Estudos/Teardown
RSSMarkdownLLMs
TeardownNotionDados

Notion: O Modelo de Blocos e o Sharding do Postgres

5 de out. de 2021 6 min Com apoio de IA000 visualizações00 reproduções
Ouvir estudo

cerca de 6 min · áudio gerado ao tocar

Em 2020, o Notion virou produto de massa. O que era um banco Postgres único e bem comportado começou a mostrar sinais sérios de estresse, conexões esgotadas, latências imprevisíveis, e uma tabela central de 'blocos' crescendo a um ritmo que tornava qualquer operação de manutenção um evento de risco. A resposta foi um sharding lógico por tenant, executado sem downtime sobre infraestrutura existente. Este teardown reconstrói a arquitetura, avalia as decisões e diz o que eu faria diferente.

Ficha Técnica

Empresa
Notion Labs, Inc.
Domínio
Produtividade / Colaboração / SaaS
Período crítico
2020–2021 (crescimento pandêmico)
Stack de banco de dados
PostgreSQL (RDS / instâncias gerenciadas), PgBouncer
Modelo de dados central
Tabela universal de blocos (block), tudo é um bloco
Estratégia de sharding
Sharding lógico por workspace_id (tenant), depois físico
Impacto declarado
Eliminação de gargalos de conexão, redução de latência p99, capacidade de escalar horizontalmente sem redesenho de aplicação
Migração
Zero downtime, dupla escrita + backfill + cutover gradual

O Problema: Tudo É Um Bloco, e Isso Tem Consequências

A decisão de modelagem mais fundamental do Notion é também a mais arriscada do ponto de vista de banco de dados: tudo é um bloco. Páginas, parágrafos, imagens, tabelas, bullet points, bancos de dados embutidos, todos são linhas na mesma tabela block. Cada bloco tem um id UUID, um type, um campo properties JSONB com conteúdo variável, e ponteiros para parent_id e space_id (o workspace). A hierarquia do documento inteiro é uma árvore de adjacência dentro dessa tabela única.

Essa escolha tem elegância real. O modelo de dados é uniforme, o código da aplicação lida com um único tipo de entidade, e funcionalidades como mover um bloco entre páginas ou tipos são triviais, você só atualiza o parent_id. Não há migrações de schema para novos tipos de conteúdo; você apenas adiciona um novo valor de type e popula properties de forma diferente.

Mas a elegância tem um custo direto em escala. Uma única tabela acumulando todos os conteúdos de todos os usuários cria uma superfície de contenção enorme. Índices crescem sem controle. O autovacuum do Postgres, o processo de fundo que recupera espaço de linhas mortas e atualiza estatísticas, começa a lutar contra o volume de writes. Operações DDL (como adicionar um índice) bloqueiam ou levam horas. E o mais crítico: o Postgres não tem sharding nativo. Toda a carga vai para uma instância primária, e escalar verticalmente tem um teto claro, tanto em custo quanto em latência de replicação.

A Arquitetura Antes do Sharding: Um Monólito Sob Pressão

Antes da migração, o Notion operava com uma arquitetura relativamente convencional para um SaaS em crescimento: uma aplicação Node.js/TypeScript conectada a um Postgres primário via PgBouncer para pooling de conexões, com read replicas para queries de leitura intensiva. O PgBouncer era essencial: Postgres tem um custo não trivial por conexão (memória, contexto de processo), e aplicações modernas com dezenas de instâncias de servidor facilmente saturam o banco com milhares de conexões simultâneas.

O problema de conexões foi o primeiro sinal visível de crise. Com crescimento acelerado em 2020, o número de instâncias de aplicação aumentou, e mesmo com PgBouncer, o banco primário começou a atingir limites. Mas o problema mais profundo era de throughput e latência de escrita. A tabela block recebia writes de altíssima frequência, cada keystroke de cada usuário pode gerar uma ou mais linhas novas ou atualizações. Com milhões de usuários ativos, isso se traduz em dezenas de milhares de writes por segundo, todos disputando locks na mesma tabela, nos mesmos índices.

O autovacuum é um ponto que merece atenção especial. No Postgres, quando você atualiza uma linha, a versão antiga não é deletada imediatamente, ela fica como 'dead tuple' até o vacuum passar. Em tabelas de alta escrita, o acúmulo de dead tuples degrada performance de leitura (os índices ficam inchados, os sequential scans ficam mais lentos) e pode, em casos extremos, causar transaction ID wraparound: um evento catastrófico que força o banco a parar de aceitar writes até que o vacuum complete. O Notion claramente estava se aproximando de condições onde o autovacuum não conseguia manter o ritmo.

Arquitetura Reconstruída: Antes e Depois do Sharding

Diagrama reconstruído com base no post oficial do Notion. Mostra o fluxo de dados do modelo de blocos, a camada de roteamento de shards, e a distribuição por tenant.

👤 Clients
  • Notion Client · (Web / Desktop / Mobile)
⚙️ Application Layer
  • API Server · (Node.js / TypeScript)
  • Shard Router · (workspace_id → shard)
🔌 Connection Layer
  • PgBouncer · Shard 0
  • PgBouncer · Shard 1
  • PgBouncer · Shard N
🗄️ Physical Shards (Postgres)
  • Postgres Primary · Shard 0 · (workspaces 0..k)
  • Postgres Replica · Shard 0
  • Postgres Primary · Shard 1 · (workspaces k+1..m)
  • Postgres Replica · Shard 1
  • Postgres Primary · Shard N
  • Postgres Replica · Shard N
🗺️ Shard Mapping
  • Shard Map · (workspace_id → shard_id) · Postgres / in-memory cache

Como Funciona: Sharding Lógico Primeiro, Físico Depois

A estratégia do Notion foi inteligente na sequência: primeiro introduzir sharding lógico sem mover dados, depois migrar fisicamente. Isso é o padrão correto para migrações de banco de dados em produção, você separa o problema de modelagem do problema de migração.

Fase 1: Sharding Lógico: A aplicação passa a tratar cada workspace_id como pertencente a um shard lógico. Um mapa de shards (uma tabela simples, cacheada em memória) mapeia workspace_id → shard_id. Toda query passa pelo shard router, que resolve o shard correto antes de abrir conexão. Nesse ponto, todos os shards lógicos ainda apontam para o mesmo banco físico, o comportamento muda, mas os dados não se movem. Isso permite testar o roteamento em produção sem risco de perda de dados.

Fase 2: Migração Física com Dupla Escrita: Para mover workspaces para novos shards físicos, o Notion usou o padrão clássico de dupla escrita (dual-write) com backfill. O processo: (a) começar a escrever em ambos os bancos (origem e destino) para novos eventos; (b) backfill dos dados históricos no destino; (c) verificar consistência; (d) redirecionar leituras para o destino; (e) parar de escrever na origem. Cada etapa é reversível. O risco de cada cutover é limitado ao conjunto de workspaces sendo migrados, não ao sistema inteiro.

A chave da escolha de workspace_id como shard key: Toda query do Notion inclui o contexto do workspace, um usuário sempre está operando dentro de um workspace específico. Isso significa que o shard key está naturalmente presente em todas as operações, e não há queries cross-shard no caminho crítico. Isso é fundamental: sharding só funciona bem quando você consegue garantir que a grande maioria das queries toca um único shard. O modelo de blocos do Notion, com sua hierarquia contida dentro de um workspace, torna isso natural.

O que não é resolvido pelo sharding: queries analíticas que precisam agregar dados de múltiplos workspaces (ex: métricas de produto, dashboards internos) agora requerem fan-out para todos os shards ou uma pipeline de dados separada. Esse é o trade-off aceito, você otimiza para o caminho crítico transacional e aceita complexidade no analítico.

Matriz de Decisões: Alternativas ao Sharding por Tenant

Sharding por workspace_id (escolha do Notion)

Prós
  • Shard key sempre presente nas queries, zero cross-shard no caminho crítico
  • Isolamento natural de tenant, workspaces grandes não impactam pequenos
  • Migração incremental possível (workspace a workspace)
  • Compatível com o modelo mental da aplicação
Contras
  • Workspaces muito grandes (hot tenants) ainda podem sobrecarregar um shard
  • Queries analíticas cross-workspace requerem fan-out ou pipeline separada
  • Rebalanceamento de shards é operacionalmente complexo

Decisão correta dado o modelo de dados e padrões de acesso

Escala vertical (upgrade de instância)

Prós
  • Zero mudança de código ou arquitetura
  • Simples operacionalmente no curto prazo
Contras
  • Teto físico de hardware, não escala indefinidamente
  • Custo cresce de forma não linear com o tamanho da instância
  • Não resolve contenção de locks ou autovacuum lag
  • Adiou o problema, não o resolve

Paliativo, adequado como medida temporária, não como estratégia

Migrar para banco distribuído (Vitess, CockroachDB, Spanner)

Prós
  • Sharding gerenciado pela plataforma, não pela aplicação
  • Rebalanceamento automático de dados
  • Escala horizontal nativa
Contras
  • Migração de banco em produção é de altíssimo risco
  • Semântica SQL pode diferir, requer validação extensa
  • Curva de aprendizado operacional significativa
  • Custo de licença ou operacional potencialmente alto

Válido para greenfield ou com muito mais tempo disponível; risco excessivo em crise

Read replicas + caching agressivo

Prós
  • Reduz carga de leitura no primário
  • Relativamente simples de implementar
Contras
  • Não resolve o problema de writes, o gargalo principal
  • Cache invalidation é complexo em modelo colaborativo em tempo real
  • Replication lag pode causar leituras inconsistentes

Complementar, não substituto, não endereça o problema fundamental

Leitura Well-Architected

Segurança

Adequado, com ressalvas. O isolamento por tenant no nível de shard é uma melhoria de segurança real, um vazamento de dados em um shard não expõe automaticamente todos os workspaces. Porém, o shard map em si é um componente crítico de segurança: se comprometido ou corrompido, queries podem ser roteadas para o shard errado, expondo dados de outro tenant. O shard map precisa de controles de acesso rígidos, auditoria e validação de integridade. Não há evidência pública de como o Notion trata isso.

Confiabilidade

Forte. A migração incremental por workspace minimizou o blast radius de cada operação. A dupla escrita com verificação de consistência antes do cutover é o padrão correto. Cada shard tem réplica, mantendo RTO/RPO razoáveis. O risco residual é o hot tenant, um workspace enterprise muito grande ainda pode degradar um shard inteiro. Mitigação recomendada: circuit breakers por workspace e monitoramento de shard imbalance.

Eficiência de performance

Forte para o caminho transacional. Distribuir writes por múltiplos primários resolve o gargalo central. Cada shard tem uma fração da carga de autovacuum, tornando a manutenção gerenciável. O PgBouncer por shard mantém o pooling eficiente. O ponto fraco é o overhead de latência do lookup no shard map, mitigado por cache em memória, mas que precisa de invalidação cuidadosa quando workspaces são migrados entre shards.

Otimização de custos

Trade-off consciente. Múltiplas instâncias Postgres (primário + réplica por shard) multiplicam o custo de infraestrutura. Porém, instâncias menores e mais especializadas são geralmente mais eficientes em custo/performance do que uma única instância monstruosa. O custo operacional de gerenciar N shards é real, mais superfície para monitoramento, backups, upgrades de versão. Esse custo operacional é frequentemente subestimado.

Sustentabilidade

Neutro. Instâncias menores e mais eficientes podem ser mais sustentáveis do que uma instância superdimensionada. Porém, a multiplicação de instâncias idle (shards com pouca carga) pode desperdiçar recursos. Auto-scaling de instâncias Postgres ainda é limitado, você não liga e desliga primários facilmente.

FA
O Que Eu Faria Diferente
Arquiteto de TI Especialista

A decisão de sharding por workspace_id é correta, não mudaria isso. O que eu questionaria e reforçaria são três pontos específicos: 1. Shard map como componente de primeira classe. O post do Notion trata o shard map quase como detalhe de implementação, mas ele é o componente mais crítico da arquitetura. Uma corrupção silenciosa no shard map pode rotear writes de um tenant para o banco de outro, um incidente de segurança e consistência de dados ao mesmo tempo. Eu trataria o shard map com o mesmo rigor que um serviço de autenticação: versionamento de esquema explícito, auditoria de todas as mutações, checksums de integridade, e testes de chaos que simulam corrupção do mapa. 2. Estratégia explícita para hot tenants. O sharding por tenant resolve o problema de escala geral, mas não resolve o problema do tenant gigante, uma empresa Fortune 500 com 50.000 usuários ativos em um único workspace. Esse workspace vai inevitavelmente dominar o shard em que está. A solução é ter uma estratégia explícita: identificar tenants acima de um threshold de escrita, e para esses, implementar sharding intra-tenant (por exemplo, por block_id range ou por sub-espaço). Isso é complexo, mas necessário para um produto que serve enterprise. 3. Pipeline analítica desde o início. O sharding transacional e a análise de dados são problemas fundamentalmente diferentes.

Veredicto

O Notion fez a coisa certa na ordem certa: identificou o shard key correto (workspace_id), introduziu sharding lógico antes de mover dados, e executou a migração física de forma incremental e reversível. Isso é engenharia de banco de dados madura, não é glamouroso, mas é o que separa sistemas que sobrevivem ao crescimento dos que colapsam sob ele. O modelo de blocos é uma aposta de modelagem de dados que funcionou para o produto mas criou pressão estrutural no banco. A elegância de 'tudo é um bloco' tem um preço que só aparece em escala, e o Notion pagou esse preço com complexidade operacional, não com redesenho do produto. Essa é uma troca razoável. O que fica como lição central: a escolha do shard key é irreversível na prática. Uma vez que você tem milhões de workspaces distribuídos por shards, mudar a estratégia de particionamento é uma migração de dados de escala total. O Notion acertou nessa escolha porque o modelo de dados do produto (tudo contido dentro de um workspace) e o padrão de acesso (usuários operam dentro de um workspace por vez) se alinharam perfeitamente com o shard key.

Referências

Notion: Sharding Postgres at Notion (Official Blog Post)PostgreSQL Documentation: AutovacuumPgBouncer: Connection Pooler for PostgreSQLAWS Blog: Sharding with Amazon RDS for PostgreSQLDesigning Data-Intensive Applications: Martin Kleppmann (Partitioning chapter)Debezium: CDC for PostgreSQL
#postgres#sharding#data-modeling#scalability#migration#multi-tenant#block-model#databases
Fontes do caso
Notion: Sharding Postgres at Notion
Compartilhar:LinkedInWhatsAppXBaixar em Markdown
Escrito com apoio de IA a partir do caso público e da minha leitura como arquiteto.
Próximo passo
Trilha Arquiteto de IA, gratuitaFundamentos, LLMs, RAG, tools, MCP e arquitetura na AWS, em aulas curtas e interativas.Começar a trilhaAgentes de IA em produção na AWSComo eu desenho, oriento e audito agentes que vão ao ar, com os números e as fontes.Ver o IA Builder

Continue lendo

TeardownFigma: Sharding Horizontal de Postgres Sem Parar de CrescerEm 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.Ler TeardownShopify: Arquitetura de Pods para Escalar na Black FridayA Shopify redesenhou sua infraestrutura monolítica em torno de pods isolados e sharding de banco de dados para sobreviver aos picos extremos da BFCM sem degradação global. Este teardown reconstrói a arquitetura, examina as decisões de trade-off e avalia o que eu faria diferente com base em 16 anos operando sistemas de missão crítica.Ler TeardownDiscord: Como Armazenar Trilhões de Mensagens: Teardown da Migração Cassandra → ScyllaDBO Discord migrou seu armazenamento de mensagens de Apache Cassandra para ScyllaDB, eliminando latências de cauda imprevisíveis e pausas de GC que afetavam milhões de usuários. Este teardown reconstrói a arquitetura, examina as decisões de engenharia e os trade-offs envolvidos, e apresenta minha leitura crítica do que foi feito bem, e o que eu faria diferente.Ler
AnteriorMeta 2021: Como um Comando de Manutenção Derrubou o Facebook por 6 HorasPróximoRoblox 2021: 73 Horas de Indisponibilidade, Consul e o Efeito de Carga
Gostou deste estudo? Receba o próximo.

Post-mortems, ADRs e deep dives de arquitetura no seu email, do jeito que um arquiteto lê.

Sem spam · cancele quando quiser

Pergunte ao Fernando sobre isto

Uma resposta focada sobre este estudo, do meu assistente de IA, baseada no meu trabalho.

Participe da conversa

Entre para comentar

Confirme seu e-mail para participar, você também recebe a newsletter. Sem senha.

Neste estudo
O Problema: Tudo É Um Bloco, e Isso Tem ConsequênciasA Arquitetura Antes do Sharding: Um Monólito Sob PressãoArquiteturaComo Funciona: Sharding Lógico Primeiro, Físico DepoisMatriz de Decisões: Alternativas ao Sharding por TenantWell-ArchitectedVeredictoReferências