Como métricas e dados podem guiar seu processo de desenvolvimento de software

Publicado por Karla Garcia no dia dev

Um dos valores do culture code da Resultados Digitais é ser Data Driven, ou seja, não baseamos nossas ações em “achismos” ou opiniões infundadas. Procuramos sempre utilizar dados para provar ou contrariar nossas hipóteses.

Trabalho no time de Produto, responsável pelo desenvolvimento do RD Station, e aqui também utilizamos dados para tomar decisões mais certeiras e evitar desperdício de tempo em soluções ineficientes.

Recentemente meu time e eu precisamos melhorar a performance de um processo e adotamos algumas medidas para garantir que nossas ações trariam resultados positivos. É sobre o procedimento que realizamos para alcançar esse objetivo que falo a seguir.

O Problema

O time do qual faço parte é responsável pela temática de Análises do RD Station, e uma de nossas responsabilidades é uma solução de Business Intelligence (BI) inserida dentro do nosso produto.

Para alimentar este BI, desenvolvemos um processo de ETL (Extraction-Transform-Load) que extrai, transforma e carrega, em um data warehouse, dados que permitem aos nossos clientes obterem insigths importantes para seus respectivos negócios.

Essa atividade é realizada diariamente e sua performance é essencial para que essas informações estejam disponíveis o mais rápido possível. Porém com o aumento contínuo no número de clientes e, consequentemente, aumento na quantidade de dados, percebemos que a fase de transformação estava se tornando cada vez mais lenta.

Nosso desafio era reduzir o tempo do processamento gasto na fase de transformação, sendo que essa se divide basicamente em três tarefas: selecionar os dados já extraídos pela etapa anterior (Extração), transformá-los para o formato esperado e atualizá-los na base de dados deixando-os disponíveis à próxima etapa (Carga).

Para termos um ganho de performance eficiente precisávamos identificar exatamente qual dessas tarefas estava consumindo mais tempo e atacá-la primeiro.

O Ofensor

Aqui no time de produto nós utilizamos o New Relic para monitorar nossas aplicações através da obtenção de métricas em tempo real referentes à transações web e tarefas executadas em background.

Também utilizamos o Sidekiq para processar tarefas executadas em background, como é o caso da transformação de dados. O agente Ruby do New Relic, por padrão, instrumenta o método de instância perform, que representa o ponto de entrada de qualquer worker. Logo nós já tínhamos uma métrica indicando que a tarefa de transformação dos dados demorava em média 6 horas para ser finalizada. Mas precisávamos de uma métrica que representasse cada etapa desta tarefa.

O agente Ruby do New Relic coleta automaticamente muitas métricas, assim como também possui uma API que permite coletar métricas adicionais sobre a aplicação. Para casos onde há a necessidade de analisar detalhes dos trechos de códigos de transações, a instrumentação personalizada possibilita uma imagem mais completa do que está acontecendo na aplicação.

Logo, passamos a monitorar cada método envolvido no processo através da instrumentação customizada, conforme apresentado abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require 'new_relic/agent/method_tracer'
 class Transformer
   include ::NewRelic::Agent::MethodTracer

   def select_events
      
   end

   def process_events
      
   end

   def update_events
      
   end

   add_method_tracer :select_events, 'Custom/select_events'
   add_method_tracer :process_events, 'Custom/process_events'
   add_method_tracer :update_events, 'Custom/update_events'
end

Vale informar que a própria documentação do New Relic faz uma observação referente ao uso de métricas customizadas pois, quando em grande quantidade, elas podem impactar na performance da sua aplicação. Logo orientam a manter a quantidade abaixo de 2000.

Nossa aposta era que o grande ofensor seria o método Transformer#process_events, responsável pelo processamento dos dados, porém para nossa surpresa o resultado obtido através do NewRelic foi este:

Segment % Time Avg calls Avg time
update_events 65.9 6.33 17,900
select_events 18.0 6.34 4,880
process_events 13.2 4.85 3,580

O método Transformer#update_events, responsável pela atualização dos dados processados estava consumindo 65.9% do tempo de execução, enquanto que o processamento era o que menos impactava. Identificado nosso grande ofensor, bastava apenas aplicar a solução.

A Solução

As informações obtidas pela Extração são referentes aos eventos realizados por cada um dos potenciais consumidores (Leads) de nossos clientes e ficam armazenadas em uma base de dados. A chegada de novos eventos associados a um consumidor específico implicam no reprocessamento de todas as suas informações, na remoção dos dados desatualizados e inclusão dos novos.

Após identificar que Transformer#update_events estava bastante lento, foi possível perceber que esse processo de incluir e remover informações, e consequentemente indexes, realizado pelo banco estava degradando muito a sua performance.

Decidimos então que, ao invés de manter uma tabela permanente na qual realizávamos sucessivas inclusões e remoções, tabelas permanentes iriam manter apenas os dados em formato raw e os dados processados seriam inseridos em tabelas temporárias que seriam removidas após o processo de Carga dos dados.

Aqui na Resultados Digitais, além de Data-Driven, também somos Lean. Precisamos avançar rápido, mesmo tendo pouco tempo e/ou recursos, pois há muito a se fazer. Logo, definida a solução, não queríamos correr o risco de investir tempo em algo que talvez não fosse melhorar a performance no nível que precisávamos.

Então como garantir que a nova solução seria mais performática que a anterior? Respondendo a esse questionamento decidimos utilizar o módulo Benchmark da Linguagem Ruby. Este módulo possui métodos que nos permitem mensurar e comparar o tempo gasto para executar códigos Ruby.

Ao final da execução de cada código o módulo retorna um relatório com o tempo de CPU gasto pelo usuário e pelo sistema, o tempo total de CPU que é a soma desses dois e o tempo real gasto, todos em segundos.

O tempo real é referente a todo o período de execução do processo, desde a inicialização até sua finalização, incluindo momentos em que o processo ficou parado por interrupções de entrada/saída, por exemplo. Logo ele não deve ser considerado em benchmarkings.

Criamos uma POC (Proof of Concept) usando o módulo Benchmark para medir e comparar o tempo de execução do código já existente com o tempo gasto pela nova versão da tarefa de atualização dos dados.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require 'benchmark'
class Updater
...
  def update
    ...
  end
...
end
class NewUpdater
  def update
    ...
  end
...
end
...
Benchmark.bm do |x|
  x.report("New Updater") { NewUpdater.new.load(events);nil }
  x.report("Actual Updater") { Updater.new.load(events);nil }
end

O resultado apresentado pela POC acima foi este:

                                  user     system      total        real
               Actual Loader:   1.533333   0.016667   1.550000 (  1.503462)
               New Loader:      1.050000   0.000000   1.050000 (  0.735473)

O tempo total de execução caiu de 1.55 segundos para 1.05 segundos, o que representa uma redução em mais de 30% no tempo gasto para atualização dos dados. Com essa informação ficamos confiantes para implementar a solução proposta, que ao final trouxe uma redução de 6 horas no tempo de transformação dos dados para apenas 2 horas.

Conclusão

Através do emprego de ferramentas simples e fáceis de utilizar, como NewRelic e Benchmark, exercemos valores Data-driven e Lean que resultam em melhorias significativas à performance da nossa aplicação.

Vale ressaltar que esses valores devem ser aplicados não apenas quando nos deparamos com um problema, mas também de forma pró ativa a fim de evitar possíveis incidentes. Aqui na Resultados Digitais cada time possui um dashboard que contém as principais métricas relacionadas às soluções pelas quais são responsáveis.

Entretanto só monitorar não é suficiente, identificado um problema devemos estudar a melhor solução para resolvê-lo e aplicar análises quantitativas com o objetivo de validar se realmente estamos tomando a decisão mais assertiva.

Me despeço por aqui, mas gostaria muito de ouvir outras histórias, bem sucedidas ou não, sobre melhoria de performance. Afinal de contas nós aprendemos muito com nossos erros.

Você já passou por alguma situação semelhante, usa alguma ferramenta para monitorar a performance de seus processos? Compartilhe comigo através dos comentários abaixo!

Referências

Quer ter acesso exclusivo a todos os materiais que utilizamos para acompanhar nossa máquina de produto?

Karla Garcia

Karla Garcia

Developer

Comentários