Produtos
Soluções
Empresa
Empresas
EntrarCrie sua rede
architecture · schema-evolution · compatibility · platform-engineering · honest-architect

A evolução de esquema falha quando a sobreposição de versões não é medida

Uma mudança de esquema falha em produção não porque a migração está errada mas porque a sobreposição de versões não é medida. Teorema 3 sobre compatibilidade retroativa e progressiva, expand-and-contract, e o contrato wire na fronteira.

A evolução de esquema falha quando a sobreposição de versões não é medida

Uma mudança de esquema é geralmente um dos tipos de mudança mais difíceis para um sistema de software, e o artigo da ByteByteGo «Schema Evolution: Changing the Contract Without Breaking What Runs» abre com a razão honesta do porquê: a migração roda limpa em staging, e serviços não relacionados começam a falhar em produção, e nada estava errado com a migração em si (ByteByteGo, «Schema Evolution: Changing the Contract Without Breaking What Runs», 20 ago. 2026, https://blog.bytebytego.com/p/schema-evolution-changing-the-contract). A falha não é a mudança. A falha é o pressuposto de que apenas uma versão de esquema está em jogo. Linhas escritas há anos são lidas por código que desde então foi substituído. Mensagens sentadas numa fila foram publicadas antes de o consumidor atual ser escrito. Versões de app mobile de dezoito meses atrás ainda estão instaladas e ainda chamam a API. A propriedade «sem quebra numa mudança de esquema» é garantida exatamente quando o mecanismo — compatibilidade retroativa e progressiva mais sequenciamento expand-and-contract — está implementado e medindo se duas versões ainda estão vivas. Theorem 3: uma propriedade é garantida exatamente quando o seu mecanismo está implementado e medindo.

Conclusões principais

  • Mudanças de esquema quebram produção não porque a migração está errada, mas porque entra em vigor enquanto duas versões da aplicação ainda correm contra a mesma base de dados, e só uma dessas versões referenciava o esquema modificado (ByteByteGo, 20 ago. 2026).
  • Mais do que uma versão de esquema está sempre em jogo: linhas escritas há anos, mensagens em fila antes do consumidor atual, apps mobile com dezoito meses ainda a chamar a API — dados escritos sob uma versão de esquema são lidos sob outra.
  • «Sem quebra» é uma propriedade que se mantém exatamente quando a compatibilidade retroativa e progressiva é implementada como mecanismos e a sobreposição de versões é medida. Uma mudança de esquema sem mecanismo de compatibilidade é uma mudança de contrato sem garantia.
  • Theorem 3: uma propriedade é garantida exatamente quando o seu mecanismo está implementado e medindo. Expand-and-contract é o mecanismo; a timeline de deprecation que rastreia o último leitor antigo é a medida.

Mais do que uma versão de esquema está sempre em jogo

A introdução da ByteByteGo nomeia o facto estrutural que cada postmortem de mudança de esquema redescobre: a migração corre sem problemas em staging, e serviços não relacionados falham em produção, e a investigação não encontra nada de errado com a migração — entrou em vigor enquanto duas versões da aplicação ainda corriam contra a mesma base de dados, e só uma dessas versões referenciava o esquema modificado. Isso não é uma lacuna staging-produção. É um pressuposto de uma versão a encontrar uma realidade de várias versões. O staging testa a migração contra o código novo. A produção corre a migração contra o código novo e o código velho e os dados velhos que o código velho escreveu e as mensagens em fila que o velho publicador escreveu. O ambiente de staging é uma topologia de versão única; produção é uma topologia de sobreposição de versões.

[UNIQUE INSIGHT] A sobreposição de versões não é delimitada pela janela de deploy. A introdução da ByteByteGo é explícita: linhas escritas há anos podem ser produzidas por código de aplicação que desde então foi substituído. Mensagens sentadas numa fila foram publicadas antes de a versão atual do consumidor ser escrita. Versões de app mobile de dezoito meses atrás ainda estão instaladas em dispositivos reais e ainda chamam a API. A janela de deploy é a sobreposição mais pequena; a sobreposição de estado durável — linhas velhas, mensagens em fila, clientes mobile instalados — é a sobreposição que efetivamente o quebra. Um plano de mudança de esquema que só contabiliza a janela de deploy está a medir a sobreposição de versões mais pequena e a afirmar a propriedade sobre a maior. Essa é a lacuna entre o mecanismo que está implementado (coordenação da janela de deploy) e o mecanismo que garantiria a propriedade (rastreio completo da sobreposição de versões, incluindo estado durável e clientes instalados).

O Honest Architect lê a afirmação da ByteByteGo como uma afirmação topológica, não uma afirmação de processo. «More than one schema version is always in play at the same time» é uma afirmação sobre a forma de um sistema distribuído: escritores e leitores estão desacoplados no tempo, e o esquema é o contrato que atravessa a lacuna temporal. Uma mudança ao contrato é segura apenas se atravessar todos os leitores que ainda estão vivos, incluindo leitores que estavam vivos quando os dados foram escritos e leitores que estarão vivos quando os dados forem lidos. Compatibilidade retroativa e progressiva são dois mecanismos diferentes, não um só.

Compatibilidade retroativa e progressiva são dois mecanismos diferentes

Compatibilidade retroativa (backward) é a propriedade de que código novo lê dados velhos. Compatibilidade progressiva (forward) é a propriedade de que código velho lê dados novos. Parecem simétricas no nome e não são simétricas no mecanismo. A compatibilidade retroativa é uma propriedade que o autor do código novo pode garantir tolerando campos em falta — o código novo sabe qual era o aspeto do esquema velho. A compatibilidade progressiva é uma propriedade que o autor do código novo não pode garantir sozinho, porque o código velho já está deployed; a única garantia é fazer a mudança de uma forma que o código velho já tolera (adicionar campos opcionais, não remover campos que o código velho lê, não mudar a semântica dos campos). A compatibilidade retroativa é uma garantia ao tempo de escrita; a compatibilidade progressiva é uma restrição ao tempo de conceção sobre a própria mudança.

O artigo da ByteByteGo promete cobrir «which changes break consumers, which do not, and the qualifiers that decide it» — e os qualificativos são o mecanismo. Adicionar um campo opcional é retrocompatível (código novo lê dados velhos que não têm o campo) e progressivamente compatível (código velho lê dados novos que têm um campo extra que ignora). Renomear um campo não é nenhum: código velho lê dados novos e procura o nome velho do campo, não o encontra, parte. Remover um campo que o código velho lê é progressivamente incompatível. Mudar o tipo ou a semântica de um campo é ambos retroativamente e progressivamente incompatível mesmo que os bytes wire sejam iguais, porque o contrato é o significado, não os bytes. O Honest Architect trata um renome ou uma mudança de tipo como uma rutura de contrato, não uma mudança de esquema — viola um pressuposto que um leitor vivo está a fazer.

[PERSONAL EXPERIENCE] Os tipos wire da Everythink são definidos uma vez, em Zod, em @everythink/types, e as respostas são analisadas na fronteira da rede. Isso é um registo de esquema na fronteira, não apenas uma definição de tipo. O esquema Zod é o contrato; o parse-at-boundary é a medida — um payload que não corresponde ao esquema aparece como um ApiError tipado, nunca um crash. Etiquetamos isso Production ✅ porque o mecanismo (parse-at-boundary) está implementado e a medida (o erro tipado) corre em cada resposta. Uma mudança de esquema no backend sem uma mudança correspondente do esquema Zod é uma mudança de contrato sem medida do lado do consumidor — Partial ⚠️ até o esquema Zod ser atualizado e o parse-at-boundary apanhar o desvio.

A invariante do Oracle — probabilidades são normalizadas num único sítio, everythink-oracle::ensemble, de modo que os consumidores podem depender de sum(probability) ≈ 1.0 — é um contrato de esquema que o código a jusante lê. Uma mudança do sítio de normalização ou da ordem de ordenação seria uma rutura de contrato mesmo que os bytes wire parecessem idênticos, porque o contrato é a garantia em que os consumidores dependem. Etiquetamos essa invariante Production ✅ porque o mecanismo (sítio único de normalização) está implementado e a medida (os testes de ensemble) corre. A disciplina de evolução de esquema para essa invariante é: nunca mover o sítio de normalização sem uma sequência expand-and-contract que permita aos consumidores velhos continuar a ler a garantia velha enquanto os novos leem a nova.

Expand and Contract — o move topológico

Expand and Contract é o mecanismo que torna uma mudança de contrato segura sob sobreposição de versões. Expand: adicionar o novo elemento de esquema de uma forma que seja ambos retroativamente e progressivamente compatível. Deixar leitores velhos continuar a ler o contrato velho e leitores novos começar a ler o novo. Esperar que a sobreposição de versões escorre — clientes velhos atualizarem-se, mensagens velhas serem consumidas, linhas velhas envelhecerem e saírem. Contract: remover o elemento de esquema velho assim que nenhum leitor vivo o referencia. A fase expand é a expansão topológica (duas rotas coexistem); a fase contract é a contração topológica (uma rota permanece).

A medida que controla a fase contract é a timeline de deprecation que rastreia o último leitor antigo. A ByteByteGo promete «versioning strategies and deprecation timelines» — e a timeline de deprecation é a medida que faz do expand-and-contract uma propriedade garantida em vez de uma esperança. O Honest Architect etiqueta um plano expand-and-contract sem medida do último leitor antigo como Partial ⚠️: o mecanismo (expand, esperar, contract) está implementado, mas a medida que controla o contract não está. Um plano que rastreia o último leitor antigo — por telemetria de versão de cliente, por idade de mensagem em fila, por tags de versão de esquema de linha — e só contrai quando essa contagem chega a zero é Production ✅.

[ORIGINAL DATA] A disciplina de migração da Everythink etiqueta isto diretamente. Cada migração .up.sql tem uma .down.sql correspondente — esse é o caminho de rollback, o contrato de que um expand falhado pode ser desfeito. A .down.sql é a rede de segurança da fase expand: se o expand quebra um leitor vivo, contrai-se a mudança de esquema (executa-se a migração down) e os leitores velhos retomam. Etiquetamos o caminho de rollback Production ✅ porque cada migração tem um e o migrador impõe o emparelhamento. Uma migração que adiciona e dropa num só passo é um cutover, não um expand-and-contract — Partial ⚠️ se o código velho ainda está vivo, porque a fase contract corre dentro da sobreposição de versões.

A cache geo do World Monitor carrega a mesma disciplina numa forma diferente. Os ids dos GeoSignal são determinísticos uuidv5(source, native_id) — re-ingest atualiza, nunca cria duplicados. Isso é um mecanismo de compatibilidade progressiva para a cache: um novo ingest do mesmo sinal atualiza a linha em vez de criar uma segunda linha, de modo que um leitor que viu o id velho e um leitor que vê o id novo estão a ler a mesma linha. A estabilidade do id é o contrato; o uuidv5 determinístico é o mecanismo; o upsert é a medida (a contagem de linhas não cresce no re-ingest). Etiquetamos isso Production ✅ porque o mecanismo está implementado e a estabilidade da contagem de linhas é observável. Uma mudança de esquema ao esquema de ids seria um renome da chave primária — a mudança mais progressivamente incompatível que existe — e exigiria um expand-and-contract que escreve ambos os ids e migra os leitores antes de dropar o velho.

O que um Honest Architect lê numa introdução paywalled

O artigo da ByteByteGo está paywalled depois do cabeçalho de secção «Version Overlap», e o Honest Architect não fabrica o corpo. O que é visível é a afirmação estrutural — mais do que uma versão de esquema está sempre em jogo, a migração é limpa em staging e parte em produção, a falha não é a migração mas a sobreposição de versões — e essa afirmação é suficiente para aplicar a disciplina de etiquetagem. A introdução visível dá: a realidade multi-versão (Production ✅ como facto estrutural dos sistemas distribuídos), a lacuna staging-produção como diferença topológica versão-única-vs-multi-versão (Production ✅ como enquadramento), e a promessa de compatibilidade retroativa/progressiva, expand/contract, registos de esquema e timelines de deprecation como mecanismos.

A regra do Honest Architect: citar a fonte real, nunca fabricar URL ou métrica, nunca afirmar que o artigo disse algo que não disse. A introdução diz que a migração corre limpa em staging e parte em produção porque duas versões estão a correr contra a mesma base de dados. Essa é a afirmação citada. O resto da análise é o mecanismo aplicado à própria stack da Everythink, etiquetado contra a nossa própria implementação, não atribuído à ByteByteGo. A afirmação cross-domain (expand-and-contract é o mesmo move topológico num esquema de base de dados e num ensemble de previsão) é Partial ⚠️ porque a forma é partilhada e os domínios são separados.

O limite de âmbito: a Everythink é uma plataforma de previsão civil e defensiva, não uma consultoria de bases de dados. A lição de evolução de esquema é cross-domain — uma mudança de contrato só é segura quando a sobreposição de versões é medida — e aplica-se seja o contrato um esquema de base de dados, um payload de API, um tipo Zod ou uma invariante de ensemble de previsão. Nenhum resultado de token, wallet ou community-credit é prometido; esses são Roadmap 🔵, sujeitos a revisão Howey. Um item de Roadmap nunca é promovido silenciosamente a Production com base numa forma de evolução de esquema.

Perguntas frequentes

Por que razão uma mudança de esquema parte a produção quando a migração correu limpa em staging?

Porque o staging testa a migração contra o código novo apenas, e a produção corre o código novo ao lado do código velho, dados velhos, mensagens em fila e clientes mobile instalados que foram escritos sob o esquema velho. A migração é limpa; a sobreposição de versões não é. A falha não é a mudança — é o pressuposto de que apenas uma versão de esquema está em jogo. Theorem 3: a propriedade «sem quebra» só é garantida quando o mecanismo de sobreposição de versões está implementado e a medir.

Qual é a diferença entre compatibilidade retroativa e progressiva?

A compatibilidade retroativa (backward) é a propriedade de que código novo lê dados velhos — o autor do código novo garante-a tolerando campos em falta. A compatibilidade progressiva (forward) é a propriedade de que código velho lê dados novos — o autor do código novo não pode mudar o código velho, por isso a única garantia é fazer a mudança de uma forma que o código velho já tolera (adicionar campos opcionais, não remover ou renomear campos que o código velho lê). A compatibilidade retroativa é uma garantia ao tempo de escrita; a compatibilidade progressiva é uma restrição ao tempo de conceção sobre a própria mudança.

O que é expand and contract?

Expand: adicionar o novo elemento de esquema de uma forma que seja ambos retroativamente e progressivamente compatível. Deixar leitores velhos e novos coexistir. Esperar que a sobreposição de versões escorre — clientes velhos atualizarem-se, mensagens velhas serem consumidas, linhas velhas envelhecerem e saírem. Contract: remover o elemento de esquema velho assim que nenhum leitor vivo o referencia. A fase expand é uma expansão topológica (duas versões de contrato coexistem); a fase contract é uma contração topológica (uma versão permanece).

Como é que a Everythink impõe evolução de esquema na fronteira wire?

Os tipos wire são definidos uma vez, em Zod, em @everythink/types. As respostas são analisadas na fronteira da rede; um payload que não corresponde ao esquema aparece como um ApiError tipado, nunca um crash. O esquema Zod é o contrato; o parse-at-boundary é a medida. Uma mudança de esquema de backend sem uma mudança correspondente do esquema Zod é uma mudança de contrato sem medida do lado do consumidor — Partial ⚠️ até o esquema Zod ser atualizado.

A Everythink promete que mudanças de esquema nunca partem os consumidores?

Não. Prometemos o mecanismo: parse-at-boundary Zod, emparelhamento .up.sql/.down.sql, invariante Oracle de sítio-único-de-normalização, uuidv5 determinístico para a cache do World Monitor. A propriedade «sem quebra numa dada mudança» se mantém quando a mudança segue expand-and-contract e a timeline de deprecation rastreia o último leitor antigo. Uma mudança que faz cutover dentro da sobreposição de versões é Partial ⚠️. Nenhum resultado de token, wallet ou community-credit é prometido; esses são Roadmap 🔵.

Sources

Se a sua equipa está pronta para medir a sobreposição de versões, não só a janela de deploy, crie a sua rede — a topologia roteia duas versões de contrato, a fronteira faz parse, o caminho de rollback desfaz.

Construa seu mundo sobre um motor que prova o que afirma.

Crie sua própria rede no motor que está em produção desde 2016 — ou fale com a equipe por trás dos 21 artigos.