Pular para o conteúdo

Arquitetura real · Pleno

Uber: por que trocou Postgres por MySQL

Arquitetura real em produção. Pontos centrais: storage engines (LSM) e migração de dados online.

15 min de leitura · Fonte: Uber · Conteúdo revisado em 23 de outubro de 2026

Resposta curta

Arquitetura real em produção. Pontos centrais: storage engines (LSM) e migração de dados online. A arquitetura inicial do Uber era um backend monolítico em Python que usava Postgres para guardar dados. Depois a arquitetura passou a microsserviços e a novas plataformas de dados, e em muitos dos casos em que antes se usava Postgres passou-se a usar o Schemaless, uma…

01 · CONTEXTO

De Postgres a MySQL: o que o Uber relatou

A arquitetura inicial do Uber era um backend monolítico em Python que usava Postgres para guardar dados. Depois a arquitetura passou a microsserviços e a novas plataformas de dados, e em muitos dos casos em que antes se usava Postgres passou-se a usar o Schemaless, uma camada de sharding de banco de dados construída sobre o MySQL.

O artigo de engenharia que explica a troca lista cinco limitações do Postgres: arquitetura ineficiente para escritas, replicação de dados ineficiente, problemas de corrupção de tabelas, suporte fraco a MVCC nas réplicas e dificuldade de migrar para versões novas.

O texto analisa essas limitações comparando como o Postgres e o MySQL, com o motor de armazenamento InnoDB, representam tabelas e índices no disco.

02 · FORMATO EM DISCO

Onde o índice aponta

Um aspecto central do desenho do Postgres são as linhas imutáveis, chamadas de tuplas e identificadas por um ctid, que representa conceitualmente o local da tupla no disco. Um mesmo registro pode ter vários ctids, por exemplo quando existem várias versões dele. Os índices, o primário e os secundários, apontam diretamente para esses locais.

Para atualizar um registro, o Postgres adiciona uma nova tupla à tabela e guarda em cada tupla um campo de versão e um ponteiro para a tupla anterior. Enquanto as duas versões existem, os índices precisam ter entradas para as duas.

O InnoDB, motor de armazenamento do MySQL, também suporta MVCC e dados mutáveis, mas seus registros de índice secundário guardam um ponteiro para o valor da chave primária, e não para o local da linha no disco.

AspectoPostgresMySQL com InnoDB
Ponteiro no índice secundárioLocal da tupla no disco (ctid)Valor da chave primária
Atualização de uma linhaAdiciona uma nova tupla, com versão e ponteiro para a anteriorNormalmente no próprio lugar; a versão antiga vai para o rollback segment
Buscas num índice secundárioUmaDuas
Quando o local da linha mudaTodos os índices precisam ser atualizadosSó o índice primário precisa ser atualizado

O custo do desenho do InnoDB é uma busca a mais: uma consulta por índice secundário procura primeiro a chave primária e depois consulta o índice primário para achar o local da linha. O texto trata isso como uma leve desvantagem frente ao Postgres nesse tipo de consulta.

03 · CUSTO DAS ESCRITAS

Uma atualização pequena, várias escritas

No exemplo do artigo, a tabela tem três índices: a chave primária e dois secundários. Atualizar o ano de nascimento de uma única linha exigiu pelo menos quatro escritas físicas no Postgres: a nova tupla e uma nova entrada em cada índice, inclusive nos que cobrem campos que não mudaram.

Cada uma dessas escritas também precisa ser refletida no WAL, então o total de escritas em disco é ainda maior. Numa tabela com uma dúzia de índices, uma atualização num campo coberto por um único índice precisa ser propagada a todos os doze.

No InnoDB a mesma atualização altera o campo no próprio lugar, copia a linha antiga para o rollback segment e atualiza apenas os índices que de fato cobrem o campo alterado; o índice da chave primária e o do nome não precisam ser tocados.

A limpeza de versões antigas também muda. No InnoDB, as linhas elegíveis para limpeza estão diretamente no rollback segment, enquanto o autovacuum do Postgres precisa varrer tabelas inteiras para identificar linhas apagadas.

04 · REPLICAÇÃO

WAL físico versus log lógico

O Postgres implementa a replicação por streaming enviando o WAL do mestre às réplicas. Como o WAL foi projetado para recuperação de falhas, ele carrega informação de baixo nível sobre as atualizações em disco, no nível das tuplas e dos seus deslocamentos.

O Uber começou com servidores físicos numa colocation na costa oeste e, para recuperação de desastres, adicionou servidores numa segunda colocation na costa leste. O mestre Postgres, com suas réplicas, ficava no data center oeste, e um conjunto de réplicas no leste.

  1. A aplicação grava no mestre Postgres, que mantém um registro de mudanças (WAL) usado na recuperação de falhas.

    Diagrama da arquitetura: Postgres: replicação física entre data centers
    CPAplicação
    DBPostgres mestremantém o WAL
    DBRéplicas do mesmo data center
    DBRéplicas do outro data center
    DBArquivo de WAL
  2. O mestre envia o WAL às réplicas; o conteúdo do WAL descreve as tuplas e seus deslocamentos no disco.

    Diagrama da arquitetura: Postgres: replicação física entre data centers
    CPAplicação
    DBPostgres mestre
    DBRéplicas do mesmo data centeraplica o WAL
    DBRéplicas do outro data centeraplica o WAL
    DBArquivo de WAL
  3. Uma pequena mudança lógica vira várias escritas físicas, e as entradas de WAL de todas elas se propagam pela rede.

    Diagrama da arquitetura: Postgres: replicação física entre data centers
    CPAplicação
    DBPostgres mestrevárias escritas físicas
    DBRéplicas do mesmo data center
    DBRéplicas do outro data center
    DBArquivo de WAL
  4. Com réplicas em outro data center, esse volume pesa no link entre os dois locais.

    Diagrama da arquitetura: Postgres: replicação física entre data centers
    CPAplicação
    DBPostgres mestre
    DBRéplicas do mesmo data center
    DBRéplicas do outro data centerpesa no link
    DBArquivo de WAL
  5. O WAL também era arquivado em um serviço de armazenamento, e no pico inicial a banda até ele não acompanhou.

    Diagrama da arquitetura: Postgres: replicação física entre data centers
    CPAplicação
    DBPostgres mestre
    DBRéplicas do mesmo data center
    DBRéplicas do outro data center
    DBArquivo de WALbanda insuficiente

Segundo o texto, a verbosidade do protocolo de replicação pode gerar um volume avassalador de dados para bancos com muitos índices, e não é possível obter um link entre costas com a mesma banda de uma interconexão local. O arquivamento do WAL também sofreu: no pico inicial, a banda até o serviço de armazenamento não acompanhava a taxa de escrita dos WALs.

O MySQL oferece replicação baseada em instruções, em linhas e uma mista. A replicação por instruções costuma ser a mais compacta, mas pode obrigar as réplicas a executar instruções caras; a por linhas é mais verbosa, porém mais previsível e eficiente nas réplicas.

Como só o índice primário aponta para os deslocamentos das linhas, o fluxo de replicação do MySQL precisa conter apenas atualizações lógicas, e as réplicas inferem sozinhas as mudanças de índice. O texto conclui que o log binário de replicação do MySQL é bem mais compacto que o fluxo de WAL do Postgres.

  1. A atualização normalmente é aplicada no próprio lugar e a versão antiga da linha vai para o rollback segment.

    Diagrama da arquitetura: MySQL: replicação lógica e leituras em réplicas
    CPAplicação
    CPConsultas de leitura
    DBMySQL mestre (InnoDB)atualiza no lugar
    DBRéplica atualizada primeiro
    DBRéplica que serve leituras
  2. O fluxo de replicação leva só atualizações lógicas de linhas; as réplicas inferem sozinhas as mudanças de índice.

    Diagrama da arquitetura: MySQL: replicação lógica e leituras em réplicas
    CPAplicação
    CPConsultas de leitura
    DBMySQL mestre (InnoDB)
    DBRéplica atualizada primeiroinfere índices
    DBRéplica que serve leiturasinfere índices
  3. Como o fluxo é lógico, a réplica pode ter semântica MVCC verdadeira e as leituras nela não bloqueiam a replicação.

    Diagrama da arquitetura: MySQL: replicação lógica e leituras em réplicas
    CPAplicação
    CPConsultas de leitura
    DBMySQL mestre (InnoDB)
    DBRéplica atualizada primeiro
    DBRéplica que serve leiturasMVCC verdadeiro
  4. Na atualização de versão, atualiza-se uma réplica por vez e, ao fim, promove-se uma delas a novo mestre.

    Diagrama da arquitetura: MySQL: replicação lógica e leituras em réplicas
    CPAplicação
    CPConsultas de leitura
    DBMySQL mestre (InnoDB)
    DBRéplica atualizada primeiroatualiza primeiro
    DBRéplica que serve leituras

05 · RÉPLICAS, CORRUPÇÃO E ATUALIZAÇÃO

Quando a replicação física cobra a conta

O Postgres não tem suporte real a MVCC em réplicas: como elas aplicam o WAL, têm uma cópia dos dados em disco idêntica à do mestre. Se uma réplica tem uma transação aberta, as atualizações que afetam as linhas seguradas por ela ficam bloqueadas e a aplicação do WAL é pausada até a transação terminar. Como a réplica pode ficar muito atrasada, o Postgres aplica um timeout e encerra a transação que bloqueia.

O texto diz que as réplicas podem rotineiramente ter um atraso de segundos em relação ao mestre, e que por isso é fácil escrever código que tem transações encerradas à força. Um exemplo é uma transação implícita que fica aberta até o fim do envio de um e-mail de recibo.

No MySQL, como o fluxo de replicação carrega atualizações lógicas, as réplicas podem ter semântica MVCC verdadeira, e as consultas de leitura nelas não bloqueiam a replicação.

O Uber relatou um bug do Postgres: durante uma promoção rotineira de mestre, as réplicas seguiram incorretamente as trocas de timeline e aplicaram mal alguns registros do WAL, e registros que deveriam ter sido marcados como inativos não foram. Uma consulta por uma chave passaria a devolver dois registros, e os resultados duplicados fizeram a lógica de aplicação falhar em vários casos.

Como o bug afetou todos os servidores, as linhas corrompidas eram diferentes de réplica para réplica, e a equipe não sabia ao certo quantas réplicas, nem se o mestre, estavam afetados. A correção das réplicas foi ressincronizá-las a partir de um novo snapshot do mestre, um processo trabalhoso, porque só havia capacidade para retirar poucas réplicas do pool de balanceamento por vez.

Segundo o texto, o bug afetou só certas versões do Postgres e foi corrigido há muito tempo, mas os autores ainda consideram preocupante que essa classe de bug possa acontecer: pela forma como a replicação funciona, o problema poderia se espalhar por todos os bancos de uma hierarquia de replicação.

No MySQL a replicação é lógica, então uma operação como rebalancear uma B-tree nunca corrompe um índice. Um problema típico é uma instrução pulada ou, menos comum, aplicada duas vezes, o que pode deixar dados faltando ou inválidos, mas não causa uma indisponibilidade do banco.

A replicação física também dificulta a atualização de versão: segundo o texto, não é possível replicar entre releases GA diferentes do Postgres. O procedimento incluía rodar o pg_upgrade no mestre, que pode levar muitas horas num banco grande sem servir tráfego, e depois apagar cada réplica e restaurá-la a partir de um novo snapshot.

O Uber concluiu a subida do Postgres 9.1 para o 9.2, mas o processo levou tantas horas que não podia ser repetido, e as instâncias legadas continuavam no 9.2 à época do texto. Para quem usa o Postgres 9.4 ou mais novo, o texto cita o pglogical, que permite replicar entre releases diferentes, mas ressalva que ele não é integrado à árvore principal do Postgres.

No MySQL, segundo o texto, é trivial replicar entre releases diferentes. O jeito típico de atualizar é aplicar a mudança a uma réplica por vez e, ao fim, promover uma delas a novo mestre, com quase nenhum tempo parado.

06 · OUTRAS DIFERENÇAS E CONCLUSÃO

Cache, conexões e o balanço do Uber

O Postgres deixa o kernel cachear os dados recentes no page cache, e ler dados por essa via exige chamadas de sistema para localizá-los, cada uma com uma troca de contexto, mais cara que acessar a memória principal. O InnoDB implementa o próprio LRU, o buffer pool, o que resulta em menos trocas de contexto.

O MySQL cria uma thread por conexão, e o Postgres usa um processo por conexão, bem mais caro. O Uber relatou problemas para escalar o Postgres além de algumas centenas de conexões ativas; a documentação recomenda fortemente um pool de conexões fora do processo, e o pgbouncer foi geralmente bem-sucedido, mas bugs de aplicação que abriram conexões demais causaram indisponibilidades prolongadas.

O texto conclui que o Postgres serviu bem no início do Uber, mas surgiram problemas significativos ao escalar com o crescimento. Restaram instâncias Postgres legadas, e a maior parte dos bancos de dados passou a ser construída sobre o MySQL, tipicamente com o Schemaless, ou, em casos especializados, em bancos NoSQL como o Cassandra.

Perguntas frequentes

O que é Uber: por que trocou Postgres por MySQL?

Arquitetura real em produção. Pontos centrais: storage engines (LSM) e migração de dados online.

O que o design de Uber: por que trocou Postgres por MySQL cobre?

Ele está organizado nestas seções: De Postgres a MySQL: o que o Uber relatou, Onde o índice aponta, Uma atualização pequena, várias escritas, WAL físico versus log lógico, Quando a replicação física cobra a conta e Cache, conexões e o balanço do Uber.

Quanto tempo leva Uber: por que trocou Postgres por MySQL e qual é o nível?

Leva cerca de 15 minutos para ler e tem nível Pleno.

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.