Reestruturação dos dados em produção

João Paulo Lethier
Zygo Tech
Published in
5 min readOct 6, 2016

Há um mês atrás, numa discussão de projeto, pensamos em uma estrutura para que nosso app tivesse uma funcionalidade que permitiria uma interação e troca de feedbacks com o usuário de uma forma muito melhor que a que tínhamos no momento. Nosso objetivo era mudar o fluxo do processo de análise dos cupons enviados pelo app do Bonuz.

Atualmente os cupons enviados pelos usuários do Bonuz são analisados sempre de forma individual, e caso o usuário não concorde com alguma análise, ele tem que entrar em contato conosco ou enviar outro cupom. Queríamos deixar esse processo mais rápido, tanto do nosso lado, dando a resposta da aprovação ou não do cupom em menos tempo, como do usuário, nos informando de alguma divergência de forma mais rápida. Com isso todo o processo custaria menos para a empresa e ao mesmo tempo a gente poderia emponderar o usuário, tornando ele parte do processo, e permitindo que ele tomasse a inciativa em alguns momentos também.

A idéia era ótima, só tinha um desafio: teríamos que mudar muita coisa na estrutura de dados atual da nossa API, e teríamos que fazer essa mudança de forma que funcionasse para os usuários das novas versões do app com essa nova funcionalidade, mas para os usuários que não atualizassem o app, isso teria que ser transparente, eles não deveriam nem perceber a mudança.

Para isso funcionar, separamos o processo de desenvolvimento em duas etapas:

  • Criação de toda a nova estrutura e mudança dos endpoints atuais da API para utilizarem a nova estrutura;
  • Mudança no processo para permitir a nova funcionalidade sem afetar os usuários atuais.

Essa divisão em etapas nos permitiu separar a etapa de migração e reestruturação dos dados da etapa de lançamento da nova funcionalidade, com isso nós conseguimos ter mais foco para pensar e resolver os impactos que cada etapa causava, tanto internamente como para os usuários.

O maior desafio da primeira etapa consistia na migração dos dados já salvos, pois nosso objetivo era não ter nenhum downtime da nossa API por causa da nova estrutura.

Dado o objetivo, definimos dois requisitos que tínhamos que cumprir para podermos fazer o deploy dessa primeira etapa:

  • Todos os processos que utilizavam a estrutura velha de cupons teriam que atualizar, criar e migrar os dados para a estrutura nova de compras em tempo de execução para todos os cupons pendentes e novos envios dos usuários;
  • A migração dos dados teria que ocorrer de forma bem rápida e migrar os dados para todos os cupons já processados pelo sistema.

Migração dos dados

Na migração de dados, listamos todas as tabelas que sofreriam algum impacto. O impacto nas tabelas de compras e cupons era óbvio, mas também teríamos que alterar a tabela onde guardamos os créditos dos usuários e a tabela onde guardamos os reviews, pois dado que iríamos agrupar mais de um cupom em uma única compra, essas duas tabelas teriam que passar a se relacionar com a tabela de compras, para que numa mesma compra não houvesse a geração de dois créditos nem fosse guardado dois reviews.

Na nossa estrutura atual tínhamos uma tabela de cupons e algumas tabelas relacionadas com ela, e a mudança impactaria em:

  • Criação de um novo registro na tabela de compras para cada um dos 2 milhões de cupons registrado no banco de dados;
  • Todos os créditos já gerados no sistema(~1,5mi) e todos os reviews já enviados pelos usuários(~500k) passariam a se relacionar com o modelo de compras, e não mais com cupom;
  • Precisávamos criar as atividades passadas relacionadas a compras na tabela de atividades de usuários para mantermos os dados de toda a história do bonuz refletindo a estrutura atual

Inicialmente criamos uma task do rails, a idéia era usar a própria tecnologia que já estamos bastante acostumados e que facilitaria para migrarmos vários models e dados através do activerecord, como estamos acostumados a trabalhar no dia-a-dia.

Rodamos essa task localmente e em staging e conferimos todos os dados migrados. Tudo parecia perfeito, todos os dados estavam corretos, a task poderia parar no meio e ser continuada depois sem problemas, mas faltava testar com a quantidade de dados que realmente precisávamos migrar.

Geramos uma nova instância do nosso banco de dados na Amazon a partir de um backup recente(uma pequena observação, péssima escolha da Amazon chamar a ação de criação de nova instância de "Restore backup"), conectamos com o banco remotamente e rodamos a task para testar a velocidade. Resultado: menos de 1k cupons por hora sendo processados na migração.

Bom, precisávamos de mais velocidade, e o primeiro passo para isso foi dentro de uma única transaction cada 5000 cupons e tirar todas as validações do activerecord. Novo resultado: cerca de 1,5k cupons por hora sendo processados. Numa pequena conta, vi que se conseguíssemos chegar a 62k cupons por hora ainda teríamos uma migração que demoraria mais de um dia para rodar.

Percebemos que utilizar o rails para essa migração não tinha sido uma boa escolha, e não era uma alternativa viável para o que precisávamos. Resolvemos excluir o rails e pensar na possibilidade de utilizarmos SQL direto para isso. Por exemplo, um script sql para criar registro de uma atividade de 'Compra criada' para cada usuário que se cadastrou antes do dia 01/01/2016 seria:

insert into activities (name, user_id, interacted_at)
select 'Conta criada', users.id, users.created_at
FROM users
WHERE users.created_at <= '2016-01-01'

Montamos o nosso script sql para testarmos a migração somente para criação das compras e resolvemos testar. Resultado: 2mi de compras inseridas no banco de dados em menos de 5 minutos. Conferimos os dados e estava tudo certo. Solução encontrada, só faltava criar e testar os scripts SQL para todas as outras mudanças.

Criamos todos os SQL, tanto de insert como de update, de todas as tabelas que precisávamos alterar, geramos mais uma instância do banco de dados na Amazon e testamos. Resultado: Migração finalizada em menos de 10min.

Solução encontrada, código pronto, faltava só fazer o deploy. Com todas as decisões e escolhas que fizemos durante o desenvolvimento, poderíamos fazer o deploy em qualquer horário que não afetaria o usuário, mas como sempre é bom ter um cuidado extra, escolhemos um horário onde temos menos movimento de envio de cupons e fizemos o deploy. Primeiro confirmamos se o fluxo novo não tinha causado nenhum impacto para os usuários atuais. Testamos o envio de cupom no app atual, o processamento, e tudo estava funcionando perfeito. Novamente tivemos um cuidado extra, pois todos os testes já haviam sido feitos em staging, e todo o código possui teste automatizado, mas nossa equipe acha importante sempre termos esse cuidado extra com o nosso usuário.

Hora da verdade, hora de rodar a migração. Primeiro colhemos vários dados do estado atual do banco de dados, e deixamos vários scripts de select prontos para podermos conferir se os dados continuavam corretos após a migração. Pronto, comando executado, migração rodando e, 9 minutos depois, migração terminada. Isso mesmo, em 9 minutos todos os nossos dados foram migrados. Conferimos todos os dados e tudo estava correto.

Problema solucionado e terminado. Mas essa foi só a primeira etapa, a da migração, a segunda etapa eu deixo talvez para um próximo post.

--

--