Blog

Como eu enxergo a arquitetura do Instagram.

Segundo texto da série em que eu leio a arquitetura de produtos que todo mundo usa, a partir de material público de engenharia. Não é a planta: é a minha leitura do que organiza o resto.

E, de todos os seis, esse é o que eu acho mais instrutivo — justamente por não ter nada de espetacular.

A tese: tecnologia entediante como estratégia

Quando o Instagram cresceu pra dezenas de milhões de usuários, ele rodava com um time minúsculo, em Python com Django, em cima de PostgreSQL e memcached.

Nada exótico. Nenhum banco novo, nenhuma linguagem nova, nenhum sistema distribuído artesanal.

A leitura preguiçosa é que eles tiveram sorte. A leitura que eu faço é que foi decisão: cada tecnologia nova é um sistema a mais que alguém precisa entender às três da manhã, e um time pequeno não tem esse orçamento.

Eles gastaram a inovação onde o problema era realmente deles, e usaram o chato e testado no resto. Isso é uma escolha de engenharia, e é a mais subestimada que existe.

Onde eles inovaram: o identificador

O problema concreto: o Postgres não escala verticalmente pra sempre. Em algum momento você precisa dividir o dado em várias máquinas.

E aí aparece a pergunta que quebra a maioria dos projetos: como gerar identificador único, ordenável por tempo, sem um ponto central de coordenação, num sistema fragmentado?

A resposta deles foi embutir o endereço dentro do próprio identificador.

41 bits · tempo ms desde uma época própria 13 bits · shard onde o dado mora 10 bits · seq 1024 por ms um inteiro de 64 bits ordenável por tempo · gerado sem coordenação · diz sozinho em qual shard buscar
O identificador como endereço — a rota vem embutida no dado

O que isso resolve de uma vez só:

Ordenação. Como o tempo está nos bits mais significativos, ordenar por identificador é ordenar por criação. Feed, paginação e índice ficam baratos, pelo mesmo motivo que descrevi em UUIDv7, ULID ou Snowflake.

Roteamento. Dado um identificador de foto, você sabe em qual fragmento ela está sem consultar nenhuma tabela de mapeamento. O roteador é aritmética.

Geração distribuída. Cada fragmento gera o seu, sem falar com ninguém. Não existe serviço central de identificador pra cair.

Tamanho. Cabe num bigint. Índice menor, junção mais rápida, menos bytes no fio.

Eu acho essa a decisão mais elegante dos seis textos dessa série. Não porque é complexa: porque ela elimina três subsistemas que a maioria dos projetos acaba construindo.

O truque do fragmento lógico

A segunda decisão é a que resolve o problema que aparece depois: você fragmentou em oito máquinas e agora precisa de dezesseis.

Se o mapeamento for direto, shard = id % 8, mudar o número de máquinas rehasheia tudo e você tem uma migração monstruosa.

A saída é uma camada de indireção: milhares de fragmentos lógicos, distribuídos sobre poucas máquinas físicas.

FRAGMENTOS LÓGICOS — MILHARES, FIXOS PARA SEMPRE s0001 s0002 s0003 s0004 s0005 s8192 MÁQUINAS FÍSICAS — POUCAS, MUDAM COM O TEMPO db-01 s0001 · s0002 · s0003 db-02 s0004 · s0005 db-03 (nova) recebe s0003 sem rehash crescer = mover fragmento inteiro, não redistribuir linha
Indireção lógica — a máquina muda, o endereço do dado não

O identificador aponta pro fragmento lógico, que nunca muda. Uma tabela pequena diz em qual máquina aquele fragmento está hoje.

Crescer vira uma operação de infraestrutura: replique o fragmento pra máquina nova, aponte o mapa, apague o antigo. Nenhuma linha muda de identificador. Nenhum código muda.

Essa é a diferença entre uma migração de um dia e uma migração de um trimestre, e ela custa uma tabela de mapeamento decidida no primeiro mês.

O feed, e por que ele é diferente do Twitter

O Instagram enfrenta o mesmo problema de fan-out que descrevi em como eu enxergo a arquitetura do Twitter, com uma diferença que muda a conta: o conteúdo é pesado.

Um tweet é texto. Uma publicação é uma imagem ou vídeo, com várias resoluções, servida por rede de distribuição, com processamento no meio.

Isso separa o sistema em dois caminhos com características opostas.

O caminho dos metadados é pequeno, transacional, ordenado, e vive no banco fragmentado. O caminho da mídia é enorme, imutável depois de escrito, e vive em armazenamento de objeto com cache na borda.

Mídia imutável é uma vantagem que eu acho pouco explorada em sistema comum: se o arquivo nunca muda, o cache nunca precisa ser invalidado, o identificador pode ser o hash do conteúdo, e a distribuição vira um problema resolvido.

O que eu levo pra sistema pequeno

O identificador é uma decisão de arquitetura, não um detalhe. Ele carrega ordenação, roteamento e capacidade de geração distribuída. Escolher uuid_generate_v4() por hábito é abrir mão dos três de graça.

Coloque indireção entre o dado e a máquina antes de precisar. Fragmento lógico com mapeamento é barato quando você tem uma máquina só, e é o que torna a segunda máquina um evento operacional em vez de um projeto.

Prefira o chato. Um time pequeno que roda Postgres, um cache e uma fila entende o próprio sistema. O mesmo time com seis bancos especializados passa o dia aprendendo a operar, e o tempo de aprender sai do tempo de construir.

A parte que mais me marca nessa história é essa última. A arquitetura mais impressionante da série não é a que tem a peça mais engenhosa. É a que conseguiu não precisar delas.

← Voltar pro blog