Ir para o conteúdo principal
Setores

Criação de ramificações no Lakebase para CI/CD: lições de 100 bancos de dados de PR

A ramificação por cópia em gravação torna as cópias isoladas de dados de produção rápidas e baratas para testes de CI/CD e casos de uso de agentes.

por Yasamin Tari e Kartik Mathur

  • O pipeline de CI/CD do Snorkel AI fornece a cada pull request aberto sua própria ramificação do Lakebase Postgres, uma cópia completa de leitura/gravação dos dados de produção que fica pronta em menos de um minuto em vez de 30 minutos ou mais, motivo pelo qual eles nunca conseguiram habilitar a cópia por PR.
  • As ramificações do Lakebase usam cópia em gravação (copy-on-write), portanto, 100 bancos de dados de PR simultâneos em um conjunto de dados de produção de ~2,5 TB adicionam armazenamento apenas para os dados que cada ramificação altera. A expiração da ramificação (TTL) limpa cada ramificação automaticamente quando o PR é concluído, mantendo o armazenamento próximo ao tamanho da produção mais as alterações dos PRs ativos.
  • A conectividade privada e o suporte ao Okta SSO oferecem um ambiente governado e seguro.

O problema: testar com dados de produção não deveria levar mais de 30 minutos

A Snorkel AI desenvolve ferramentas de desenvolvimento de dados para equipes de IA, usadas por anotadores de dados especializados e provedores de modelos de ponta. Neste caso de uso específico, a Snorkel entrega código por meio de um pipeline de CI construído no Kubernetes. Cada pull request aberto, além de commits ad-hoc, recebe seu próprio namespace efêmero do Kubernetes (um "namespace de PR"). Os desenvolvedores implantam sua ramificação lá e executam o conjunto completo de testes antes do merge.

Esses testes são mais úteis quando executados com dados reais. O banco de dados de produção da Snorkel é um cluster Amazon Aurora PostgreSQL. Dar a cada namespace de PR sua própria cópia desse banco de dados era a parte difícil. Restaurar uma cópia levava cerca de 20 minutos por ambiente, o que é muito lento quando os desenvolvedores abrem PRs o dia todo e a fila de merge precisa de um ambiente novo para cada execução. As alternativas (bancos de dados de staging compartilhados, fixtures enxutas ou dados sintéticos) trazem de volta os problemas que os testes em ambiente semelhante ao de produção deveriam resolver: testes interferindo uns nos outros, esquemas desatualizados e bugs que só aparecem com formatos de dados reais.

Critérios de sucesso

A Snorkel consideraria o Lakebase um sucesso para CI/CD se ele atendesse a estes critérios:

  • Provisionamento rápido: Uma nova ramificação de PR fica pronta em menos de 60 segundos (p95).
  • Escala: Cerca de 100 ramificações de PR simultâneas no pico, cada uma com leituras e gravações independentes.
  • Compatibilidade: A aplicação existente e o conjunto de CI executam no Lakebase sobre o Postgres padrão, com desempenho comparável ao do Aurora e suporte a grandes colunas JSONB/LOB.
  • Dados atualizados: A ramificação principal permanece a cerca de 2 minutos do Aurora de produção por meio de replicação contínua do DMS.
  • Segurança: Os dados de produção permanecem em rede privada e nunca cruzam a internet pública, os desenvolvedores fazem login com o Okta SSO e os pipelines de CI se autenticam como service principals.

Por que a ramificação de banco de dados se encaixa no CI/CD

O Lakebase é o banco de dados Postgres serverless da Databricks. Seu armazenamento e computação são separados e ele suporta ramificação: você pode criar uma nova ramificação gravável e isolada de um banco de dados a partir de qualquer ponto no tempo, da mesma forma que cria uma ramificação do Git.

As ramificações são do tipo copy-on-write. A criação de uma ramificação é uma operação de metadados, portanto, nenhum dado é copiado quando ela é criada. Uma nova ramificação compartilha páginas de armazenamento com sua ramificação pai, e apenas as páginas alteradas pela ramificação são gravadas como novo armazenamento. Para CI, isso significa:

  • Criar uma ramificação leva segundos, não minutos. Isso não depende do tamanho do banco de dados.
  • O armazenamento cresce com as alterações feitas por cada ramificação, e não com o tamanho do banco de dados. Cada ramificação de PR da Snorkel grava cerca de 5 a 10 GB de alterações sobre uma base de ~1 TB.
  • A computação é por ramificação e escala para zero. Cada ramificação possui sua própria computação com autoscaling, que reduz a zero entre as execuções de CI. Mesmo com 100 ramificações provisionadas, a Snorkel espera que apenas cerca de 20 a 30 atendam a tráfego ao mesmo tempo, a cerca de 5 consultas por segundo cada no pico.

A arquitetura

image1.png

1. Manter uma cópia atualizada da produção na ramificação principal. O Lakebase é o Postgres padrão, portanto, funciona como destino para o AWS DMS sem ferramentas personalizadas. A Snorkel aponta uma tarefa do DMS para seu banco de dados Aurora de produção e o replica para a ramificação principal do projeto no Lakebase: primeiro uma carga completa, depois a captura contínua de dados alterados (CDC) que mantém a principal a cerca de 2 minutos da produção. O modo LOB completo transfere os valores JSONB de 10 a 20 MB. A equipe já executa o DMS, então não havia nenhum novo pipeline de replicação para construir ou manter. A ramificação principal é a "cópia definitiva" a partir da qual cada ramificação de PR começa.

2. Criar uma ramificação quando um PR é aberto. Quando um desenvolvedor abre um PR, o pipeline de CI chama a API do Lakebase para criar uma ramificação a partir da principal, define um tempo deexpiração nela e passa os detalhes de conexão da ramificação para o namespace do PR. A ramificação fica pronta em menos de um minuto.

3. Executar testes com dados reais. A aplicação no namespace do PR conecta-se à sua ramificação sobre o Postgres padrão. Os testes podem executar migrações, gravar dados e alterar esquemas sem afetar a produção, a principal ou qualquer outro PR.

4. Excluir a ramificação quando o PR for fechado ou o teste for aprovado, com o TTL como salvaguarda. Quando o PR é mesclado ou fechado, o pipeline exclui a ramificação. Se essa etapa de limpeza não for executada, a ramificação expira sozinha quando seu TTL se esgota.

Segurança: rede privada e identidade para humanos e pipelines

Dois requisitos de segurança descartaram outras opções, por isso merecem sua própria seção.

Conectividade privada. Os dados de produção passam do Aurora para o Lakebase, e do Lakebase para o cluster de CI, por meio de rede privada em vez da internet pública.

Identidade. A Snorkel tem dois tipos de usuários de banco de dados: desenvolvedores que se conectam interativamente para depurar um ambiente de PR e pipelines de CI que executam sem intervenção humana. O Okta SSO é construído em torno de login interativo no navegador, então dividimos o design por tipo de usuário:

  • Desenvolvedores: O Okta é federado à conta Databricks e os usuários são provisionados por meio de SCIM. Os desenvolvedores entram com sua identidade usual do Okta.
  • Pipelines de CI: Cada namespace de PR é executado como um service principal da Databricks (escopo por equipe ou por repositório), também provisionado via SCIM. O pod de PR usa as credenciais do service principal para obter tokens OAuth do Lakebase de curta duração por meio do SDK do Databricks e os atualiza durante execuções longas de teste. Não há senhas de banco de dados de longa duração no CI.
  • Auditoria: Os logs de auditoria do Postgres registram a identidade do service principal para cada consulta, de modo que cada consulta pode ser rastreada até o pipeline que a executou.

Por que o copy-on-write e a expiração de ramificações mantêm o CI rápido e econômico

Dois recursos do Lakebase fazem esse padrão funcionar em escala: ramificação baseada em copy-on-write e expiração de ramificações (TTL).

image3.png

O copy-on-write torna as ramificações instantâneas. Uma nova ramificação não copia nenhum dado. Ela aponta para as páginas de armazenamento existentes do pai, e apenas as páginas modificadas pela ramificação são gravadas como novo armazenamento. Criar uma ramificação a partir de uma cópia de produção de ~1 TB leva praticamente o mesmo tempo que criar uma a partir de um banco de dados de 1 GB: segundos, e não os ~20 minutos que uma restauração completa levava. Cada namespace de PR obtém sua própria cópia gravável e isolada de produção imediatamente.

O copy-on-write torna as ramificações econômicas. Cada ramificação compartilha a maior parte de seus dados com a principal, portanto, o armazenamento cresce com as alterações feitas por cada ramificação, e não com o tamanho do banco de dados. Para a Snorkel, isso representa cerca de 5 a 10 GB por ramificação de PR além da base compartilhada. A computação é por ramificação e escala para zero quando ociosa, de modo que apenas as ramificações que estão executando testes ativamente consomem computação.

A expiração de ramificações mantém o custo baixo ao longo do tempo. O copy-on-write economiza mais quando as ramificações têm vida curta. Uma ramificação mantém a versão dos dados que existia quando foi criada. Conforme o DMS continua atualizando a principal com as alterações de produção, uma ramificação que permanece ativa continua segurando essas páginas mais antigas, de modo que o armazenamento usado cresce com o tempo. Definir um TTL em cada ramificação de CI significa que cada ramificação é removida automaticamente assim que seu PR é concluído. Isso mantém o armazenamento próximo ao tamanho da base de produção mais as alterações de PRs ativos.

O mesmo padrão funciona para agentes de IA

Os namespaces de PR não são as únicas cargas de trabalho que precisam de uma cópia descartável de produção. Agentes de IA que escrevem SQL, executam migrações ou alteram dados também precisam de um lugar seguro para testar coisas, e a ramificação por copy-on-write oferece isso a eles.

Uma ramificação por tarefa de agente. Cada execução de agente obtém sua própria ramificação da principal. O agente pode ler dados reais, gravar neles e até mesmo alterar o esquema sem tocar na produção ou no trabalho de qualquer outro agente. Criar uma ramificação leva segundos, não importa o tamanho do banco de dados, por isso é prático criar uma para cada tarefa.

Vários agentes em paralelo. O armazenamento cresce apenas com as páginas que cada ramificação altera, e a computação de cada ramificação escala para zero quando está ociosa. Executar dezenas de agentes lado a lado custa aproximadamente o mesmo que as alterações que eles fazem, e não dezenas de cópias do banco de dados.

Revise as alterações ou descarte-as. As equipes podem verificar o que um agente alterou em sua ramificação antes de aplicar qualquer coisa à produção. Se o resultado não estiver correto, elas excluem a ramificação e não há nada para reverter.

Os TTLs fazem a limpeza para os agents. Os agents podem criar muitas branchs de curta duração. Uma data de expiração em cada branch remove as abandonadas automaticamente, da mesma forma que os TTLs limpam as branchs de CI.

Identidade com escopo para agents. Assim como os pipelines de CI, os agents podem ser executados como entidades de serviço com tokens OAuth de curta duração, para que cada consulta executada por um agent possa ser rastreada nos logs de auditoria.

Resultados

  • Tempo de provisionamento: mais de 30 minutos antes, menos de 60 segundos com o Lakebase [VERIFY p95 measured]
  • Escala: Projetado para cerca de 100 branchs de PR concorrentes em um conjunto de dados de produção de ~2,5 TB
  • Compatibilidade: A suíte de CI é executada no Lakebase sem "grandes problemas de compatibilidade" / desempenho comparado ao Aurora
  • Adoção: Implementação para 60 a 70 desenvolvedores em várias equipes
  • Citação de desenvolvedor: "Anteriormente, precisávamos copiar dados fisicamente e esses trabalhos de cópia levavam horas e frequentemente esbarravam em problemas de rede e outros gargalos com várias plataformas de CI (github actions, circle ci etc.). Estávamos procurando uma solução que atendesse aos nossos requisitos de segurança e conformidade caso os dados saíssem da nossa VPC"

Melhores práticas para ramificação do Lakebase em CI/CD

  1. Uma branch por PR, criada a partir de uma branch principal sincronizada continuamente.
  2. Sempre defina um TTL. Considere a expiração como obrigatória, não opcional. Alinhe-a com a duração normal de um PR ou de uma execução de teste.
  3. Crie uma nova branch em vez de manter uma antiga. Se um PR precisar de dados mais recentes, crie uma nova branch a partir da principal. Leva segundos.
  4. Permita que a computação seja dimensionada para zero. A maioria das branchs provisionadas fica ociosa a qualquer momento.
  5. Use entidades de serviço e tokens OAuth de curta duração para pipelines, e SSO para pessoas.
  6. Monitore o armazenamento por branch para identificar branchs de longa duração precocemente.

Pronto para dar a cada pull request seu próprio banco de dados semelhante ao de produção? Comece a usar o Lakebase e leia a documentação de ramificação para configurar a expiração de branchs no seu pipeline de CI.

(Esta publicação no blog foi traduzida utilizando ferramentas baseadas em inteligência artificial) Publicação original

Tags relacionadas:

tecnología

Receba os posts mais recentes na sua caixa de entrada

Assine nosso blog e receba os posts mais recentes diretamente na sua caixa de entrada.