<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://engineering.axur.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://engineering.axur.com/" rel="alternate" type="text/html" /><updated>2025-11-17T18:22:15+00:00</updated><id>https://engineering.axur.com/feed.xml</id><title type="html">Axur Engineering blog</title><subtitle>Nossa missão é tornar a internet um lugar mais seguro. Desenvolvemos tecnologia para encontrar e eliminar riscos digitais, preservando a relação de confiança entre nossos clientes e seus públicos.</subtitle><author><name>Axur</name></author><entry xml:lang="en"><title type="html">Best Practices for AI-Assisted Coding</title><link href="https://engineering.axur.com/2025/05/09/best-practices-for-ai-assisted-coding.html" rel="alternate" type="text/html" title="Best Practices for AI-Assisted Coding" /><published>2025-05-09T11:00:00+00:00</published><updated>2025-05-09T11:00:00+00:00</updated><id>https://engineering.axur.com/2025/05/09/best-practices-for-ai-assisted-coding</id><content type="html" xml:base="https://engineering.axur.com/2025/05/09/best-practices-for-ai-assisted-coding.html"><![CDATA[<p>At Axur, we’re always looking for new ways to improve the productivity and efficiency of our development team. With AI quickly changing the landscape of software engineering, it can sometimes be hard to separate genuine breakthroughs from mere hype. By experimenting extensively with AI coding tools like Cursor, Junie, and Windsurf, we’ve identified several practical strategies that consistently deliver good results.</p>

<p>This post covers some best practices we found particularly valuable when using AI to assist with coding tasks:</p>

<h1 id="choose-the-right-tasks-for-ai">Choose the right tasks for AI</h1>

<p>While AI is becoming very good at coding tasks, developers should remain firmly in control of designing complex interactions and software architecture. Good architecture demands deep domain knowledge, strategic planning, and long-term thinking—areas where AI currently struggles. The most effective strategy is for developers to define the overall architecture and then delegate specific, well-defined tasks to AI assistants. This approach ensures the system remains robust, scalable, and aligned with your organization’s goals.</p>

<h1 id="provide-clear-task-definitions">Provide clear task definitions</h1>

<p>When assigning tasks to an AI assistant, clarity is critical. Large language models lack the broader context and domain knowledge that developers have, so defining clear expectations greatly enhances the quality of the output. Whenever possible, you should include relevant context and examples of input and expected outputs so that the agent has a better chance of grasping the nuances of the task.</p>

<p>For example, instead of broadly stating “Write a sorting function”, specify the exact input data types, expected sorting order, corner cases, and provide representative examples. We found that such clarity significantly reduces misunderstandings and iterations.</p>

<h1 id="implement-guardrails">Implement guardrails</h1>

<p>An effective way to ensure AI-generated code remains focused and on-point is by establishing clear boundaries. Techniques like unit testing, explicit interface definitions, and strong typing help keep the AI from drifting off-task or generating unrelated code.</p>

<p>Consider unit tests as the ultimate guardrail: they specify exactly what the generated code should accomplish and provide objective feedback about correctness. Type definitions and interfaces also help, by ensuring that the AI adheres strictly to the desired standard, making integration smoother and more predictable.</p>

<h1 id="apply-test-driven-development">Apply Test-Driven Development</h1>

<p>Among all the practices we’ve tested, making the AI agent use TDD has shown the most interesting results. Many AI tools now include some setting to allow the agent to autonomously execute commands. By allowing the agent to run build and test commands for the project, we found that it was capable of fixing its own mistakes and iterate on automated feedback until it comes up with a stable result. Here is the approach we found especially effective:</p>

<ul>
  <li><strong>Step 1: Ask the AI to write tests first</strong></li>
</ul>

<p>Explicitly prompt the AI to generate unit tests before implementing the feature. Ask it to make sure the tests are failing.</p>

<ul>
  <li><strong>Step 2: Implement the feature</strong></li>
</ul>

<p>Only after validating the tests and ensuring they are appropriately failing the agent should try to implement the requested feature</p>

<ul>
  <li><strong>Step 3: Iteratively run tests</strong></li>
</ul>

<p>After implementing the feature, run tests again to confirm they are all passing. Repeat these steps if necessary.</p>

<h1 id="use-smaller-prompts-and-incremental-complexity">Use smaller prompts and incremental complexity</h1>

<p>A common pitfall when using AI assistants is providing overly complex prompts right from the start. We have observed that breaking down tasks into smaller, isolated chunks yield better results. This approach allows the AI to focus, producing cleaner and more maintainable solutions.</p>

<p>If the AI assistant takes too long or struggles with a complex prompt, it’s usually better to stop it and reframe the problem. We also noticed that multiple failed attempts to build complex functionality tend to generate lots of unused code that does not get deleted by the agent. Building complexity incrementally, instead of expecting the AI to solve everything at once, provides more predictable results and reduces frustration.</p>

<h1 id="define-clear-abstraction-layers">Define clear abstraction layers</h1>

<p>Avoid asking the AI agent to handle multiple abstraction layers simultaneously - for example, implementing a UI integrated with backend in a single prompt. Instead, clearly separate these concerns. Define interfaces and types, then allow the agent to focus on UI elements and backend integration independently.</p>

<p>Explicitly defining these abstraction layers not only helps AI assistants deliver cleaner and more modular code but also aligns with good engineering principles, enhancing overall maintainability and clarity.</p>

<h1 id="maintain-a-short-context-window">Maintain a short context window</h1>

<p>AI coding assistants work with a limited context window, meaning they remember only a certain number of previous interactions. Long conversations or complex sessions tend to lead to decreased accuracy and unintended results. To avoid this:</p>

<ul>
  <li>Break down tasks into smaller units</li>
  <li>Regularly “close” completed blocks of functionality</li>
  <li>Start fresh sessions for new tasks or substantial changes in direction</li>
</ul>

<p>This ensures the assistant remains accurate, focused on the immediate task, and effective.</p>

<h1 id="use-styleguides-and-custom-rules">Use styleguides and custom rules</h1>

<p>While consistency in coding style is vital, be cautious about providing excessively detailed custom style rules, as they can consume valuable context window space. A simple, concise set of guidelines provided early can significantly enhance the consistency and readability. Always review generated code to ensure it aligns with your style standards.</p>

<h1 id="conduct-thorough-code-review">Conduct thorough code review</h1>

<p>Remember: as engineers, we remain responsible for all code committed to the repository. AI assistants are powerful tools, but they are far from infallible. Thoroughly review AI-generated code, maintain full accountability, and only commit code that you personally understand and trust. Integrating AI into your workflow means enhancing, not replacing, rigorous quality checks and thoughtful reviews.</p>

<h1 id="conclusion">Conclusion</h1>

<p>The rapid evolution of AI tools in software development presents many opportunities for productivity gains. However, it requires disciplined practices to realize these benefits fully. Interestingly, many strategies that enhance AI-assisted coding (such as breaking down complexity, using clear abstractions, and employing automated testing) are the same long-lasting principles derived from decades of traditional software engineering.</p>

<p>While AI-assisted development can accelerate the pace at which new software is delivered, it’s essential to remember that short-term speed isn’t the only measure for successful software development. In this context, developers play a critical role in ensuring that code is secure, reliable, and maintainable in the long run.</p>

<p>As AI continues to mature, exciting new technologies like the Model Context Protocol (MCP) are emerging, promising even richer developer interactions. We are currently exploring MCP and other standards, and we look forward to sharing our insights in future posts.</p>]]></content><author><name>Guilherme Alles</name></author><summary type="html"><![CDATA[At Axur, we’re always looking for new ways to improve the productivity and efficiency of our development team. With AI quickly changing the landscape of software engineering, it can sometimes be hard to separate genuine breakthroughs from mere hype. By experimenting extensively with AI coding tools like Cursor, Junie, and Windsurf, we’ve identified several practical strategies that consistently deliver good results.]]></summary></entry><entry xml:lang="pt"><title type="html">Práticas de engenharia de software na Axur</title><link href="https://engineering.axur.com/2025/03/25/praticas-de-engenharia-de-software-na-axur.html" rel="alternate" type="text/html" title="Práticas de engenharia de software na Axur" /><published>2025-03-25T11:00:00+00:00</published><updated>2025-03-25T11:00:00+00:00</updated><id>https://engineering.axur.com/2025/03/25/praticas-de-engenharia-de-software-na-axur</id><content type="html" xml:base="https://engineering.axur.com/2025/03/25/praticas-de-engenharia-de-software-na-axur.html"><![CDATA[<p>Na Axur, tornamos a internet um lugar mais seguro com nossas soluções de cibersegurança. Nosso time de Engineering trabalha para fornecer a tecnologia necessária para monitorar e reagir às ameaças em uma internet cada vez mais dinâmica. Como cada ameaça detectada representa uma situação sensível para os nossos clientes, precisamos ser capazes de entregar software de qualidade o mais rápido o possível. Com base nessa necessidade, construímos uma cultura forte de engenharia de software com diversas práticas que vamos compartilhar com vocês, começando pela maneira que projetamos nosso software.</p>

<h1 id="domain-driven-design">Domain-Driven Design</h1>

<p>Existem diversas maneiras de gerenciar a complexidade do software, e aqui na Axur utilizamos uma abordagem chamada de <em>Domain-Driven Design</em> (DDD) para encarar esse desafio. O <em>Domain-Driven Design</em> foi introduzido em 2003 em uma obra de mesmo nome escrita por Eric Evans. Outro livro que esclarece bem os conceitos dessa abordagem é <em>Implementando Domain-Driven Design</em>, de Vaughn Vernon, que apresenta técnicas de DDD na prática. A ideia central desse design é colocar o domínio (os conceitos e as regras de negócio) no centro do desenvolvimento do software, utilizando uma linguagem ubíqua que é compartilhada entre os desenvolvedores de software e os demais especialistas de domínio, como analistas que transformam as necessidades dos clientes em novas funcionalidades e operadores que realizam uma série de ações necessárias para funcionamento do negócio.</p>

<p>Ao aplicar o DDD nossos componentes de software são projetados em camadas aninhadas, onde as camadas mais internas não devem acessar as camadas mais externas. Utilizamos três camadas:</p>

<ol>
  <li>
    <p>Infraestrutura: Fornece as implementações técnicas necessárias, como comunicação com banco de dados, as declarações de APIs HTTP, entre outros.</p>
  </li>
  <li>
    <p>Aplicação: Coordena tarefas de caso de uso, gerencia transações e define as autorizações de segurança necessárias.</p>
  </li>
  <li>
    <p>Modelo (Domínio): O domínio é o coração da aplicação. Portanto, tanto as regras de negócio quanto os objetos de domínio que modelam conceitos da área de atuação do software constam nessa camada.</p>
  </li>
</ol>

<p><img src="/assets/2025-03-25-praticas-de-engenharia-de-software-na-axur/ddd-layers.png" alt="Camadas do DDD" /></p>

<p>No entanto, o DDD, por si só, não contém todas as respostas para a qualidade de código que almejamos. Por isso também valorizamos outras práticas como o código limpo (<em>clean code</em>).</p>

<h1 id="código-limpo">Código Limpo</h1>

<p>Podemos considerar que o código limpo refere-se às qualidades apresentadas por um software de fácil leitura, compreensão e manutenção. O termo “código limpo” foi popularizado por uma obra de mesmo nome, escrita pelo renomado engenheiro de software e autor Robert C. Martin (coloquialmente conhecido como “Uncle Bob”). Aqui na Axur, acreditamos plenamente que, ao aplicar os ensinamentos descritos nesse livro, desenvolvemos um código de excelência.</p>

<p>Seguimos normas como a utilização de nomes expressivos, reuso de código, tratamos erros com exceções descritivas e escrevemos classes pequenas que seguem o princípio de responsabilidade única, entre outras práticas. Além disso, buscamos sempre garantir que o próprio código expresse suas intenções com clareza, reduzindo assim a necessidade de comentários explicativos sobre a lógica implementada.</p>

<p>Portanto, há diversas recomendações concretas que seguimos ao desenvolver o nosso software para torná-lo mais limpo. Entretanto, não existe uma solução exata para todos os cenários. O título original em inglês do livro citado do Uncle Bob é <em>Clean Code: A Handbook of Agile Software Craftsmanship</em>. Essa palavra final, <em>craftsmanship</em>, não tem um paralelo exato na língua portuguesa, mas o dicionário Oxford a define como “o nível de habilidade demonstrado por alguém ao fazer algo bonito com as mãos”. Aqui na Axur, valorizamos o fato de sermos os artesãos do nosso software, e sempre colocamos um cuidado adicional na escrita de cada linha de código. Além do código limpo, também queremos técnicas que nos ajudem a garantir o comportamento esperado do nosso software, o que nos leva ao nosso próximo tópico.</p>

<h1 id="desenvolvimento-orientado-a-testes-tdd">Desenvolvimento Orientado a Testes (TDD)</h1>

<p>O Desenvolvimento Orientado a Testes é uma técnica na qual o desenvolvimento do software é guiado pelo processo de escrita dos testes. Popularmente conhecida como TDD (<em>Test Driven Development</em>), essa abordagem foi concebida por Kent Beck como um dos pilares da metodologia de desenvolvimento de software ágil conhecida como Extreme Programming. O ciclo de desenvolvimento com TDD consiste em três etapas conhecidas como as etapas Vermelha, Verde e Refatoração.</p>

<ol>
  <li>
    <p>Vermelha: O processo inicia-se com a criação dos testes unitários, antes mesmo do desenvolvimento da funcionalidade em si. Por isso que essa etapa é chamada de vermelha (cor que representa a falha dos testes na maioria das ferramentas). Dessa forma pode-se observar os cenários básicos falharem, tornando possível ver a transformação do estado errôneo para o estado correto após a implementação do código.</p>
  </li>
  <li>
    <p>Verde: Nessa etapa, o código da funcionalidade é desenvolvido. Não é necessariamente o código da maior qualidade, mas sim o código suficiente para passar nos testes.</p>
  </li>
  <li>
    <p>Refatoração: Por fim, é essencial realizar a refatoração do código, garantindo que os testes unitários continuem passando.</p>
  </li>
</ol>

<p>O bom uso do TDD aumenta tanto a qualidade externa quanto a interna do software. O processo de desenvolvimento já inicia com a elaboração formal em código de todos os cenários possíveis, evitando o erro comum de considerar apenas o fluxo de quando tudo ocorre conforme o esperado. Dessa forma, criamos uma aplicação com alta cobertura de testes, minimizando a chance de a funcionalidade apresentar comportamentos inesperados. Também garantimos que eventuais futuras introduções de bugs sejam detectadas na execução desses testes.</p>

<p>Uma regra de ouro é que um bom design de código é aquele fácil de se testar. Ao iniciar o desenvolvimento pelos testes, decisões de design, como quais dependências devem ser injetadas, são pensadas para facilitar o teste, o que tende a melhorar outras características do projeto como legibilidade, reuso, baixo acoplamento, alta manutenibilidade e coesão.</p>

<p>Discutimos bastante sobre o que fazemos para aumentar a qualidade do nosso software, mas, além de desenvolver software de qualidade, também precisamos adotar as melhores práticas para disponibilizar esse software para os nossos clientes.</p>

<h1 id="devops">DevOps</h1>

<p>O termo DevOps é uma combinação das palavras <em>Development</em> (Desenvolvimento) e <em>Operations</em> (Operações) representando a união de pessoas, tecnologias e processos para aumentar a capacidade de uma organização de entregar software de maneira rápida e confiável.</p>

<p>O time de Engineering da Axur é responsável não apenas por desenvolver o software, mas também por gerenciar os recursos de infraestrutura e realizar o deployment das soluções no ambiente de produção. A seguir, explicaremos algumas das melhores práticas de DevOps que adotamos aqui na Axur.</p>

<h1 id="integração-contínua">Integração Contínua</h1>

<p>Trabalhamos com uma arquitetura de microsserviços, que são componentes de software autônomos com uma única responsabilidade. Ao realizar uma alteração no repositório de um dos nossos microsserviços, um webhook é acionado e o nosso deployment pipeline inicia automaticamente.</p>

<p>O deployment pipeline executa uma série de etapas, como a compilação, a geração de artefatos executáveis, a execução de testes automatizados, análise estática (que pode identificar bugs, code smells e vulnerabilidades no código fonte) e a instalação do software em um ambiente de testes. Todo o processo ocorre de forma autônoma e sem nenhum downtime. Para prosseguir com a instalação no ambiente de produção, basta uma aprovação manual com um único clique.</p>

<h1 id="entrega-contínua">Entrega Contínua</h1>

<p>Algumas organizações realizam o processo de deployment em frequências pré-determinadas, como apenas uma vez por semana, por exemplo. Aqui na Axur, seguimos uma prática de DevOps chamada de entrega contínua, na qual realizamos dezenas de deployments por dia, implementando uma série de modificações pequenas e incrementais.</p>

<p>A adoção da entrega contínua nos traz diversos benefícios, sendo o maior deles a redução do tempo entre o commit e o deployment em produção. Além disso, o processo de fragmentar as mudanças de software em ciclos menores nos obriga a testar as mudanças com mais frequência e de maneira mais isolada, aumentando nossas chances de detectar eventuais comportamentos indesejados.</p>

<p>A combinação dessas duas práticas é conhecida na indústria pelo acrônimo CI/CD do inglês <em>Continuous Integration / Continuous Delivery</em>.</p>

<h1 id="desenvolvimento-baseado-em-tronco">Desenvolvimento Baseado em Tronco</h1>

<p>O Desenvolvimento Baseado em Tronco, também conhecido pela sigla <em>Trunk Based Development</em> (TBD), é uma maneira de gerenciar o versionamento do software, em que todos os desenvolvedores trabalham em uma única versão do projeto.</p>

<p>A palavra “tronco” faz alusão a uma forma de visualizar o versionamento do projeto como uma árvore contendo apenas um único e contínuo tronco, em contraste com a prática de ramificar o código em diversas versões simultâneas, formando uma estrutura análoga a uma árvore com longos galhos, conhecidos como branches.</p>

<p>Na Axur adotamos o TBD e, por isso, não utilizamos <em>pull requests</em> para integrar diferentes versões de código. Essa abordagem proporciona diversos benefícios, como uma integração mais fluida à prática de entrega contínua, possibilitando entregas mais rápidas de software. Outro benefício importante é a redução da perda de produtividade decorrente de <em>merges</em> complexos, que frequentemente geram conflitos e retrabalho.</p>

<h1 id="infraestrutura-como-código">Infraestrutura como código</h1>

<p>Por fim, todo o software precisa ser executado em máquinas físicas. Em vez de utilizar interfaces interativas, preferimos gerenciar a nossa infraestrutura com código, prática conhecida como <em>Infrastructure as Code</em> (IaC).</p>

<p>Recursos como banco de dados, armazenamentos de arquivos e servidores são declarados como código em templates declarativos. Essa abordagem nos oferece diversas vantagens como a redução da chance de introduzirmos erros humanos, a padronização da infraestrutura (tornando-a facilmente replicável em outros ambientes), controle de versionamento e maior agilidade no processo de configuração, o que torna todo o processo de deployment mais rápido.</p>

<h1 id="conclusão">Conclusão</h1>

<p>Todas essas práticas ajudam a aumentar a capacidade da Axur de reagir cada vez mais rápido às ameaças da internet, além de tornar o trabalho de desenvolvimento de software muito mais satisfatório.</p>]]></content><author><name>Bruno Toresan</name></author><summary type="html"><![CDATA[Na Axur, tornamos a internet um lugar mais seguro com nossas soluções de cibersegurança. Nosso time de Engineering trabalha para fornecer a tecnologia necessária para monitorar e reagir às ameaças em uma internet cada vez mais dinâmica. Como cada ameaça detectada representa uma situação sensível para os nossos clientes, precisamos ser capazes de entregar software de qualidade o mais rápido o possível. Com base nessa necessidade, construímos uma cultura forte de engenharia de software com diversas práticas que vamos compartilhar com vocês, começando pela maneira que projetamos nosso software.]]></summary></entry><entry xml:lang="pt"><title type="html">Microfrontends independentes através de eventos</title><link href="https://engineering.axur.com/2024/02/01/microfrontends-independentes-atraves-de-eventos.html" rel="alternate" type="text/html" title="Microfrontends independentes através de eventos" /><published>2024-02-01T03:50:00+00:00</published><updated>2024-02-01T03:50:00+00:00</updated><id>https://engineering.axur.com/2024/02/01/microfrontends-independentes-atraves-de-eventos</id><content type="html" xml:base="https://engineering.axur.com/2024/02/01/microfrontends-independentes-atraves-de-eventos.html"><![CDATA[<p>No universo do desenvolvimento de aplicações web, enfrentamos desafios crescentes em relação à complexidade das aplicações. Especificamente no contexto de frontend, não é suficiente que uma aplicação seja visualmente agradável e intuitiva; a aplicação precisa encapsular e interpretar uma grande quantidade de conceitos de negócio, e apresentá-los a usuários de forma competente.</p>

<p>Esses conceitos de negócio, muitas vezes, estão interligados de formas complexas e funcionam de maneira intrincada. Por outro lado, é responsabilidade do time de desenvolvimento de software controlar a complexidade das bases de código, para que elas não se transformem em um obstáculo para o desenvolvimento ágil e eficiente. Nesse cenário, uma das missões do time de Engenharia da Axur é justamente gerenciar a complexidade das nossas bases de código mantendo a qualidade técnica do software desenvolvido.</p>

<h1 id="microfrontends-independentes"><em>Microfrontends</em> independentes</h1>

<p>Uma das respostas para lidar com a complexidade crescente é fragmentar a aplicação em pedaços menores, independentes e coesos. Chamaremos esses pedaços de <em>microfrontends</em>. A ideia principal é criar <em>microfrontends</em> como unidades independentes de funcionalidade, que interagem entre si para construir a experiência do usuário final. Dessa forma, times de desenvolvimento podem distribuir a responsabilidade e trabalhar simultaneamente em diferentes áreas de uma mesma aplicação, sem interferir uns nos outros.</p>

<p>Mesmo nesse cenário, é improvável que todos os casos de uso de uma aplicação possam ser atendidos por um único <em>microfrontend</em>. Eventualmente, pode ser necessário que dois subsistemas distintos compartilhem informações. Desse ponto, surge um novo desafio: como esses <em>microfrontends</em>, que atuam como ilhas de funcionalidade, comunicam-se entre si?</p>

<p>Imagine um componente de carrinho de compras, responsável por armazenar temporariamente todos os produtos de uma sessão do usuário. Imagine também a página de um produto específico. É natural imaginar que esses casos de uso sejam implementados em <em>microfrontends</em> distintos e independentes. E se, nesse cenário, o carrinho de compras precisar ser atualizado (ou exibir uma animação) quando o usuário clicar no botão de “comprar” em uma página de produto? Como o carrinho de compras pode ser informado do novo produto recém selecionado?</p>

<h1 id="emissão-de-eventos">Emissão de eventos</h1>

<p>Uma solução possível é estabelecer um canal de comunicação que não requer acoplamento direto entre dois <em>microfrontends</em>. Podemos fazer isso através de uma solução de mensageria compartilhada. Neste modelo, permitimos que <em>microfrontends</em> que compartilham o mesmo ambiente de execução possam trocar mensagens entre si.</p>

<p>Essencialmente, definimos dois tipos de mensagens, baseados no padrão de <a href="https://en.wikipedia.org/wiki/Event-driven_architecture">arquiteturas orientadas a eventos</a>:</p>

<ol>
  <li><strong>Comandos:</strong> São enviados de qualquer remetente para um destinatário específico. Comandos indicam claramente a ação que deve ser realizada pelo destinatário, e são escritos usando a linguagem de domínio do destinatário. A título de exemplo, uma solicitação de mudança de rota pode ser tratada como um comando.</li>
  <li><strong>Eventos:</strong> Diferentemente de comandos, eventos são emitidos por um remetente específico e podem ser capturados por quaisquer destinatários interessados. Eventos indicam acontecimentos relevantes, e são escritos em termos do domínio do remetente. Isso garante que o remetente não precise conhecer detalhes de implementação dos destinatários. A título de exemplo, um evento pode ser disparado quando o resultado de uma lista é renderizado na tela ou quando um usuário realiza login com sucesso.</li>
</ol>

<h1 id="consumo-de-eventos">Consumo de eventos</h1>

<p>O consumo de mensagens é feito por handlers, que podem ser registrados em um <em>microfrontend</em> para que sejam invocados sempre que determinado evento for disparado na aplicação. Cada <em>microfrontend</em> é responsável por declarar e inicializar os seus próprios handlers, preferencialmente em um ponto centralizado da aplicação. Isso permite que os eventos aceitos por cada <em>microfrontend</em> sejam facilmente identificados por um leitor externo.</p>

<h1 id="mensageria-usando-a-dom">Mensageria usando a DOM</h1>

<p>A estratégia de comunicação por mensagens pode ser implementada de inúmeras formas, seja através de uma implementação própria ou com o auxílio de uma biblioteca específica. A DOM (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model">Document Object Model</a>) é uma API presente em todos os browsers e que fornece interfaces para o tratamento de eventos personalizados (<a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent">Custom Events</a>). Esse ferramental pode ser utilizado para criar uma solução simples de mensageria, disparando mensagens como Custom Events e registrando event handlers para tratá-los na aplicação.</p>

<p>A título de exemplo, o código TypeScript abaixo utiliza APIs da DOM para enviar um evento de login para a aplicação. O evento enviado contém também o ID e o nome do usuário logado:</p>

<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">const</span> <span class="nx">notifyLogin</span> <span class="o">=</span> <span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">loginEvent</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CustomEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">event.login.success</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">detail</span><span class="p">:</span> <span class="p">{</span> <span class="na">user</span><span class="p">:</span> <span class="p">{</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span> <span class="p">}</span> <span class="p">}</span>
  <span class="p">});</span>

  <span class="nb">document</span><span class="p">.</span><span class="nx">dispatchEvent</span><span class="p">(</span><span class="nx">loginEvent</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>Em outro ponto da aplicação, um <em>microfrontend</em> interessado no evento de login pode registrar um handler, tratando o evento conforme necessário:</p>

<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">const</span> <span class="nx">loginHandler</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="nx">CustomEvent</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
  <span class="nx">alert</span><span class="p">(</span><span class="s2">`User </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2"> (id </span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">) just logged in!`</span><span class="p">);</span>
<span class="p">}</span>

<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">event.login.success</span><span class="dl">'</span><span class="p">,</span> <span class="nx">loginHandler</span><span class="p">);</span></code></pre></figure>

<p>A partir desse ponto, o <code class="language-plaintext highlighter-rouge">loginHandler</code> será invocado sempre que um evento de tipo <code class="language-plaintext highlighter-rouge">'event.login.success'</code> chegar ao objeto <code class="language-plaintext highlighter-rouge">document</code>. O envio de um evento de login pode ser feito chamando a função <code class="language-plaintext highlighter-rouge">notifyLogin</code> definida anteriormente:</p>

<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nx">notifyLogin</span><span class="p">(</span><span class="dl">'</span><span class="s1">123</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">);</span></code></pre></figure>

<p>Na implementação acima, é importante notar que os dois módulos (produtor e consumidor) são completamente independentes. O acoplamento entre produtor e consumidor de eventos se dá apenas pelo contrato entre os dois módulos, que pode ser interpretado como uma API definida pelo emissor. Essa API define o tipo do evento e o formato do <em>payload</em>, mas não faz nenhuma suposição quanto à existência de consumidores. Da mesma forma, consumidores de eventos simplesmente respeitam o contrato definido sem fazer nenhuma suposição quanto à existência de um emissor. Essa característica garante o baixo acoplamento entre os dois módulos.</p>

<p>Vale notar também que essa implementação é apenas um exemplo de como atingir o baixo acoplamento, utilizando APIs amplamente disponíveis em navegadores. Como já mencionado, existem diferentes formas de implementar a comunicação através de mensagens, e até mesmo bibliotecas que disponibilizam um ferramental similar para lidar com produtores e consumidores.</p>

<h1 id="conclusão">Conclusão</h1>

<p>Com a comunicação baseada em eventos, equipes de software podem desenvolver, testar e implementar novas funcionalidades de forma mais ágil. A emissão de eventos permite a criação de componentes independentes, de forma que múltiplas equipes trabalhem simultaneamente em diferentes partes da aplicação sem interferir umas nas outras. Da mesma forma, essa estratégia de comunicação promove o desacoplamento ao implementar uma nova interface entre dois subsistemas independentes. Por fim, ao evitar o acoplamento direto, erros ou falhas em um subsistema (no nosso caso, em um <em>microfrontend</em>) deixam de afetar diretamente o comportamento de outros, garantindo uma experiência mais estável a usuários.</p>

<p>Em aplicações complexas, o desacoplamento de conceitos é vital para garantir escalabilidade e flexibilidade. Nesse artigo, exploramos uma estratégia de mensageria como forma de comunicação entre <em>microfrontends</em>, permitindo que ilhas de funcionalidades isoladas possam se comunicar sem a necessidade de criar dependências rígidas entre elas. Adotando esse padrão, equipes de desenvolvimento podem se beneficiar de um processo de trabalho mais ágil e independente, além de uma arquitetura mais limpa e uma experiência mais robusta para o usuário final.</p>]]></content><author><name>Guilherme Rezende Alles</name></author><summary type="html"><![CDATA[No universo do desenvolvimento de aplicações web, enfrentamos desafios crescentes em relação à complexidade das aplicações. Especificamente no contexto de frontend, não é suficiente que uma aplicação seja visualmente agradável e intuitiva; a aplicação precisa encapsular e interpretar uma grande quantidade de conceitos de negócio, e apresentá-los a usuários de forma competente.]]></summary></entry><entry xml:lang="pt"><title type="html">Transformação de schemas relacionais sem downtime</title><link href="https://engineering.axur.com/2021/08/10/schemas-relacionais-sem-downtime.html" rel="alternate" type="text/html" title="Transformação de schemas relacionais sem downtime" /><published>2021-08-10T18:00:00+00:00</published><updated>2021-08-10T18:00:00+00:00</updated><id>https://engineering.axur.com/2021/08/10/schemas-relacionais-sem-downtime</id><content type="html" xml:base="https://engineering.axur.com/2021/08/10/schemas-relacionais-sem-downtime.html"><![CDATA[<p>Como explicamos neste <a href="https://engineering.axur.com/2020/07/08/pilares-tecnicos.html">artigo</a>, uma de nossas práticas para maximizar a entrega de software é a transformação de deployments em eventos triviais, que acontecem a qualquer hora, muitas vezes por dia. Pensando nisso, não podemos aceitar que a entrega de novas funcionalidades e melhorias cause possíveis downtime, com interrupção de serviço que impacte os usuários. Nesse contexto, um problema bem específico se manifesta: como podemos aplicar mudanças nas estruturas de tabelas já existentes de bancos de dados relacionais, mantendo os microsserviços que dependem deles em funcionamento durante todo o processo? Neste artigo, vamos apresentar uma solução possível a partir de uma necessidade real: a mudança de charset de uma tabela para suportar caracteres especiais.</p>

<p>O jeito mais comum e simples de fazer essa mudança é usando o comando do MySQL para alterar o charset da tabela (<a href="https://dev.mysql.com/doc/refman/5.6/en/alter-table.html">ALTER TABLE</a> table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci), porém este comando impossibilita a leitura e escrita na tabela que está sofrendo alteração. No nosso cenário, a tabela contém 60 milhões de registros e foi estimado que levaria de 2 a 3 horas para concluir a execução do comando. Era inviável deixar o microsserviço parado por 3 horas e impactar todos os usuários da nossa plataforma, então decidimos seguir outra abordagem para que não houvesse downtime no microsserviço.</p>

<p>Nesta outra abordagem decidimos não usar o comando de alteração. Optamos por recriar a tabela, desta vez com o charset correto. Também tivemos que replicar toda a estrutura para manter a coerência entre as constraints das tabelas.
Outro ponto que tivemos que repensar foi como seria feita a migração dos dados que estavam na tabela antiga. Se simplesmente fossem migrados todos os dados antes do microsserviço apontar para a nova tabela, o problema de downtime não seria resolvido, porque teríamos que parar o microsserviço, fazer a migração e depois colocar a nova versão que aponta para a nova tabela. Com isso chegamos à solução final, na qual decidimos realizar a migração de dados adicionando uma lógica no código para acessar os dados antigos e os novos. Abaixo a organização da implementação desta solução</p>

<p><img src="/assets/2021-08-10-schemas-relacionais-sem-downtime/migration.png" alt="Fluxo migração" /></p>

<p>Portanto os novos registros eram salvos na tabela nova, e os registros na tabela antiga eram migrados à medida que os dados eram consultados. Para facilitar o entendimento, segue um snippet do código da classe (migrationClass) que contém as lógicas para inserção, consulta e atualização.</p>

<ul>
  <li>Todos os registros novos, são inseridos na tabela novas.</li>
</ul>

<figure class="highlight"><pre><code class="language-java" data-lang="java">   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">add</span><span class="o">(</span><span class="nc">Ticket</span> <span class="n">ticket</span><span class="o">)</span> <span class="o">{</span>
      <span class="n">ticketRepositoryNew</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">ticket</span><span class="o">);</span>
   <span class="o">}</span>
   </code></pre></figure>

<ul>
  <li>A consulta aos registros foi alterada. Além de ler os dados, também é realizada a migração dos registros para a tabela nova, caso eles se encontrem na tabela antiga. Depois de salvo na nova tabela, o registro é excluído da antiga.</li>
</ul>

<figure class="highlight"><pre><code class="language-java" data-lang="java">   <span class="kd">public</span> <span class="nc">Ticket</span> <span class="nf">find</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
      <span class="k">try</span> <span class="o">{</span>
         <span class="nc">Ticket</span> <span class="n">ticketOld</span> <span class="o">=</span> <span class="n">ticketRepository</span><span class="o">.</span><span class="na">find</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
         <span class="n">migrationTicket</span><span class="o">(</span><span class="n">ticketOld</span><span class="o">)</span>
      <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">TicketNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
         <span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Ticket not found in old table: {}"</span><span class="o">,</span> <span class="n">id</span><span class="o">);</span>
      <span class="o">}</span>
   <span class="k">return</span> <span class="n">ticketRepositoryNew</span><span class="o">.</span><span class="na">find</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
   <span class="o">}</span>
   </code></pre></figure>

<ul>
  <li>A atualização de registros segue a lógica da consulta: caso o registro esteja na tabela antiga, primeiro ele é migrado para a tabela nova e depois sofre a atualização.</li>
</ul>

<figure class="highlight"><pre><code class="language-java" data-lang="java">   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">update</span><span class="o">(</span><span class="nc">Ticket</span> <span class="n">ticket</span><span class="o">)</span> <span class="o">{</span>
      <span class="k">try</span> <span class="o">{</span>
         <span class="nc">Ticket</span> <span class="n">ticketOld</span> <span class="o">=</span> <span class="n">ticketRepository</span><span class="o">.</span><span class="na">find</span><span class="o">(</span><span class="n">ticket</span><span class="o">.</span><span class="na">id</span><span class="o">());</span>
         <span class="n">migrationTicket</span><span class="o">(</span><span class="n">ticketOld</span><span class="o">)</span>
      <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">TicketNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
         <span class="n">logger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Ticket not found in old table: {}"</span><span class="o">,</span> <span class="n">id</span><span class="o">);</span>
      <span class="o">}</span>
   <span class="n">ticketRepositoryNew</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">ticket</span><span class="o">);</span>
   <span class="o">}</span>
   </code></pre></figure>

<ul>
  <li>Código da migração:</li>
</ul>

<figure class="highlight"><pre><code class="language-java" data-lang="java">   <span class="nc">Private</span> <span class="kt">void</span> <span class="nf">migrationTicket</span><span class="o">(</span><span class="nc">Ticket</span> <span class="n">ticketOld</span><span class="o">)</span> <span class="o">{</span>
      <span class="n">ticketRepositoryNew</span><span class="o">.</span><span class="na">insert</span><span class="o">(</span><span class="n">ticketOld</span><span class="o">);</span>
      <span class="n">ticketRepository</span><span class="o">.</span><span class="na">delete</span><span class="o">(</span><span class="n">ticketOld</span><span class="o">);</span>
   <span class="o">}</span>
   </code></pre></figure>

<p>Com essa solução foi possível suportar UTF-8 na tabela, sem qualquer downtime no serviço. Os dados foram migrados aos poucos, sem sobrecarregar o sistema, uma vez que a migração só ocorre ao consultar/atualizar alguma informação. Os dados que permaneceram na tabela antiga, por não terem sido consultados/atualizados no período dedicado a migração automática, foram migrados manualmente após algum tempo, sendo finalizada a migração e possibilitando a remoção da tabela antiga e do código de migração.</p>]]></content><author><name>Matheus Rodrigues da Silva</name></author><summary type="html"><![CDATA[Como explicamos neste artigo, uma de nossas práticas para maximizar a entrega de software é a transformação de deployments em eventos triviais, que acontecem a qualquer hora, muitas vezes por dia. Pensando nisso, não podemos aceitar que a entrega de novas funcionalidades e melhorias cause possíveis downtime, com interrupção de serviço que impacte os usuários. Nesse contexto, um problema bem específico se manifesta: como podemos aplicar mudanças nas estruturas de tabelas já existentes de bancos de dados relacionais, mantendo os microsserviços que dependem deles em funcionamento durante todo o processo? Neste artigo, vamos apresentar uma solução possível a partir de uma necessidade real: a mudança de charset de uma tabela para suportar caracteres especiais.]]></summary></entry><entry xml:lang="pt"><title type="html">Micro-benchmarks em Java</title><link href="https://engineering.axur.com/2021/05/27/micro-benchmarks.html" rel="alternate" type="text/html" title="Micro-benchmarks em Java" /><published>2021-05-27T13:13:00+00:00</published><updated>2021-05-27T13:13:00+00:00</updated><id>https://engineering.axur.com/2021/05/27/micro-benchmarks</id><content type="html" xml:base="https://engineering.axur.com/2021/05/27/micro-benchmarks.html"><![CDATA[<p>Durante a participação em um projeto, o engenheiro de <em>software</em> pode ter que passar pelo processo de escolha
de uma determinada biblioteca, <em>framework</em> ou componente dentre uma gama de possibilidades, baseado no
desempenho do componente. Pode ser necessário escolher qual a biblioteca de serialização de JSONs mais
rápida, qual a biblioteca de processamento de expressões regulares mais rápida, e assim por diante. Geralmente,
será possível encontrar na Internet <em>benchmarks</em> prontos e de qualidade para os tópicos mais quentes do mundo da
programação, contudo, para casos particulares, pode ser necessário que o próprio engenheiro tenha que executar
seus <em>benchmarks</em> para ajudar no processo de decisão. Neste artigo, abordaremos alguns aspectos do processo de
criação/execução de <em>micro-benchmark</em> em Java para o caso em que seja necessária a criação rápida de <em>benchmarks</em>.</p>

<h1 id="o-que-é-um-micro-benchmark">O que é um <em>micro-benchmark</em>?</h1>

<p>Um <em>micro-benchmark</em> é um <em>benchmark</em> rápido e leve, com o objetivo de testar rapidamente alguma ideia ou
conceito no código, podendo inclusive ser descartado após a obtenção dos resultados. Em geral, o enfoque
do <em>micro-benchmark</em> é a comparação entre duas ou mais opções. Isso o diferencia do <em>profiling</em>, que é a análise
do desempenho de um código com objetivo de otimização, incluindo possivelmente a análise de frequência e
duração das chamadas de métodos.</p>

<p>Um exemplo de <em>micro-benchmark</em> pode ser um <em>benchmark</em> que mostra qual componente é mais rápido em gerar
um número aleatório, comparando entre <code class="language-plaintext highlighter-rouge">java.util.Random</code> e <code class="language-plaintext highlighter-rouge">java.util.SplittableRandom</code>. É um <em>benchmark</em>
rápido que pode guiar as decisões de desenvolvimento de um componente mais complexo, e também é um assunto
que não é tão quente e pode não haver um <em>benchmark</em> pronto na Internet.</p>

<h1 id="anatomia-de-um-micro-benchmark">Anatomia de um <em>micro-benchmark</em></h1>

<p>O <em>micro-benchmark</em> mais simples é composto de um trecho de código a ser testado, e de uma rotina que chama
esse código cronometrando o tempo que o código testado leva para rodar. Contudo, os
<em>benchmarks</em> mais úteis são aqueles que comparam diversas abordagens de forma a auxiliar em uma tomada de
decisão. Assim, o <em>micro-benchmark</em> mais comum possui dois ou mais trechos de código a serem testados,
sendo um deles considerado o código padrão (chamado <em>baseline</em>) e as outras opções sendo variações ou opções
do mesmo código a fim de provar alguma hipótese ou conceito.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">microBenchmarkRandomNextInt</span><span class="o">()</span> <span class="o">{</span>
   <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
   <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">().</span><span class="na">nextInt</span><span class="o">();</span>
   <span class="kt">long</span> <span class="n">stopTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
   <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"Duration: %d%n"</span><span class="o">,</span> <span class="n">stopTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>

<p>O trecho de código acima é um exemplo de <em>micro-benchmark</em> bem simples que serve para cronometrar o tempo
necessário para <code class="language-plaintext highlighter-rouge">Random</code> retornar um número inteiro aleatório de 32 bits. Contudo, o trecho de código também
é um exemplo de um <em>micro-benchmark</em> de baixa qualidade, e veremos o motivo a seguir.</p>

<h1 id="armadilhas-em-micro-benchmarks-em-java">Armadilhas em <em>micro-benchmarks</em> em Java</h1>

<p>A máquina virtual Java (JVM) é muito boa em otimização de código, incluindo o uso do compilador <em>Just-In-Time</em> (JIT)
para geração de código de máquina a fim de acelerar os caminhos críticos de execução de código.
Além disso, a JVM também é muito boa em
esconder os detalhes de otimização de forma que os desenvolvedores não precisem se preocupar em
micro-gerenciamento dos pormenores de otimização. Contudo, esse gerenciamento de otimização pode ser
relevante para <em>benchmarks</em>, pois a natureza sintética do <em>benchmark</em> pode induzir a máquina virtual
a fazer otimizações inesperadas ou indesejáveis. A seguir, vamos enumerar algumas situações que podem gerar
um comportamento inesperado em <em>micro-benchmarks</em>.</p>

<p><strong>Remoção de variáveis e resultados que nunca são lidos</strong></p>

<p>A JVM possui uma implementação bem madura para detectar código inútil ou nunca utilizado,
e isso também vale para variáveis que são gravadas e nunca são lidas.
Nesse caso, a atribuição é um candidato a ser eliminado do caminho de execução.
Caso a atribuição tenha efeitos colaterais, por exemplo a chamada de uma rotina, o otimizador pode desmembrar
os efeitos colaterais e descartar a parte não usada pelo retorno da rotina. O código a seguir é um exemplo em
que a atribuição da variável <code class="language-plaintext highlighter-rouge">result</code> pode ser suprimida pois a variável nunca é lida.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">microBenchmarkRandom</span><span class="o">()</span> <span class="o">{</span>
   <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
   <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">().</span><span class="na">nextInt</span><span class="o">();</span>
   <span class="kt">long</span> <span class="n">stopTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
   <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"Duration: %d%n"</span><span class="o">,</span> <span class="n">stopTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>

<p><strong>Necessidade de aquecimento do JIT</strong></p>

<p>Em geral, a JVM executa o <em>bytecode</em> Java através de interpretação do <em>bytecode</em>, que é um processo muito mais
lento do que a execução de código de máquina. Contudo, a JVM também possui disponível um compilador JIT para
geração de código de máquina em tempo de execução. Apesar disso, nem todo código é transformado em
código de máquina, pois a geração de código de máquina é um processo custoso e que consome recursos
computacionais importantes. Em face disso, o otimizador analisa o perfil de execução de trechos de código
e escolhe, em tempo de execução, os códigos executados com maior frequência como candidatos para execução
através do JIT. Dessa forma, o código de um <em>micro-benchmark</em> precisa ser executado dezenas ou centenas de
vezes a fim de garantir que o otimizador prepare o código para ser executado através de JIT. As primeiras
execuções também precisam ser descartadas, pois representam a execução do código de maneira interpretada, e
podem causar um desvio estatístico nos tempos calculados.</p>

<p><strong>Remoção de gravações intermediárias para uma mesma variável</strong></p>

<p>Caso uma variável seja gravada diversas vezes, o otimizador pode suprimir as diversas gravações e manter
somente a última atribuição. Caso o valor da última atribuição dependa das gravações intermediárias, por
exemplo em um laço, o otimizador ainda pode tentar algumas otimizações para suprimir o laço, incluindo
calcular o valor final a frente e manter apenas a última atribuição, eliminando completamente o laço.
Este ponto é especialmente importante no caso de <em>micro-benchmarks</em> executados em um laço - que é um caso
comum em vista da necessidade de aquecimento do JIT.</p>

<p><strong>Granularidade da base de tempo</strong></p>

<p>Às vezes, pode ser necessário fazer o <em>benchmark</em> de um código que executa muito rápido, por exemplo
a comparação entre o acesso a um vetor usando <code class="language-plaintext highlighter-rouge">MethodHandle</code> e <code class="language-plaintext highlighter-rouge">VarHandle</code>, cujo tempo de execução é da ordem
de nanosegundos. Para casos assim, existe a dificuldade de obter uma base de tempo precisa e da ordem de
nanosegundos, pois as bases de tempo mais comuns disponíveis para o desenvolvedor possuem uma granularidade
da ordem de alguns milissegundos. Além disso, para marcações de tempo menores do que 1 milissegundo,
existe a possibilidade de que os testes possam conter ruído das imprecisões causadas pelo próprio sistema
operacional, como trocas de contexto e interrupções de <em>hardware</em>.</p>

<p>Para contornar este problema, a saída mais comum é executar o código em teste diversas vezes, acumulando
o tempo total de execução e depois calculando o tempo médio de cada execução. Dessa forma, o ruído da
imprecisão presente em algumas execuções estará diluído entre todas as centenas ou milhares de execuções.</p>

<p><strong>Execução do <em>Garbage-Collector</em></strong></p>

<p>O <em>garbage-collector</em> pode ser executado pela JVM a qualquer momento, incluindo durante a execução do
<em>micro-benchmark</em>, provocando pausas na execução e outros efeitos que podem causar divergências nos resultados.
A execução (ou não execução) do <em>garbage-collector</em> é um elemento da JVM que não é fácil de ser controlado pelo
desenvolvedor, e por isso mesmo é um ponto relevante a ser levado em conta na construção do código de teste.
A maneira mais fácil para tentar aliviar os efeitos das execuções do <em>garbage-collector</em> é evitar grandes
alocações de memória, bem como executar o teste centenas ou milhares de vezes a fim de distribuir o desvio
estatístico causado por ele.</p>

<h1 id="como-criar-um-micro-benchmark-de-qualidade">Como criar um <em>micro-benchmark</em> de qualidade?</h1>

<p>Conhecendo algumas das principais armadilhas para a construção de um <em>benchmark</em>, podemos enumerar os
principais pontos necessários para a construção de um bom código de teste:</p>

<ul>
  <li>Evitar a eliminação de código, armazenando e/ou usando os valores retornados pelos códigos em teste em
variáveis públicas e/ou voláteis. A semântica do modificador <code class="language-plaintext highlighter-rouge">volatile</code> do Java pode ajudar a impedir
que atribuições sejam eliminadas pelo otimizador, já que informa ao otimizador que a variável pode ser
lida/gravada a qualquer momento por outras <em>threads</em>.</li>
  <li>Executar o código muitas vezes antes da execução cronometrada do teste, a fim de aquecer o JIT, de forma que
o <em>bytecode</em> seja transformado em código de máquina.</li>
  <li>Executar o código muitas vezes a fim de diluir ruídos na marcação de tempo que podem ocorrer por causa de
fatores externos (sistema operacional, <em>hardware</em>), fatores internos (execução do <em>garbage-collector</em>,
execução do otimizador JIT) ou granularidade da base de tempo.</li>
</ul>

<p>Certamente, um código de teste pode ser construído levando em consideração todos esses requisitos sem
muita dificuldade. A seguir é mostrado um código de <em>micro-benchmark</em> que mostra o tempo de execução médio
para uma construção do tipo <code class="language-plaintext highlighter-rouge">variable = random.nextInt();</code>.</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SelfMadeMicroBenchmarkRandomNextInt</span> <span class="o">{</span>

   <span class="kd">public</span> <span class="kd">static</span> <span class="kd">volatile</span> <span class="kt">int</span> <span class="n">sinkHole</span><span class="o">;</span>

   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
      <span class="n">executeBenchmark</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">executeBenchmark</span><span class="o">()</span> <span class="o">{</span>
      <span class="kt">int</span> <span class="n">maxCount</span> <span class="o">=</span> <span class="mi">100000000</span><span class="o">;</span>
      <span class="nc">Random</span> <span class="n">random</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">();</span>
      <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">pass</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">pass</span> <span class="o">&lt;</span> <span class="mi">8</span><span class="o">;</span> <span class="n">pass</span><span class="o">++)</span> <span class="o">{</span>
         <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
         <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">count</span> <span class="o">&lt;</span> <span class="n">maxCount</span><span class="o">;</span> <span class="n">count</span><span class="o">++)</span> <span class="o">{</span>
            <span class="n">sinkHole</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
         <span class="o">}</span>
         <span class="kt">long</span> <span class="n">stopTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
         <span class="kt">double</span> <span class="n">averageTime</span> <span class="o">=</span> <span class="o">(</span><span class="n">stopTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">)</span> <span class="o">*</span> <span class="mf">1000000.0</span> <span class="o">/</span> <span class="n">maxCount</span><span class="o">;</span>
         <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"PASS %d: average %.2f ns%n"</span><span class="o">,</span> <span class="n">pass</span><span class="o">,</span> <span class="n">averageTime</span><span class="o">);</span>
      <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Ao executar o código, a saída obtida é a seguinte:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">PASS 0: average 10.94 ns
PASS 1: average 12.97 ns
PASS 2: average 13.06 ns
PASS 3: average 12.97 ns
PASS 4: average 12.95 ns
PASS 5: average 12.93 ns
PASS 6: average 12.95 ns
PASS 7: average 12.93 ns</code></pre></figure>

<p>Como podemos ver acima, o primeiro passe possui um certo nível de ruído inserido, inclusive mostrando um
valor menor do que a média dos valores seguintes. De qualquer forma, o código é muito simples e não mostra
atributos estatísticos da amostragem, por exemplo o desvio padrão e erro médio, que poderiam ser dados
interessantes a serem mostrados dependendo da natureza do teste.</p>

<h1 id="usando-jmh-para-escrever-micro-benchmarks-de-qualidade">Usando JMH para escrever <em>micro-benchmarks</em> de qualidade</h1>

<p>O <em>Java Microbenchmark Harness</em> (JMH) é um <em>framework</em> para criação rápida de <em>micro-benchmarks</em> em Java,
sendo que podemos destacar como principais vantagens a facilidade de escrita do teste,
e a abstração dos principais problemas de otimização ocultos pela JVM.</p>

<p>Para usar o JMH é bem simples, primeiramente acrescentando as seguintes dependências no arquivo POM do projeto Java:</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.openjdk.jmh<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>jmh-core<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.31<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.openjdk.jmh<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>jmh-generator-annprocess<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.31<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span></code></pre></figure>

<p>Em seguida, basta marcar os métodos de teste com <code class="language-plaintext highlighter-rouge">@Benchmark</code>, e, para rodar o <em>micro-benchmark</em>, pode ser
usado um método <code class="language-plaintext highlighter-rouge">main</code> conforme o exemplo abaixo:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">JmhRegexExample</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">REGEX</span> <span class="o">=</span> <span class="s">"(alfa|bravo|charlie|delta|echo)"</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">CONTENT</span> <span class="o">=</span>
      <span class="s">"abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Pattern</span> <span class="no">PATTERN</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="no">REGEX</span><span class="o">);</span>

   <span class="nd">@Benchmark</span>
   <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">notPreCompiledRegex</span><span class="o">()</span> <span class="o">{</span>
      <span class="k">return</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="no">REGEX</span><span class="o">).</span><span class="na">matcher</span><span class="o">(</span><span class="no">CONTENT</span><span class="o">).</span><span class="na">find</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="nd">@Benchmark</span>
   <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">preCompiledRegex</span><span class="o">()</span> <span class="o">{</span>
      <span class="k">return</span> <span class="no">PATTERN</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="no">CONTENT</span><span class="o">).</span><span class="na">find</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
      <span class="n">org</span><span class="o">.</span><span class="na">openjdk</span><span class="o">.</span><span class="na">jmh</span><span class="o">.</span><span class="na">Main</span><span class="o">.</span><span class="na">main</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Ao rodar o método <code class="language-plaintext highlighter-rouge">main</code> da classe <code class="language-plaintext highlighter-rouge">JmhRegexExample</code>, o componente JMH executa o <em>benchmark</em> para os dois
métodos marcados com <code class="language-plaintext highlighter-rouge">@Benchmark</code>, exibindo os resultados ao final. Para o exemplo, estamos verificando a
diferença de desempenho entre usar uma <em>regex</em> pré-compilada e compilar a <em>regex</em> a cada teste.</p>

<p>Quem está acostumado com JUnit deve ter notado que os métodos de <em>benchmark</em> não estão com retorno
do tipo <code class="language-plaintext highlighter-rouge">void</code>. Esta é uma das várias <em>features</em> do JMH, e neste caso auxilia o otimizador para que não
simplifique a execução do processamento para o valor de retorno não usado visto anteriormente na seção
“Remoção de variáveis e resultados que nunca são lidos”.</p>

<p>Após mais de 18 minutos de processamento do <em>benchmark</em>, os resultados obtidos foram os seguintes:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text"># Run complete. Total time: 00:18:23
Benchmark                             Mode  Cnt       Score       Error  Units
JmhRegexExample.notPreCompiledRegex  thrpt   25  560290,156 ± 26009,161  ops/s
JmhRegexExample.preCompiledRegex     thrpt   25  694972,423 ±  4512,448  ops/s</code></pre></figure>

<p>Basicamente, os resultados mostram que o uso da <em>regex</em> pré-compilada é quase 24% mais rápido
(<em>score</em> de 694.972 ops/s) do que a abordagem de compilar a <em>regex</em> a cada uso (<em>score</em> de 560.290 ops/s),
para os casos do exemplo.</p>

<p>Um ponto de atenção é que a configuração padrão para os testes pode demorar um tempo significativo, pois
está ajustada para executar muitas repetições. Na versão 1.31 usada no exemplo, a
configuração padrão está ajustada para executar 5 <em>warmups</em> e 5 <em>iterations</em> de 10 segundos por teste, com
5 repetições (<em>forks</em>) para cada teste. Para ajustar os tempos e número de repetições, podem ser usadas as
anotações <code class="language-plaintext highlighter-rouge">@Warmup</code>, <code class="language-plaintext highlighter-rouge">@Measurement</code> e <code class="language-plaintext highlighter-rouge">@Fork</code>, conforme os exemplos abaixo:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">@Warmup(iterations = 5, time = 3000, timeUnit = TimeUnit.MILLISECONDS)</code></strong>: antes de cada teste, o
JMH vai executar 5 <em>warmups</em> (campo <em>iterations</em>), ou seja, executar o <em>benchmark</em> sem
computar o tempo, de forma que o otimizador da JVM possa executar o JIT e outros componentes do fluxo de
otimização de código. Cada <em>warmup</em> será executado por 3000 milissegundos.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">@Measurement(iterations = 5, time = 3000, timeUnit = TimeUnit.MILLISECONDS)</code></strong>: para cada teste, o JMH
vai executar 5 <em>iterations</em> de 3000 milissegundos cada, registrando estatísticas para
as contagens de execução e o tempo decorrido.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">@Fork(value = 2, warmups = 1)</code></strong>: cada teste será executado 3 vezes, sendo a primeira vez a título
de <em>warmup</em>, ou seja, sem computar as
estatísticas, e as outras duas vezes com armazenamento das estatísticas. As repetições para o <code class="language-plaintext highlighter-rouge">@Fork</code> podem
envolver a execução dos testes em novas instâncias da JVM, de forma a validar a execução em uma instância
nova ao invés de reaproveitar a mesma instância para as repetições.</li>
</ul>

<p>Dessa forma, podemos variar a estrutura de repetições dos testes para obtermos uma execução mais rápida
ou mais precisa conforme cada caso. As anotações <code class="language-plaintext highlighter-rouge">Warmup</code>, <code class="language-plaintext highlighter-rouge">Measurement</code> e <code class="language-plaintext highlighter-rouge">Fork</code> também podem ser usadas
a nível de classe, e então são aplicadas para todos os <em>benchmarks</em> dentro da classe.</p>

<p>O JMH também permite o ajuste do parâmetro <code class="language-plaintext highlighter-rouge">@BenchmarkMode</code> para selecionar o modo de cálculo do resultado
do <em>benchmark</em>, sendo 4 opções disponíveis: <code class="language-plaintext highlighter-rouge">Throughput</code>, <code class="language-plaintext highlighter-rouge">AverageTime</code>, <code class="language-plaintext highlighter-rouge">SampleTime</code> e <code class="language-plaintext highlighter-rouge">SingleShotTime</code>.
Caso mais de uma opção seja selecionada, o JMH vai calcular os resultados dos <em>benchmarks</em> dos diferentes
modos e exibir os diferentes valores ao final. Ainda é possível usar a marcação <code class="language-plaintext highlighter-rouge">@OutputTimeUnit</code> para informar
a unidade de tempo do resultado (<code class="language-plaintext highlighter-rouge">TimeUnit</code>).</p>

<ul>
  <li>Modo <strong><code class="language-plaintext highlighter-rouge">Throughput</code></strong>: o resultado será calculado em termos de contagem de operações por segundo
(ou pela unidade informada em <code class="language-plaintext highlighter-rouge">OutputTimeUnit</code>)</li>
  <li>Modo <strong><code class="language-plaintext highlighter-rouge">AverageTime</code></strong>: conforme a documentação, na prática é o inverso do <code class="language-plaintext highlighter-rouge">Throughput</code>, ou seja, o tempo
médio para execução do método do <em>benchmark</em>.</li>
  <li>Modo <strong><code class="language-plaintext highlighter-rouge">SingleShotTime</code></strong>: usado para que seja feita apenas uma execução do <em>benchmark</em>, sem aquecimento
e repetições.</li>
  <li>Modo <strong><code class="language-plaintext highlighter-rouge">SampleTime</code></strong>: nesse modo, é gerada uma distribuição do tempo de execução do <em>benchmark</em>, mostrando
alguns percentis e seus <em>scores</em>, conforme o exemplo abaixo:</li>
</ul>

<figure class="highlight"><pre><code class="language-text" data-lang="text">Benchmark                              Mode     Cnt       Score    Error  Units
JmhExample.preCompiledRegex          sample  321947    1514,339 ± 27,902  ns/op
JmhExample.preCompiledRegex:p0.00    sample            1410,000           ns/op
JmhExample.preCompiledRegex:p0.50    sample            1462,000           ns/op
JmhExample.preCompiledRegex:p0.90    sample            1498,000           ns/op
JmhExample.preCompiledRegex:p0.95    sample            1504,000           ns/op
JmhExample.preCompiledRegex:p0.99    sample            1558,000           ns/op
JmhExample.preCompiledRegex:p0.999   sample            3556,000           ns/op
JmhExample.preCompiledRegex:p0.9999  sample          114588,262           ns/op
JmhExample.preCompiledRegex:p1.00    sample          997376,000           ns/op</code></pre></figure>

<p>Cabe salientar que o trecho de código apresentado para o método <code class="language-plaintext highlighter-rouge">main</code> roda todos os <em>benchmarks</em> presentes
no projeto Java, independente de estarem localizados na mesma classe onde se encontra o método <code class="language-plaintext highlighter-rouge">main</code>.
Para obter um melhor controle da execução dos <em>benchmarks</em>, pode ser usado o <code class="language-plaintext highlighter-rouge">OptionsBuilder</code> conforme exemplo
abaixo, onde é selecionado para executar somente os <em>benchmarks</em> da classe <code class="language-plaintext highlighter-rouge">JmhRegexExample</code>:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">RunnerException</span> <span class="o">{</span>
   <span class="nc">Options</span> <span class="n">opt</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">OptionsBuilder</span><span class="o">()</span>
      <span class="o">.</span><span class="na">include</span><span class="o">(</span><span class="nc">JmhRegexExample</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">())</span>
      <span class="o">.</span><span class="na">forks</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>
      <span class="o">.</span><span class="na">build</span><span class="o">();</span>
   <span class="k">new</span> <span class="nf">Runner</span><span class="o">(</span><span class="n">opt</span><span class="o">).</span><span class="na">run</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>

<h1 id="usando-jmh-no-intellij">Usando JMH no IntelliJ</h1>

<p>Através do uso de métodos <code class="language-plaintext highlighter-rouge">main</code> conforme mostrado anteriormente, os <em>benchmarks</em> podem ser executados de dentro
do próprio IntelliJ usando os atalhos já disponíveis na <em>interface</em> da IDE.</p>

<p>Existem alguns <em>plugins</em> de integração à ferramenta que auxiliam o uso dela na IDE.
Um exemplo deles é o <strong>JMH Java Microbenchmark Harness</strong>,
fornecendo, dentre outras coisas, botões de atalho na IDE para a execução dos <em>benchmarks</em> diretamente a partir
dos métodos. Dessa forma, não há a necessidade de declaração dos métodos <code class="language-plaintext highlighter-rouge">main</code>, conforme o
exemplo de captura a seguir:</p>

<p><img src="/assets/2021-05-27-micro-benchmarks/intellij-jmh-plugin.png" alt="IntelliJ JMH Plugin" /></p>

<h1 id="conclusão">Conclusão</h1>

<p>Os <em>micro-benchmarks</em> são uma ferramenta poderosa para auxiliar o engenheiro de <em>software</em> a entender melhor
os detalhes de desempenho tanto do código sendo produzido, quanto das bibliotecas e outros componentes em uso
em um projeto. Mostramos um pouco das armadilhas e detalhes escondidos em termos de otimização de código pela
JVM, que podem afetar a construção de <em>micro-benchmarks</em> de qualidade e gerar resultados imprecisos ou incorretos.</p>

<p>Mostramos também uma forma de escrever <em>benchmarks</em> de qualidade em Java usando o <em>framework</em> JMH para
construção fácil de <em>micro-benchmarks</em>. O JMH abstrai do desenvolvedor a maioria dos problemas e detalhes
técnicos quanto a otimização de código, fornecendo uma <em>interface</em> simples e concisa para a escrita dos <em>benchmarks</em>.</p>

<h1 id="referências">Referências</h1>

<ul>
  <li>
    <p><a href="https://en.wikipedia.org/wiki/Profiling_(computer_programming)"><em>Profiling (computer programming)</em></a></p>
  </li>
  <li>
    <p><a href="https://github.com/openjdk/jmh"><em>GitHub - Java Microbenchmark Harness (JMH)</em></a></p>
  </li>
  <li>
    <p><a href="https://www.baeldung.com/java-microbenchmark-harness"><em>Microbenchmarking with Java</em></a></p>
  </li>
  <li>
    <p><a href="https://wiki.openjdk.java.net/display/HotSpot/MicroBenchmarks"><em>OpenJDK Wiki - So You Want to Write a Micro-Benchmark</em></a></p>
  </li>
  <li>
    <p><a href="https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_02_BenchmarkModes.java"><em>JMH Samples - BenchmarkMode</em></a></p>
  </li>
  <li>
    <p><a href="https://plugins.jetbrains.com/plugin/7529-jmh-java-microbenchmark-harness"><em>IntelliJ Plugins - JMH Java Microbenchmark Harness</em></a></p>
  </li>
</ul>]]></content><author><name>Jose Ferreira</name></author><summary type="html"><![CDATA[Durante a participação em um projeto, o engenheiro de software pode ter que passar pelo processo de escolha de uma determinada biblioteca, framework ou componente dentre uma gama de possibilidades, baseado no desempenho do componente. Pode ser necessário escolher qual a biblioteca de serialização de JSONs mais rápida, qual a biblioteca de processamento de expressões regulares mais rápida, e assim por diante. Geralmente, será possível encontrar na Internet benchmarks prontos e de qualidade para os tópicos mais quentes do mundo da programação, contudo, para casos particulares, pode ser necessário que o próprio engenheiro tenha que executar seus benchmarks para ajudar no processo de decisão. Neste artigo, abordaremos alguns aspectos do processo de criação/execução de micro-benchmark em Java para o caso em que seja necessária a criação rápida de benchmarks.]]></summary></entry><entry xml:lang="pt"><title type="html">Otimizando o uso de Expressões Regulares (Regex)</title><link href="https://engineering.axur.com/2021/05/17/regex-optimization.html" rel="alternate" type="text/html" title="Otimizando o uso de Expressões Regulares (Regex)" /><published>2021-05-17T12:36:00+00:00</published><updated>2021-05-17T12:36:00+00:00</updated><id>https://engineering.axur.com/2021/05/17/regex-optimization</id><content type="html" xml:base="https://engineering.axur.com/2021/05/17/regex-optimization.html"><![CDATA[<p>Como descrito em nossos <a href="https://engineering.axur.com/2020/07/08/pilares-tecnicos.html">Pilares Técnicos</a>, nosso objetivo como time de Engineering da Axur é construir a tecnologia que permite a nossos produtos tornarem a internet mais segura. Para tal, existem etapas essenciais para prover maior segurança na Web, como o monitoramento e a inspeção de conteúdo na internet. É dessa forma que identificamos possíveis ameaças aos nossos clientes. Este artigo aborda <a href="https://pt.wikipedia.org/wiki/Express%C3%A3o_regular">expressões regulares</a> (<em>regex</em>, do inglês <em>Regular Expressions</em>), uma entre tantas ferramentas que utilizamos para o combate a riscos digitais.</p>

<p>De mecanismos simples como comparação de <em>strings</em> até recursos mais complexos como o uso de <a href="https://blog.axur.com/pt/como-a-axur-usa-machine-learning-para-encontrar-phishings"><em>Machine Learning</em></a>, existe uma variedade de tecnologias e conhecimentos técnicos aplicados para processar a enorme quantidade de dados que coletamos na internet — tudo isso em tempo hábil e com custo computacional apropriado. Entre essas tecnologias, o uso de expressões regulares destaca-se como um forte aliado para executarmos buscas flexíveis em conteúdos diversos. Todavia, o emprego inapropriado de <em>regex</em> gera gargalos de processamentos, principalmente em um cenário de alto <em>throughput</em> de dados.</p>

<p>Aqui, discutiremos diferentes aspectos da utilização de expressões regulares, com <em>benchmarks</em>, otimizações e comparativos de resultados. Não será abordada a sintaxe das expressões. Este artigo também não tem o objetivo ser um manual exaustivo de todas as otimizações possíveis, e sim, mostrar alguns pontos importantes ao se trabalhar com <em>regex</em>.</p>

<h2 id="compile-uma-única-vez">Compile uma única vez</h2>

<p>Linguagens de programação geralmente possuem suporte <em>built-in</em> ao uso de <em>regex</em>. Esse uso pode ser classificado em duas etapas principais: compilação e <em>matching</em>.</p>

<p>Na compilação, a expressão é transformada em uma sequência de instruções internas que serão usadas por um motor de correspondência (<em>regex/matching engine</em>). Para o <em>matching,</em> um determinado conjunto de caracteres é comparado com esse padrão compilado para verificar equivalência. Em Python, por exemplo, <a href="https://docs.python.org/3/howto/regex.html">o motor desenvolvido em C interpreta <em>bytecodes</em> gerados</a>.</p>

<p>Aqui temos exemplos em diferentes linguagens de programação do uso da compilação e <em>matching</em>. Neste caso, o código busca a palavra "internet" na frase <em>"a Axur torna a internet mais segura"</em>.</p>

<details>
 <summary>Código Java</summary>


<a href="https://replit.com/@EduardoBrito5/Match-Simples-Java" target="_blank">Se deseja ver o código completo e/ou executar o código em seu browser clique aqui</a>


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">[...]</span>
<span class="kd">final</span> <span class="nc">Pattern</span> <span class="n">pattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">".*internet.*"</span><span class="o">);</span>
<span class="kd">final</span> <span class="nc">Matcher</span> <span class="n">matcher</span> <span class="o">=</span> <span class="n">pattern</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="s">"a Axur torna a internet mais segura"</span><span class="o">);</span>
<span class="kt">boolean</span> <span class="n">hasMatched</span> <span class="o">=</span> <span class="n">matcher</span><span class="o">.</span><span class="na">find</span><span class="o">();</span> <span class="c1">// uso do “find” para que seja procurada uma substring</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"has matched: "</span> <span class="o">+</span> <span class="n">hasMatched</span><span class="o">);</span>
<span class="o">[...]</span></code></pre></figure>



</details>
<p><br /></p>

<details>
 <summary>Código Python</summary>
<a href="https://replit.com/@EduardoBrito5/Match-Simples-Python" target="_blank">Se deseja ver o código completo e/ou executar o código em seu browser clique aqui</a>


<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">re</span>

<span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span><span class="s">'.*internet.*'</span><span class="p">)</span>
<span class="n">has_matched</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="n">match</span><span class="p">(</span><span class="s">'a Axur torna a internet mais segura'</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="s">"has matched:"</span><span class="p">,</span> <span class="nb">bool</span><span class="p">(</span><span class="n">has_matched</span><span class="p">))</span></code></pre></figure>



</details>
<p><br /></p>

<p>Dependendo da linguagem de programação, existem outros meios de verificar a palavra na frase com outras sintaxes, objetos, funções, classes e métodos. Todavia, o ponto é que, devido ao custo computacional da compilação da <em>regex</em>, deve-se evitar a necessidade de recompilá-la toda vez que há uma verificação de <em>matching</em>. Para demonstrar os efeitos da compilação, veja o código a seguir:</p>

<details>
 <summary>Código Java</summary>


<a href="https://replit.com/@EduardoBrito5/Compilacao-Codigo-Java" target="_blank">Se deseja ver o código completo e/ou executar o código em seu browser clique aqui</a>


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">[...]</span>
<span class="kd">final</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">allRegex</span> <span class="o">=</span> <span class="n">generateRegexes</span><span class="o">(</span><span class="n">keywords</span><span class="o">);</span>

<span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">contents</span> <span class="o">=</span> <span class="o">{</span><span class="s">"a axur torna a internet mais segura"</span><span class="o">,</span>
        <span class="s">"Detecte e remova fraudes digitais da internet automaticamente"</span><span class="o">,</span>
        <span class="s">"Takedown proativo e transparente"</span><span class="o">};</span>

<span class="kt">long</span> <span class="n">totalTimeInMsForMethod1</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="kt">long</span> <span class="n">totalTimeInMsForMethod2</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Processing... it may take a while..."</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="no">TOTAL_CHECKS</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">content</span> <span class="o">=</span> <span class="n">randomContentFrom</span><span class="o">(</span><span class="n">contents</span><span class="o">);</span>
    <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="nl">regex:</span> <span class="n">allRegex</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">totalTimeInMsForMethod1</span> <span class="o">+=</span> <span class="n">alwaysCompilingMethod</span><span class="o">(</span><span class="n">regex</span><span class="o">,</span> <span class="n">content</span><span class="o">);</span>
        <span class="n">totalTimeInMsForMethod2</span> <span class="o">+=</span> <span class="n">cachedPatternCompilingMethod</span><span class="o">(</span><span class="n">regex</span><span class="o">,</span> <span class="n">content</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Average time (ms) for Method 1: "</span> <span class="o">+</span> <span class="n">totalTimeInMsForMethod1</span><span class="o">/</span> <span class="no">TOTAL_CHECKS</span><span class="o">.</span><span class="na">floatValue</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Total time (seconds) for Method 1: "</span> <span class="o">+</span> <span class="n">totalTimeInMsForMethod1</span><span class="o">/</span><span class="no">MILLIS_IN_SECONDS</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Average time (ms) for Method 2: "</span> <span class="o">+</span> <span class="n">totalTimeInMsForMethod2</span><span class="o">/</span> <span class="no">TOTAL_CHECKS</span><span class="o">.</span><span class="na">floatValue</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Total time (seconds) for Method 2: "</span> <span class="o">+</span> <span class="n">totalTimeInMsForMethod2</span><span class="o">/</span><span class="no">MILLIS_IN_SECONDS</span><span class="o">);</span>
<span class="o">[...]</span>

<span class="o">[...]</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">long</span> <span class="nf">alwaysCompilingMethod</span><span class="o">(</span><span class="nc">String</span> <span class="n">regex</span><span class="o">,</span> <span class="nc">String</span> <span class="n">content</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
    <span class="nc">Pattern</span> <span class="n">pattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="n">regex</span><span class="o">);</span>
    <span class="nc">Matcher</span> <span class="n">matcher</span> <span class="o">=</span> <span class="n">pattern</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="n">content</span><span class="o">);</span>
    <span class="n">sinkHole</span> <span class="o">=</span> <span class="n">matcher</span><span class="o">.</span><span class="na">find</span><span class="o">();</span>
    <span class="kt">long</span> <span class="n">endTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
    <span class="k">return</span> <span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">;</span>
<span class="o">}</span>

<span class="kd">private</span> <span class="kd">static</span> <span class="kt">long</span> <span class="nf">cachedPatternCompilingMethod</span><span class="o">(</span><span class="nc">String</span> <span class="n">regex</span><span class="o">,</span> <span class="nc">String</span> <span class="n">content</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
    <span class="nc">Matcher</span> <span class="n">matcher</span> <span class="o">=</span> <span class="n">matcherFromCache</span><span class="o">(</span><span class="n">regex</span><span class="o">,</span> <span class="n">content</span><span class="o">);</span>
    <span class="n">sinkHole</span> <span class="o">=</span> <span class="n">matcher</span><span class="o">.</span><span class="na">find</span><span class="o">();</span>
    <span class="kt">long</span> <span class="n">endTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
    <span class="k">return</span> <span class="n">endTime</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">;</span>
<span class="o">}</span>

<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Matcher</span> <span class="nf">matcherFromCache</span><span class="o">(</span><span class="nc">String</span> <span class="n">regex</span><span class="o">,</span> <span class="nc">String</span> <span class="n">content</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">cachedPatterns</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">regex</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">cachedPatterns</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">regex</span><span class="o">).</span><span class="na">matcher</span><span class="o">(</span><span class="n">content</span><span class="o">);</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
        <span class="nc">Pattern</span> <span class="n">pattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="n">regex</span><span class="o">);</span>
        <span class="n">cachedPatterns</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">regex</span><span class="o">,</span> <span class="n">pattern</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">pattern</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="n">content</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
<span class="o">[...]</span></code></pre></figure>



</details>
<p><br /></p>

<p>Este código gera 10.000 expressões regulares combinando palavras-chave (<em>keywords</em>). Então, são verificados se os padrões das expressões têm correspondência com 1.000 conteúdos randômicos. A ideia deste algoritmo é simular uma ampla gama de conteúdos diferentes testados com uma grande variedade de <em>regexes</em> para <em>matching</em>.</p>

<p>Para o <em>benchmark</em> são usados dois métodos distintos: um que compila a <em>regex</em> em toda verificação de <em>match</em> da expressão com o conteúdo (método 1) e outro que compila uma única vez e salva em memória o padrão compilado para reuso (método 2). Ou seja, o segundo método usa um mecanismo simples de <em>cache</em>. O resultado da execução do código é o seguinte:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">Average time (ms) for Method 1: 40.592
Total time (seconds) for Method 1: 40
Average time (ms) for Method 2: 24.546
Total time (seconds) for Method 2: 24</code></pre></figure>

<p>Cada execução pode gerar resultados diferentes de acordo com o ambiente utilizado. Entretanto, independentemente disso, existe diferença significativa entre os métodos. Nesse ambiente, com uso de <em>caching</em>, a velocidade de processamento foi 66% maior.</p>

<p><strong>Fique atento!</strong> De acordo com a linguagem utilizada, este mecanismo pode estar embutido de alguma forma. Em Python, a operação de <em>compiling</em> usa <em>cache</em> em memória. O trecho a seguir foi retirado da <em>Python Standard Library</em>, mais especificamente do arquivo "re.py":</p>

<details>
 <summary>Código do re.py</summary>

<p>


<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">_MAXCACHE</span> <span class="o">=</span> <span class="mi">512</span>
<span class="k">def</span> <span class="nf">_compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">):</span>
    <span class="c1"># internal: compile pattern
</span>    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">flags</span><span class="p">,</span> <span class="n">RegexFlag</span><span class="p">):</span>
        <span class="n">flags</span> <span class="o">=</span> <span class="n">flags</span><span class="p">.</span><span class="n">value</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">_cache</span><span class="p">[</span><span class="nb">type</span><span class="p">(</span><span class="n">pattern</span><span class="p">),</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">]</span>
    <span class="k">except</span> <span class="nb">KeyError</span><span class="p">:</span>
        <span class="k">pass</span>
    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">flags</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span>
                <span class="s">"cannot process flags argument with a compiled pattern"</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">pattern</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">sre_compile</span><span class="p">.</span><span class="n">isstring</span><span class="p">(</span><span class="n">pattern</span><span class="p">):</span>
        <span class="k">raise</span> <span class="nb">TypeError</span><span class="p">(</span><span class="s">"first argument must be string or compiled pattern"</span><span class="p">)</span>
    <span class="n">p</span> <span class="o">=</span> <span class="n">sre_compile</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">DEBUG</span><span class="p">):</span>
        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">_cache</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">_MAXCACHE</span><span class="p">:</span>
            <span class="c1"># Drop the oldest item
</span>            <span class="k">try</span><span class="p">:</span>
                <span class="k">del</span> <span class="n">_cache</span><span class="p">[</span><span class="nb">next</span><span class="p">(</span><span class="nb">iter</span><span class="p">(</span><span class="n">_cache</span><span class="p">))]</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">StopIteration</span><span class="p">,</span> <span class="nb">RuntimeError</span><span class="p">,</span> <span class="nb">KeyError</span><span class="p">):</span>
                <span class="k">pass</span>
        <span class="n">_cache</span><span class="p">[</span><span class="nb">type</span><span class="p">(</span><span class="n">pattern</span><span class="p">),</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">]</span> <span class="o">=</span> <span class="n">p</span>
    <span class="k">return</span> <span class="n">p</span></code></pre></figure>


</p>

</details>
<p><br /></p>

<p>É importante lembrar: <strong>há um <em>trade-off</em> entre velocidade de processamento e uso de memória</strong> , pois a <em>cache</em> irá gastar invariavelmente mais recursos de memória.</p>

<h2 id="tamanho-do-conteúdo-e-complexidade-das-expressões">Tamanho do conteúdo e complexidade das expressões</h2>

<p>A velocidade de processamento de <em>matching</em> de uma <em>regex</em> depende de ao menos dois fatores: complexidade da expressão e tamanho do conteúdo em que é feita a busca do padrão da expressão. Para demonstrar o impacto dessas duas variáveis, temos o seguinte <em>benchmark</em>.</p>

<details>
 <summary>Código Java</summary>


<a href="https://replit.com/@EduardoBrito5/Tamanho-de-Conteudo-e-Complexidade" target="_blank">Se deseja ver o código completo e/ou executar o código em seu browser clique aqui</a>


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">[...]</span>
<span class="nc">String</span> <span class="n">smallContent</span> <span class="o">=</span> <span class="n">generateContentWithNKeywords</span><span class="o">(</span><span class="n">keywords</span><span class="o">,</span> <span class="mi">10</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">mediumContent</span> <span class="o">=</span> <span class="n">generateContentWithNKeywords</span><span class="o">(</span><span class="n">keywords</span><span class="o">,</span> <span class="mi">100</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">bigContent</span> <span class="o">=</span> <span class="n">generateContentWithNKeywords</span><span class="o">(</span><span class="n">keywords</span><span class="o">,</span> <span class="mi">10000</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">giantContent</span> <span class="o">=</span> <span class="n">generateContentWithNKeywords</span><span class="o">(</span><span class="n">keywords</span><span class="o">,</span> <span class="mi">100000</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"For simple regex, increasing content size...."</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">smallContent</span><span class="o">,</span> <span class="n">simpleRegex</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">mediumContent</span><span class="o">,</span> <span class="n">simpleRegex</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">bigContent</span><span class="o">,</span> <span class="n">simpleRegex</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">giantContent</span><span class="o">,</span> <span class="n">simpleRegex</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"For complex regex, increasing content size...."</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">smallContent</span><span class="o">,</span> <span class="n">complexRegex</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">mediumContent</span><span class="o">,</span> <span class="n">complexRegex</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">bigContent</span><span class="o">,</span> <span class="n">complexRegex</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">giantContent</span><span class="o">,</span> <span class="n">complexRegex</span><span class="o">);</span>
<span class="o">[...]</span></code></pre></figure>



</details>
<p><br /></p>

<p>Os resultados da execução do código foram os seguintes:</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">For simple regex, increasing content size....
Took 2 ms
Took 1 ms
Took 94 ms
Took 53 ms
For complex regex, increasing content size....
Took 2 ms
Took 4 ms
Took 112 ms
Took 323 ms</code></pre></figure>

<p><sub><sup>* os resultados variam a cada execução, mas eles mantêm sempre a notável diferença entre os tempos para as diferentes combinações de <em>regexes</em> e conteúdos.
</sup></sub></p>

<p>Pelos resultados, há impacto significativo de ambas as variáveis. Para dois conteúdos idênticos, com uma <em>regex</em> mais complexa entre um conteúdo e outro, o tempo aumentou em mais de 6 vezes. Já para duas <em>regexes</em> idênticas o tempo cresceu em mais de 160 vezes, com o aumento do conteúdo entre uma comparação e outra.</p>

<p>Assim, fica clara a importância de verificar a complexidade da expressão. Ademais, é interessante identificar se o conteúdo pode ser reduzido antes da comparação. Por exemplo, se for desejado buscar algum texto em página Web pode ser que faça sentido aplicar a <em>regex</em> apenas no texto visível e não em todo o HTML da página.</p>

<h2 id="otimização-nas-expressões">Otimização nas expressões</h2>

<p>Um ponto importante é entender como funcionam <em>regexes</em> e suas sintaxes na linguagem de programação escolhida pelo desenvolvedor. O mau uso da sintaxe pode gerar duas expressões regulares equivalentes em <em>matching</em> de padrões, mas com desempenhos completamente distintos. Isso pode ser facilmente verificado no <em>benchmark</em> abaixo:</p>

<details>
 <summary>Código Java</summary>


<a href="https://replit.com/@EduardoBrito5/Otimizacao-de-Expressao-Java" target="_blank">Se deseja ver o código completo e/ou executar o código em seu browser clique aqui</a>


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">[...]</span>
<span class="nc">Pattern</span> <span class="n">notOptimizedPattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span>
        <span class="s">".?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?"</span> <span class="o">+</span>
        <span class="s">"(m.?e.?u.?s.?i.?t.?e.?|site|meusite|minhapagina|teste|website|internet|p[aá]gina|"</span> <span class="o">+</span>
        <span class="s">"(my).?site|sitenovo)"</span><span class="o">);</span>

<span class="nc">Pattern</span> <span class="n">optimizedSamePattern</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">".{0,21}"</span> <span class="o">+</span>
        <span class="s">"(m.?e.?u.?s.?i.?t.?e.?|site|meusite|minhapagina|teste|website|internet|p[aá]gina|"</span> <span class="o">+</span>
        <span class="s">"(my).?site|sitenovo)"</span><span class="o">);</span>

<span class="nc">String</span> <span class="n">contentThatDoesNotMatch</span> <span class="o">=</span> <span class="s">"uma string com mais de vinte um caracteres no inicio fazendo que o match não ocorra"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">contentThatMatches</span> <span class="o">=</span> <span class="s">"uma string qualquer sitenovo"</span><span class="o">;</span>


<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"For NOT optimized regex..."</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">contentThatDoesNotMatch</span><span class="o">,</span> <span class="n">notOptimizedPattern</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">contentThatMatches</span><span class="o">,</span> <span class="n">notOptimizedPattern</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"For optimized regex..."</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">contentThatDoesNotMatch</span><span class="o">,</span> <span class="n">optimizedSamePattern</span><span class="o">);</span>
<span class="n">testMatching</span><span class="o">(</span><span class="n">contentThatMatches</span><span class="o">,</span> <span class="n">optimizedSamePattern</span><span class="o">);</span>
<span class="o">[...]</span></code></pre></figure>



</details>
<p><br /></p>

<p>O resultado, em tempo para processamento, é <strong>absurdamente diferente</strong> :</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">For NOT optimized regex...
Took 36796 ms - Result:false
Took 1 ms - Result:true
For optimized regex...
Took 1 ms - Result:false
Took 0 ms - Result:true</code></pre></figure>

<p>A otimização teve êxito: as expressões são equivalentes em <em>matching</em> de conteúdos e o tempo de processamento foi reduzido drasticamente em um dos casos. Com apenas a substituição das sequências de <code class="language-plaintext highlighter-rouge">.?</code> pelo uso de quantificadores <code class="language-plaintext highlighter-rouge">{n,m}</code>, notou-se aumento na velocidade de processamento em <strong>dezenas de milhares de vezes</strong>.</p>

<p><strong>Por que isso ocorre?</strong> Java utiliza <a href="https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton">autômatos finitos não determinísticos</a> — como pode ser visto <a href="https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/regex/Pattern.html#:~:text=The%20Pattern%20engine%20performs%20traditional%20NFA-based%20matching%20with%20ordered%20alternation%20as%20occurs%20in%20Perl%205">aqui</a> — para resolver as expressões. O algoritmo usa o mecanismo de <a href="https://en.wikipedia.org/wiki/Backtracking"><em>backtracking</em></a>, testa todas as expansões da expressão regular e aceita a primeira correspondência encontrada. Por conta das sequências de <code class="language-plaintext highlighter-rouge">.?</code>, existe uma explosão de caminhos que devem ser verificados, prejudicando o desempenho.</p>

<p>Em outras implementações algorítmicas esses resultados podem ser bem diferentes. Por este motivo, é importante conhecer a implementação utilizada e realizar validações nas <em>regexes</em> criadas. Para o uso da <em>regex</em> padrão do Java, o impacto da não otimização da <em>regex</em> é enorme e pode gerar gargalos significativos em um sistema. No código equivalente em Python também há diferença nos tempos de processamento entre as expressões, porém o tempo total de execução da aplicação foi muito menor que em Java. Isso não é um problema da linguagem, apenas que <strong>neste cenário</strong> , o <em>matching engine</em> do Java foi menos eficiente que o motor usado por Python.</p>

<details>
 <summary>Código Python</summary>


<a href="https://replit.com/@EduardoBrito5/Otimizacao-de-Expressao-Python" target="_blank">Se deseja ver o código completo e/ou executar o código em seu browser clique aqui</a>


<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="p">[...]</span>
<span class="n">not_optimized</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span>
    <span class="s">".?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?.?"</span> <span class="o">+</span>
    <span class="s">"(m.?e.?u.?s.?i.?t.?e.?|site|meusite|minhapagina|teste|website|internet|p[aá]gina|"</span>
    <span class="o">+</span> <span class="s">"(my).?site|sitenovo)"</span><span class="p">)</span>
<span class="n">optimized</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span>
    <span class="s">".{0,21}(m.?e.?u.?s.?i.?t.?e.?|site|meusite|minhapagina|teste|website|internet|p[aá]gina|"</span>
    <span class="o">+</span> <span class="s">"(my).?site|sitenovo)"</span><span class="p">)</span>

<span class="n">content_that_does_not_match</span> <span class="o">=</span> <span class="s">"uma string com mais de vinte um caracteres no inicio fazendo que o match não ocorra"</span>
<span class="n">content_that_matches</span> <span class="o">=</span> <span class="s">"uma string qualquer sitenovo"</span>
<span class="k">print</span><span class="p">(</span><span class="s">"For NOT optimized regex..."</span><span class="p">)</span>
<span class="n">test_matching</span><span class="p">(</span><span class="n">content_that_does_not_match</span><span class="p">,</span> <span class="n">not_optimized</span><span class="p">)</span>
<span class="n">test_matching</span><span class="p">(</span><span class="n">content_that_matches</span><span class="p">,</span> <span class="n">not_optimized</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="s">"For optimized regex..."</span><span class="p">)</span>
<span class="n">test_matching</span><span class="p">(</span><span class="n">content_that_does_not_match</span><span class="p">,</span> <span class="n">optimized</span><span class="p">)</span>
<span class="n">test_matching</span><span class="p">(</span><span class="n">content_that_matches</span><span class="p">,</span> <span class="n">optimized</span><span class="p">)</span>
<span class="p">[...]</span></code></pre></figure>



</details>
<p><br /></p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">For NOT optimized regex...
Took 503.0701160430908 ms - Result: False
Took 0.0059604644775390625 ms - Result: True
For optimized regex...
Took 0.004291534423828125 ms - Result: False
Took 0.0016689300537109375 ms - Result: True</code></pre></figure>

<h2 id="só-use-se-necessário">Só use se necessário</h2>

<p>Sempre verifique se a utilização da expressão regular faz sentido em determinado contexto. Muitas vezes podemos substituí-la por outros recursos da linguagem que melhoram o desempenho do código e o tornam mais legível. Vejamos o exemplo a seguir:</p>

<details>
 <summary>Código Java</summary>


<a href="https://replit.com/@EduardoBrito5/Regex-e-StartsWith-Java" target="_blank">Se deseja ver o código completo e/ou executar o código em seu browser clique aqui</a>


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o">[...]</span>
<span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">names</span> <span class="o">=</span> <span class="o">{</span>
        <span class="s">"João da Silva"</span><span class="o">,</span> <span class="s">"Eduardo dos Santos"</span><span class="o">,</span> <span class="s">"Maria Joana"</span><span class="o">,</span> <span class="s">"Carlos de Jesus"</span>
<span class="o">};</span>
<span class="nc">Pattern</span> <span class="n">regex</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"^João .*"</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"With regex..."</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="nl">name:</span> <span class="n">names</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">checkFirstNameWithRegex</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">regex</span><span class="o">);</span>
<span class="o">}</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Without regex..."</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="nl">name:</span> <span class="n">names</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">checkFirstNameWithStartsWith</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">[...]</span>

<span class="o">[...]</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">checkFirstNameWithStartsWith</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">nanoTime</span><span class="o">();</span>
    <span class="kt">boolean</span> <span class="n">hasMatched</span> <span class="o">=</span> <span class="n">name</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"João "</span><span class="o">);</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Took "</span> <span class="o">+</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">nanoTime</span><span class="o">()</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">)</span> <span class="o">+</span> <span class="s">" ms - Matched: "</span> <span class="o">+</span> <span class="n">hasMatched</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">checkFirstNameWithRegex</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">Pattern</span> <span class="n">regex</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">nanoTime</span><span class="o">();</span>
    <span class="kt">boolean</span> <span class="n">hasMatched</span> <span class="o">=</span> <span class="n">regex</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="n">name</span><span class="o">).</span><span class="na">matches</span><span class="o">();</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Took "</span> <span class="o">+</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">nanoTime</span><span class="o">()</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">)</span> <span class="o">+</span> <span class="s">" ms - Matched: "</span> <span class="o">+</span> <span class="n">hasMatched</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">[...]</span></code></pre></figure>



</details>
<p><br /></p>

<p>Neste código, procura-se o primeiro nome "João" em uma lista de nomes. O resultado apresenta variações de desempenho entre os conteúdos. Houve melhor desempenho no uso do método <code class="language-plaintext highlighter-rouge">startsWith</code> em vez de <em>regex</em> no teste realizado.</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">With regex...
Took 410914 ns - Matched: true
Took 13858 ns - Matched: false
Took 7386 ns - Matched: false
Took 6795 ns - Matched: false
Without regex...
Took 10815 ns - Matched: true
Took 2538 ns - Matched: false
Took 1745 ns - Matched: false
Took 1529 ns - Matched: false</code></pre></figure>

<p>A performance <em>pode</em> ser irrelevante para um cenário simples como esse. Entretanto, o método <code class="language-plaintext highlighter-rouge">startsWith</code> também deixa mais clara a intenção do código que a expressão regular. Isso deve ser considerado na solução, afinal, <a href="https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-time-spent-reading-versus-writing-is"><em>um código é muito mais lido que escrito</em></a>.</p>

<h2 id="conclusão">Conclusão</h2>

<p>Neste artigo mostramos testes que comprovam o impacto da compilação e sintaxe das expressões. Também ficam claros os efeitos das otimizações realizadas e a diferença entre abordagens com e sem <em>regex</em>. Demonstramos aqui a importância de testar e validar diferentes expressões, entender o contexto de aplicação da ferramenta e como algumas otimizações ajudam na construção de programas melhores. Em sistemas que processam grandes volumes de dados, esses cuidados podem fazer a diferença para garantir um bom desempenho e evitar gargalos.</p>]]></content><author><name>Eduardo Stein Brito</name></author><summary type="html"><![CDATA[Como descrito em nossos Pilares Técnicos, nosso objetivo como time de Engineering da Axur é construir a tecnologia que permite a nossos produtos tornarem a internet mais segura. Para tal, existem etapas essenciais para prover maior segurança na Web, como o monitoramento e a inspeção de conteúdo na internet. É dessa forma que identificamos possíveis ameaças aos nossos clientes. Este artigo aborda expressões regulares (regex, do inglês Regular Expressions), uma entre tantas ferramentas que utilizamos para o combate a riscos digitais.]]></summary></entry><entry xml:lang="pt"><title type="html">Gerenciamento de Memória no Java - Containers Docker</title><link href="https://engineering.axur.com/2020/12/28/gerenciamento-memoria-java-part2.html" rel="alternate" type="text/html" title="Gerenciamento de Memória no Java - Containers Docker" /><published>2020-12-28T16:41:00+00:00</published><updated>2020-12-28T16:41:00+00:00</updated><id>https://engineering.axur.com/2020/12/28/gerenciamento-memoria-java-part2</id><content type="html" xml:base="https://engineering.axur.com/2020/12/28/gerenciamento-memoria-java-part2.html"><![CDATA[<p>Nesta segunda parte do artigo sobre Gerenciamento de Memória no Java, veremos alguns aspectos mais específicos do
Gerenciamento de Memória do Java quando estiver rodando dentro de <em>containers Docker</em>, bem como algumas abordagens
para configuração de memória, e também dicas de como tratar os problemas de memória mais comuns.</p>

<p>Para acessar a primeira parte do artigo, utilize o link abaixo:</p>

<p><a href="/2020/12/17/gerenciamento-memoria-java-part1.html">Parte 1 - Gerenciamento de Memória no Java</a></p>

<h1 id="memória-de-um-processo-javajvm-dentro-de-um-container-docker">Memória de um Processo Java/JVM dentro de um <em>container Docker</em></h1>

<p>Vimos que a JVM usa a memória total do sistema para calcular alguns parâmetros e limites, incluindo o limite de uso
do grupo <em>Heap</em>. Na medida em que o uso de <em>containers Docker</em> para rodar serviços Java se popularizou, essa
abordagem de usar o valor do total de memória do sistema para calcular alguns parâmetros se mostrou problemática.
Isso aconteceu porque os limites de memória e recursos impostos pelo <em>Docker</em> são direcionados para impor limites
nas chamadas do sistema, sem afetar as rotinas que fornecem informações sobre o <em>hardware</em> do sistema, como memória
total do computador.</p>

<p>Essa mudança exigiu uma abordagem ativa dos processos para que fossem compatíveis com <em>Docker</em>, ou seja, é esperado
que o processo detecte se está rodando dentro de um <em>container Docker</em> limitado, e faça o tratamento necessário
dos limites impostos pelo container.</p>

<p>A funcionalidade de detecção e suporte ao <em>Docker</em> foi adicionada ao Java na <strong>versão 8 release 181</strong>. Antes desta
versão, a JVM rodando dentro de um <em>container Docker</em> não era capaz de entender os limites impostos pelo <em>Docker</em>,
considerando a memória total do sistema e outros parâmetros sem levar em conta os limites configurados. Então,
por exemplo, considerando um computador com 4Gb de RAM rodando <em>Docker</em>, com uma instância Java rodando em um
<em>container Docker</em> com limite de memória de 512Mb e com uma versão sem suporte a <em>Docker</em> (anterior a versão Java 8
<em>release</em> 181), o limite de Heap seria ajustado para 1Gb de RAM (1/4 da memória total, que é 4Gb), e quando o
consumo de memória do processo Java ultrapassasse 512Mb o processo seria finalizado pelo serviço <em>Docker</em>, com o
código de erro padrão de retorno 137 para indicar que o processo ultrapassou os limites definidos pelo <em>container</em>.</p>

<p>Cabe salientar que a finalização do <em>container</em> através deste processo não envia sinais nem Exceções ao processo
Java, e dessa forma nenhuma <em>Exception</em> é executada a nível do código Java, que é simplesmente finalizado sem mais
informações.</p>

<p>Contudo, para as versões de JVM compatíveis com <em>Docker</em> (Java versão 8 <em>release</em> 181 e posteriores), o limite de
<em>Heap</em> é ajustado corretamente conforme esperado, acompanhando a configuração de limite de memória imposta pelo
<em>container Docker</em>. Abaixo podemos acompanhar um exemplo da variação do limite de <em>Heap</em> conforme o limite de memória
do <em>container</em>:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Memória do Docker (parâmetro <code class="language-plaintext highlighter-rouge">-m</code>)</th>
      <th style="text-align: center"><em>MaxHeapSize</em></th>
      <th style="text-align: center">Diferença</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">128Mb</td>
      <td style="text-align: center">64Mb</td>
      <td style="text-align: center">64Mb</td>
    </tr>
    <tr>
      <td style="text-align: center">192Mb</td>
      <td style="text-align: center">96Mb</td>
      <td style="text-align: center">96Mb</td>
    </tr>
    <tr>
      <td style="text-align: center">256Mb</td>
      <td style="text-align: center">126Mb</td>
      <td style="text-align: center">130Mb</td>
    </tr>
    <tr>
      <td style="text-align: center">384Mb</td>
      <td style="text-align: center">126Mb</td>
      <td style="text-align: center">258Mb</td>
    </tr>
    <tr>
      <td style="text-align: center">512Mb</td>
      <td style="text-align: center">128Mb</td>
      <td style="text-align: center">384Mb</td>
    </tr>
    <tr>
      <td style="text-align: center">768Mb</td>
      <td style="text-align: center">192Mb</td>
      <td style="text-align: center">576Mb</td>
    </tr>
    <tr>
      <td style="text-align: center">1024Mb</td>
      <td style="text-align: center">256Mb</td>
      <td style="text-align: center">768Mb</td>
    </tr>
  </tbody>
</table>

<p>Nota: dados extraídos usando a imagem <code class="language-plaintext highlighter-rouge">openjdk:8u275-jre-slim</code></p>

<p>No gráfico acima também podemos acompanhar a diferença entre o total de memória do <em>container</em> e o limite de <em>Heap</em>,
que na prática será o espaço de memória onde será alocado o <em>Metaspace</em> e seus componentes, bem como o <em>Stack</em>, e
demais módulos específicos de cada JVM. Podemos derivar as seguintes conclusões a partir do gráfico:</p>

<ul>
  <li>O valor do limite do <em>Heap</em> pode ser usado para configurar a quantidade de memória alocada para o <em>Heap</em> e para
o <em>Metaspace</em> dentro do <em>container</em>.</li>
  <li>A partir de um <em>container</em> com 256Mb, a configuração automática do limite do <em>Heap</em> vai alocar cada vez mais
memória para o <em>Metaspace</em> e pouco para o <em>Heap</em>, e isso pode resultar em um desperdício de memória que poderia
ser alocada para o <em>Heap</em> e estaria disponível dentro do Java para armazenamento de variáveis e objetos.</li>
</ul>

<p>Em vista de tudo isso, a conclusão mais forte de todas é: quase nunca a configuração automática de <em>Heap</em> de um
processo Java dentro de um <em>container Docker</em> será a melhor configuração possível de alocação de memória. As
configurações automáticas, em geral, vão variar entre alguma das seguintes situações:</p>

<ul>
  <li>Desperdício de memória que não está alocada para o <em>Heap</em> e não está em uso pelo <em>Metaspace</em>.</li>
  <li>Memória insuficiente alocada para o <em>Heap</em>, causando <em>Exception</em> de <em>OutOfMemoryError</em> durante a execução.</li>
  <li>Memória insuficiente alocada para o <em>Metaspace/Stack</em>, causando finalização abrupta do <em>container</em> por erro
137 (limite excedido) do <em>Docker</em>.</li>
</ul>

<h1 id="como-configurar-a-memória-do-java-em-um-container-docker">Como Configurar a Memória do Java em um <em>Container Docker</em>?</h1>

<p>A maneira mais simples de configurar os parâmetros de memória de um processo Java rodando em <em>containers Docker</em>
é através do monitoramento do consumo de memória do processo durante um teste de carga que exercite o processo
com os principais casos de uso do componente, usando, por exemplo, uma ferramenta de <em>Profiling</em> como o
<a href="https://visualvm.github.io/">VisualVM</a> (<em>open source</em>) ou o
<a href="https://www.ej-technologies.com/products/jprofiler/overview.html">JProfiler</a> (comercial). Através da ferramenta,
é possível acompanhar a variação dos valores alocado/máximo para o <em>Heap</em> e também para o <em>Metaspace</em> do processo,
obtendo um <em>footprint</em> do consumo de memória que pode ser usado para ajustar o limite de <em>Heap</em> e por consequência
o espaço disponível para o <em>Metaspace</em>.</p>

<p>Contudo, nem sempre é possível ou está disponível um teste de carga para que seja feito o <em>Profiling</em> do processo
Java, e nestes casos, algumas heurísticas simples podem ser usadas de forma a se obter valores iniciais para a
configuração de memória.</p>

<p>Uma das abordagens heurísticas mais simples para definição da memória de um processo Java rodando dentro de um
<em>container Docker</em> envolve a simplificação dos espaços de memória do processo em dois grupos, <em>Heap</em> e <em>Metaspace</em>
(considerando o <em>Metaspace</em> como contendo também o <em>Stack</em>), calculando um valor aproximado do consumo de <em>Metaspace</em>,
e ajustando o valor do limite do <em>Heap</em> de forma a dividir os espaços conforme o cálculo aproximado. A seguir
temos o resumo da abordagem:</p>

<ul>
  <li>Calcular um valor aproximado do consumo de <em>Metaspace</em>, como sendo entre 3 a 4 vezes o tamanho do conteúdo do
pacote Jar (deve ser um <a href="https://pt.stackoverflow.com/questions/400626/o-que-%C3%A9-fat-jar"><em>fat jar</em></a> - um
pacote Jar contendo a aplicação e também todas as suas dependências). Como o pacote Jar é um arquivo comprimido
do tipo ZIP, deve ser consultado o tamanho do conteúdo descomprimido, ou então pode ser feita uma simplificação e
considerado entre 6 a 8 vezes o tamanho do arquivo do pacote Jar. Cabe ressaltar que a melhor abordagem é
consultar o tamanho do conteúdo, posto que existem processos de criação de pacotes Jar que não aplicam
compressão ZIP, e nesse caso pode tornar os valores muito imprecisos.</li>
  <li>Calcular o limite do <em>Heap</em> como sendo a diferença entre a memória total do <em>container</em> e o tamanho do <em>Metaspace</em>
calculado acima.</li>
  <li>Ajustar o limite do <em>Heap</em> no processo Java dentro do <em>container</em> usando o parâmetro <code class="language-plaintext highlighter-rouge">-Xmx</code></li>
  <li>Esta abordagem é boa para processos que não lançam muitas <em>threads</em> ou que têm um limite no número total de 
<em>threads</em> em uso, já que o valor do <em>Stack</em> não é levado em conta nos cálculos.</li>
  <li>Processos que usam muitas <em>threads</em>, ou que possuem <code class="language-plaintext highlighter-rouge">ThreadPools</code> muito grandes ou sem limites, ou <em>listeners</em> de
API HTTP sem limite de requisições paralelas, podem sofrer interrupções do <em>container Docker</em> por erro 137 na
presença de muitas <em>threads</em> em execução, por exemplo em situações de picos de conexões de requisições HTTP.</li>
</ul>

<p>Para os casos em que o processo Java usa muitas <em>threads</em> ou pode ter picos de consumo de <em>threads</em>, a abordagem
heurística recomendada pode ser levemente alterada para levar em conta o consumo do <em>Stack</em>, conforme segue:</p>

<ul>
  <li>Calcular um valor aproximado do consumo de <em>Metaspace</em>, conforme a abordagem anterior.</li>
  <li>Calcular um consumo aproximado de <em>Stack</em>, considerando um valor entre 512Kb e 1Mb para cada <em>thread</em> paralela
que se deseja que possa estar executando ao mesmo tempo. Considerando um <em>listener</em> de requisições HTTP em que se
deseja que até 128 requisições possam ser atendidas em paralelo, o valor de memória considerado para o <em>Stack</em>
seria entre 64Mb e 128Mb. Cabe salientar que, se o <code class="language-plaintext highlighter-rouge">ThreadPool</code> do <em>listener</em> HTTP não tiver limite de <em>threads</em>,
um pico de requisições poderá ultrapassar o número previsto e resultar na interrupção do container por erro 137.</li>
  <li>Calcular o limite do <em>Heap</em> como sendo a diferença entre a memória total do <em>container</em> e a soma do tamanho
do <em>Metaspace</em> e do <em>Stack</em>.</li>
  <li>Ajustar o limite do <em>Heap</em> no processo Java dentro do <em>container</em> usando o parâmetro <code class="language-plaintext highlighter-rouge">-Xmx</code></li>
  <li>Esta abordagem é boa para processos que podem lançar muitas <em>threads</em>, de forma a levar em conta a quantidade
de <em>threads</em> no cálculo dos limites de memória.</li>
</ul>

<p>Seguindo as abordagens heurísticas descritas acima, será possível obter valores iniciais razoavelmente interessantes
para a configuração de memória de um processo Java dentro de <em>container Docker</em>. Ainda assim, o processo Java poderá
sofrer <code class="language-plaintext highlighter-rouge">Exceptions</code> e interrupções por falta de memória, e seguindo as abordagens descritas, podemos enumerar as
ações recomendadas a serem tomadas:</p>

<ul>
  <li>Caso uma <em>thread</em> receba uma <code class="language-plaintext highlighter-rouge">Exception</code> do tipo <code class="language-plaintext highlighter-rouge">OutOfMemoryError</code>: este caso indica que o processo Java tentou
manipular objetos muito grandes ou uma quantidade grande de objetos, associados a um limite de <em>Heap</em> insuficiente,
e neste caso a ação corretiva deve ser recalcular os valores de forma a fornecer mais memória <em>Heap</em> através do
parâmetro <code class="language-plaintext highlighter-rouge">-Xmx</code>. Talvez seja necessário alterar também o limite de memória total do <em>container</em>, já que alterar
somente o limite do <em>Heap</em> vai interferir com o valor alocado para o <em>Metaspace vs Heap</em>.</li>
  <li>Caso o processo seja interrompido por um erro 137 do <em>Docker</em>: este caso indica que a área de <em>Metaspace/Stack</em>
do processo Java cresceu além do tamanho pré-estabelecido, por exemplo durante um pico de uso de <em>threads</em> ou
outra situação de uso excessivo de <em>Metaspace</em>. Neste caso, a ação corretiva é refazer os cálculos para aumentar
a área de memória alocada para o <em>Metaspace</em>. Talvez seja necessário alterar também o limite de memória total
do <em>container</em>, já que alterar somente o limite do <em>Heap</em> vai interferir com o valor alocado para
o <em>Metaspace vs Heap</em>.</li>
</ul>

<h1 id="pontos-de-atenção-sobre-o-consumo-de-memória-no-java">Pontos de Atenção sobre o Consumo de Memória no Java</h1>

<ul>
  <li>No Java 8 e anteriores, um <code class="language-plaintext highlighter-rouge">String</code> ocupa o dobro do espaço em bytes, já que é armazenado no formato UTF-16
(dois bytes por caractere). No Java 9 e posteriores, o <code class="language-plaintext highlighter-rouge">String</code> é armazenado como UTF-8 (1 byte por caractere)
caso não contenha caracteres especiais (acentos, emoji, etc), e o mesmo do Java 8 caso contrário.</li>
  <li>Os processos de serialização/deserialização podem exigir até várias vezes a quantidade de memória da instância
em questão, já que várias conversões de dados devem ser feitas, como por exemplo na cadeia de conversão
entre <em>buffer</em> de rede do SO / <em>buffer</em> de memória nativa no Java (<em>Metaspace</em>) / vetor de bytes (<em>Heap</em>)
/ <code class="language-plaintext highlighter-rouge">String</code> (<em>Heap</em>) / Objeto desserializado (<em>Heap</em>). Isso significa, por exemplo, que um processo Java com
limite de <em>Heap</em> de 128Mb não é capaz de desserializar com sucesso um payload de rede de 64Mb no exemplo acima.</li>
  <li>O <em>stack</em> de uma <em>thread</em> nunca usada em um <code class="language-plaintext highlighter-rouge">ThreadPool</code> praticamente não utiliza memória, já que as páginas
virtuais ainda não foram alocadas para o <em>Stack</em>. Contudo, uma <em>thread</em> já usada e que retorna para o <code class="language-plaintext highlighter-rouge">ThreadPool</code>
segue ocupando o <em>Stack</em> até que seja finalizada. Esta condição pode ser relevante em casos em que
um <code class="language-plaintext highlighter-rouge">ThreadPool</code> com muitas <em>threads</em> pode ter todas elas ocupando muito espaço de <em>Stack</em>, mesmo que não haja
um pico de <em>threads</em>, mas na condição em que todas tiverem sido utilizadas pelo menos uma vez.</li>
</ul>

<h1 id="referências">Referências</h1>

<ul>
  <li>
    <p><a href="https://blog.softwaremill.com/docker-support-in-new-java-8-finally-fd595df0ca54"><em>Docker support in Java 8 — finally!</em></a></p>
  </li>
  <li>
    <p><a href="https://developers.redhat.com/blog/2017/03/14/java-inside-docker/"><em>Java inside docker: What you must know to not FAIL</em></a></p>
  </li>
  <li>
    <p><a href="https://visualvm.github.io/"><em>VisualVM - All-in-One Java Troubleshooting Tool</em></a></p>
  </li>
  <li>
    <p><a href="https://www.ej-technologies.com/products/jprofiler/overview.html"><em>JProfiler - “THE AWARD-WINNING ALL-IN-ONE JAVA PROFILER”</em></a></p>
  </li>
  <li>
    <p><a href="https://cf-docs.jp-east-1.paas.cloud.global.fujitsu.com/en/manual/overview/overview/topics/t-fjbp-tuning.html"><em>Tuning Java heap size, metaspace size and other such items</em></a></p>
  </li>
  <li>
    <p><a href="https://stackoverflow.com/questions/31455644/should-i-set-a-maxmetaspacesize"><em>Should I set a MaxMetaspaceSize?</em></a></p>
  </li>
  <li>
    <p><a href="https://pt.stackoverflow.com/questions/400626/o-que-%C3%A9-fat-jar">O que é Fat JAR?</a></p>
  </li>
  <li>
    <p><a href="https://stackoverflow.com/questions/11947037/what-is-an-uber-jar"><em>What is an uber jar?</em></a></p>
  </li>
</ul>]]></content><author><name>Jose Ferreira</name></author><summary type="html"><![CDATA[Nesta segunda parte do artigo sobre Gerenciamento de Memória no Java, veremos alguns aspectos mais específicos do Gerenciamento de Memória do Java quando estiver rodando dentro de containers Docker, bem como algumas abordagens para configuração de memória, e também dicas de como tratar os problemas de memória mais comuns.]]></summary></entry><entry xml:lang="pt"><title type="html">Gerenciamento de Memória no Java</title><link href="https://engineering.axur.com/2020/12/17/gerenciamento-memoria-java-part1.html" rel="alternate" type="text/html" title="Gerenciamento de Memória no Java" /><published>2020-12-17T13:40:00+00:00</published><updated>2020-12-17T13:40:00+00:00</updated><id>https://engineering.axur.com/2020/12/17/gerenciamento-memoria-java-part1</id><content type="html" xml:base="https://engineering.axur.com/2020/12/17/gerenciamento-memoria-java-part1.html"><![CDATA[<p>O gerenciamento automático de memória do Java pode esconder do engenheiro alguns detalhes que podem ser relevantes em 
certos casos de uso, como no desenvolvimento de Microsserviços e também de aplicações <em>Serverless</em>. Abordaremos neste 
artigo alguns aspectos do gerenciamento de memória do Java que podem ser relevantes para o desenvolvimento de serviços
e aplicações headless. Para o desenvolvimento de aplicações interativas com UI, outras abordagens podem ser
necessárias/relevantes.</p>

<p>Este artigo está divido em duas partes. Nesta primeira parte, veremos alguns aspectos mais gerais do
Gerenciamento de Memória do Java. Na segunda parte, a ser publicada nas próximas semanas, abordaremos aspectos
sobre o Gerenciamento de Memória do Java rodando em <em>containers Docker</em>.</p>

<p>Para acessar a segunda parte do artigo, utilize o link abaixo:</p>

<p><a href="/2020/12/28/gerenciamento-memoria-java-part2.html">Parte 2 - Gerenciamento de Memória no Java - Containers Docker</a></p>

<h1 id="memória-de-um-processo">Memória de um Processo</h1>

<p>Tomaremos como base a plataforma Linux, que apesar de semelhante em muitos aspectos ao Windows, é uma das plataformas
mais fáceis para deploy de aplicações não-interativas. No Linux, a memória de um processo pode ser dividida em 3
grandes classes:</p>

<ul>
  <li><em>Data</em>: dados manipulados pelo processo</li>
  <li><em>Text</em> ou <em>code</em>: memória onde o código de máquina/executável do programa é armazenado</li>
  <li><em>Stack</em>: memória reservada para uso do <em>stack</em> do programa</li>
</ul>

<p>Olhando do ponto de vista de um programa clássico em C, teremos o seguinte mapeamento:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Tipo de dados C</th>
      <th style="text-align: center">Local no processo</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><em>Stack</em>, chamadas entre funções (por <em>thread</em>)</td>
      <td style="text-align: center"><em>Stack</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Inicialização de variáveis/estruturas</td>
      <td style="text-align: center"><em>Data</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Variáveis globais - <em>Heap</em></td>
      <td style="text-align: center"><em>Data</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Variáveis locais - <em>Stack</em></td>
      <td style="text-align: center"><em>Stack</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Memória dinâmica - <em>malloc</em></td>
      <td style="text-align: center"><em>Data</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Constantes</td>
      <td style="text-align: center">Geralmente <em>Data</em>, às vezes <em>Text</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Código executável</td>
      <td style="text-align: center"><em>Text</em></td>
    </tr>
  </tbody>
</table>

<p>O total de memória consumido por um processo pode ser aproximado de maneira prática para a soma da memória utilizada
pelas 3 grandes classes, ou então pela soma da memória utilizada pelos tipos de dados se o processo for analisado sob
a ótica de um programa clássico C.</p>

<h1 id="memória-de-um-processo-javajvm">Memória de um Processo Java/JVM</h1>

<p>Apesar de todos os níveis de abstração e gerenciamentos automáticos, o gerenciamento de memória de um programa Java
também pode ser avaliado sob a ótica do gerenciamento de memória de um programa C. Inclusive as principais Máquinas
Virtuais Java são escritas em C++, incluindo partes em <em>assembler</em> e outras linguagens.</p>

<p>Dessa forma, a memória de um processo Java pode ser separada nos seguintes grandes grupos:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Recurso da JVM</th>
      <th style="text-align: center">Grupo de memória JVM</th>
      <th style="text-align: center">Local no processo</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">Variáveis - <em>Heap</em></td>
      <td style="text-align: center"><em>Heap</em></td>
      <td style="text-align: center"><em>Data</em></td>
    </tr>
    <tr>
      <td style="text-align: center"><em>Byte code</em> das classes</td>
      <td style="text-align: center"><em>Metaspace</em></td>
      <td style="text-align: center"><em>Data</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Código executável JIT</td>
      <td style="text-align: center"><em>Metaspace</em></td>
      <td style="text-align: center">Seção especial executável</td>
    </tr>
    <tr>
      <td style="text-align: center">Código executável da JVM</td>
      <td style="text-align: center"><em>Metaspace</em></td>
      <td style="text-align: center"><em>Text</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Dados da JVM</td>
      <td style="text-align: center"><em>Metaspace</em></td>
      <td style="text-align: center"><em>Data</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Memória nativa/dinâmica</td>
      <td style="text-align: center"><em>Metaspace</em></td>
      <td style="text-align: center"><em>Data</em></td>
    </tr>
    <tr>
      <td style="text-align: center"><em>Stack</em> (por <em>thread</em>)</td>
      <td style="text-align: center"><em>Stack</em></td>
      <td style="text-align: center"><em>Stack</em></td>
    </tr>
  </tbody>
</table>

<p>Dependendo da implementação da JVM, este mapeamento pode ser alterado de forma a obter diferentes níveis de
desempenho/flexibilidade durante a execução. Seguiremos com a análise considerando um mapeamento como o exposto acima,
e eventuais diferenças podem ser ajustadas seguindo a abordagem apresentada.</p>

<p>Por padrão, a JVM clássica ajusta um limite inicial de 1/4 da memória total do sistema para o grupo <em>Heap</em> do processo
Java, deixando livre o tamanho dos outros grupos (na verdade, os outros grupos também possuem limites iniciais, mas
estes são ajustados com muita folga, então podem ser considerados sem limites para todos os efeitos práticos).
O valor de limite de 1/4 para o grupo <em>Heap</em> também pode variar, dependendo da quantidade de memória do sistema:
para valores de memória do sistema abaixo de 512Mb de RAM, o limite do grupo Heap é ajustado entre 1/4 (512Mb) e
1/2 (128Mb) do total, em vez de fixo em 1/4.</p>

<p>Dessa forma, ao executar um processo Java em um computador com 8Gb de RAM, a JVM vai ajustar um limite no grupo
<em>Heap</em> de 2Gb de RAM. Se o programa Java tentar alocar um conjunto de variáveis maior do que 2Gb de RAM, será
disparada uma <em>Exception</em> do tipo <strong>java.lang.OutOfMemoryError</strong> no contexto da thread que está tentando ultrapassar o
limite de alocação de memória. Cabe salientar que, antes de disparar a Exception, a JVM ainda tem uma chance de
tentar liberar memória que não está mais em uso, através da execução do
<a href="https://blog.mandic.com.br/artigos/java-garbage-collection-melhores-praticas-tutoriais-e-muito-mais/"><em>Garbage Collector</em></a>.</p>

<p>Para controlar explicitamente o limite de memória do grupo Heap, pode ser usado o parâmetro <code class="language-plaintext highlighter-rouge">-Xmx</code> da JVM:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Parâmetro</th>
      <th style="text-align: center">Resultado</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">-Xmx1G</code> <code class="language-plaintext highlighter-rouge">-Xmx1g</code></td>
      <td style="text-align: center">Ajusta o limite do grupo <em>Heap</em> para 1Gb de RAM</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">-Xmx256M</code> <code class="language-plaintext highlighter-rouge">-Xmx256m</code></td>
      <td style="text-align: center">Ajusta o limite do grupo <em>Heap</em> para 256Mb de RAM</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">-Xmx256</code></td>
      <td style="text-align: center">Ajusta o limite para 256 bytes - provavelmente um erro!</td>
    </tr>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">java -Xmx512M -jar programa.jar</code></td>
      <td style="text-align: center">Executa o <em>programa.jar</em> com o grupo <em>Heap</em> limitado a 512Mb de RAM</td>
    </tr>
  </tbody>
</table>

<p>Para verificar o limite do grupo Heap (e outros parâmetros de configuração da JVM) pode ser usado o comando a seguir:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ java -XX:+PrintFlagsFinal -version | grep MaxHeap
  uintx MaxHeapFreeRatio                          = 100                             
  uintx MaxHeapSize                              := 4169138176                      
 openjdk version "1.8.0_265"
 OpenJDK Runtime Environment (build 1.8.0_265-b01)
 OpenJDK 64-Bit Server VM (build 25.265-b01, mixed mode)
</code></pre></div></div>

<h4 id="exemplo-1">Exemplo 1</h4>

<ul>
  <li>Um processo Java está em execução com limite do grupo <em>Heap</em> de 1000Mb de RAM.</li>
  <li>No <em>Heap</em> do processo existem 800Mb alocados em dados de variáveis, sendo que 500Mb dessas variáveis já não
estão mais em uso (por exemplo, o método já retornou, ou não existem mais referências para elas).</li>
  <li>Uma <em>thread</em> tenta alocar um vetor de 600Mb. Neste momento, o processo precisa aumentar o tamanho do <em>Heap</em> para
1400Mb, sendo 800Mb (tamanho atual) + 600Mb (nova alocação). A alocação falha porque o tamanho ultrapassa o limite
de 1000Mb.</li>
  <li>Em resposta a falha de alocação, o gerenciador de memória do Java executa o <em>Garbage Collector</em>.
O <em>Garbage Collector</em> analisa as referências das variáveis alocadas, e detecta que 500Mb dessas variáveis já não
estão mais em uso, e então libera a memória alocada por estas variáveis.</li>
  <li>O <em>Heap</em> do processo, com tamanho alocado de 800Mb, agora possui 500Mb de espaço livre, e neste momento pode
atender com sucesso a requisição da <em>thread</em> para alocar um vetor de 600Mb, através do aumento do tamanho
do <em>Heap</em> para 900Mb.</li>
</ul>

<h4 id="exemplo-2">Exemplo 2</h4>

<ul>
  <li>Um processo Java está em execução com limite do grupo <em>Heap</em> de 1000Mb de RAM.</li>
  <li>No <em>Heap</em> do processo existem 800Mb alocados em dados de variáveis, sendo que 100Mb dessas variáveis já não estão
mais em uso (por exemplo, o método já retornou, ou não existem mais referências para elas).</li>
  <li>Uma <em>thread</em> tenta alocar um vetor de 600Mb. Neste momento, o processo precisa aumentar o tamanho do <em>Heap</em>
para 1400Mb, sendo 800Mb (tamanho atual) + 600Mb (nova alocação).
A alocação falha porque o tamanho ultrapassa o limite de 1000Mb.</li>
  <li>Em resposta a falha de alocação, o gerenciador de memória do Java executa o <em>Garbage Collector</em>.
O <em>Garbage Collector</em> analisa as referências das variáveis alocadas, e detecta que 100Mb dessas variáveis já não
estão mais em uso, e então libera a memória alocada por estas variáveis.</li>
  <li>O <em>Heap</em> do processo, com tamanho alocado de 800Mb, agora possui 100Mb de espaço livre, e neste momento ainda
não é possível atender com sucesso a requisição da <em>thread</em> para alocar um vetor de 600Mb.</li>
  <li>Como ainda não é possível atender a requisição de alocação de memória, a requisição é considerada como uma falha
de alocação de memória.</li>
  <li>A JVM dispara a <em>Exception</em> do tipo <code class="language-plaintext highlighter-rouge">OutOfMemoryError</code> no contexto da thread que está tentando alocar a memória
sem sucesso. Caso seja a única <em>thread</em> do processo e a <em>Exception</em> não seja tratada, o processo é encerrado com a
mensagem de erro de falta de memória.</li>
</ul>

<p>O uso do <em>Heap</em> em um processo Java é o principal recurso sob controle do desenvolvedor, mas a memória alocada
pelos outros recursos também pode variar ou ser controlada direta/indiretamente através de algumas das seguintes
atividades:</p>

<ul>
  <li><em>Byte code</em> das classes: o espaço alocado é proporcional ao tamanho e quantidade de classes usada pelo projeto,
e pode expandir durante a geração dinâmica de código, por exemplo, via atividade de
<a href="https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_orientada_a_aspecto"><em>Aspect-oriented Programming</em></a>.</li>
  <li>Código executável JIT: depende muito da abordagem de cada tipo de JVM, mas é uma área que não varia muito em
tamanho durante a execução do processo.</li>
  <li>Código executável da JVM: código executável fixo que compõe a parte executável da própria JVM, juntamente com
o código das bibliotecas do sistema operacional usadas, e em geral não varia durante a execução.</li>
  <li>Dados da JVM: dados internos/de controle da própria JVM, não variam muito durante a execução.</li>
  <li>Memória nativa/dinâmica: memória que pode ser alocada por chamadas nativas, e/ou usada por bibliotecas e outros
componentes nativos, além de <em>buffers</em> para IO. Pode variar bastante dependendo da interação entre o processo
e o sistema operacional, por exemplo, no acesso à rede e ao disco.</li>
  <li><em>Stack</em>: o espaço alocado é proporcional à quantidade de <em>threads</em> criadas/em uso pelo processo, incluindo
<em>threads</em> pré-alocadas em <code class="language-plaintext highlighter-rouge">ThreadPools</code>. A profundidade de chamadas também afeta negativamente o uso de memória.</li>
</ul>

<h1 id="metaspace-em-um-processo-java"><em>Metaspace</em> em um processo Java</h1>

<p>O conceito de <em>Metaspace</em> foi criado a partir da versão 8 do Java, e substituiu o conceito de <code class="language-plaintext highlighter-rouge">PermGen</code>,
que era uma área reservada para dados permanentemente alocados da JVM (em contraste ao <em>Heap</em>).
Em geral, a documentação das JVMs não deixa claro o exato tamanho e composição do <em>Metaspace</em> e quais componentes
de fato são alocados dentro deste espaço de memória. Uma simplificação comumente usada para um processo Java é
de que o espaço alocado pelo <em>Metaspace</em> é a diferença entre a memória total ocupada e o valor alocado pelo <em>Heap</em>.</p>

<p>Essa simplificação nem sempre é correta, ainda mais se levarmos em conta o espaço ocupado pelo <em>Stack</em>, que
supostamente não faz parte do <em>Metaspace</em>. Contudo, essa simplificação ajuda a obter uma visão panorâmica geral
do uso de memória de um processo Java, bem próxima do uso real, e ainda ajuda a simplificar a complexidade das
diversas áreas especiais de memória do processo.</p>

<p>Assim como o <em>Heap</em>, o <em>Metaspace</em> também pode ser limitado através do parâmetro <code class="language-plaintext highlighter-rouge">-XX:MaxMetaspaceSize</code> da JVM
clássica. Contudo, diferentemente do <em>Heap</em>, que está diretamente sob controle do desenvolvedor e do código em
execução, a natureza da memória alocada pelo <em>Metaspace</em>, em geral, não pode ser determinada facilmente sem o uso
de <em>Profiling</em> de Memória ou outras ferramentas de instrumentação que ajudem a mostrar os valores médios de
memória em uso pelo <em>Metaspace</em> durante o processo em teste de carga, e por isso mesmo a configuração padrão
do parâmetro é “sem limite”.</p>

<p>Mesmo assim, uma das principais características do <em>Metaspace</em> é que seu tamanho cresce até determinado ponto
em que todos os módulos envolvidos foram ativados e usados algumas vezes, e depois seu valor estabiliza em certo
patamar que, normalmente, não é ultrapassado, a menos que haja um <em>leak</em> de uso de algum componente presente no
<em>Metaspace</em> (por exemplo a criação dinâmica de código/classes).</p>

<h1 id="próximos-passos">Próximos Passos</h1>

<p>Na parte 2 deste artigo, veremos alguns detalhes do comportamento do gerenciamento de memória de um processo Java
rodando dentro de <em>containers Docker</em>, bem como algumas abordagens para configuração de memória, e também dicas
de como tratar os problemas de memória mais comuns.</p>

<p>Para acessar a segunda parte do artigo, utilize o link abaixo:</p>

<p><a href="/2020/12/28/gerenciamento-memoria-java-part2.html">Parte 2 - Gerenciamento de Memória no Java - Containers Docker</a></p>

<h1 id="referências">Referências</h1>

<ul>
  <li>
    <p><a href="https://cf-docs.jp-east-1.paas.cloud.global.fujitsu.com/en/manual/overview/overview/topics/t-fjbp-tuning.html"><em>Tuning Java heap size, metaspace size and other such items</em></a></p>
  </li>
  <li>
    <p><a href="https://stackoverflow.com/questions/31455644/should-i-set-a-maxmetaspacesize"><em>Should I set a MaxMetaspaceSize?</em></a></p>
  </li>
</ul>]]></content><author><name>Jose Ferreira</name></author><summary type="html"><![CDATA[O gerenciamento automático de memória do Java pode esconder do engenheiro alguns detalhes que podem ser relevantes em certos casos de uso, como no desenvolvimento de Microsserviços e também de aplicações Serverless. Abordaremos neste artigo alguns aspectos do gerenciamento de memória do Java que podem ser relevantes para o desenvolvimento de serviços e aplicações headless. Para o desenvolvimento de aplicações interativas com UI, outras abordagens podem ser necessárias/relevantes.]]></summary></entry><entry xml:lang="pt"><title type="html">Pilares técnicos da engenharia de software na Axur</title><link href="https://engineering.axur.com/2020/07/08/pilares-tecnicos.html" rel="alternate" type="text/html" title="Pilares técnicos da engenharia de software na Axur" /><published>2020-07-08T03:00:00+00:00</published><updated>2020-07-08T03:00:00+00:00</updated><id>https://engineering.axur.com/2020/07/08/pilares-tecnicos</id><content type="html" xml:base="https://engineering.axur.com/2020/07/08/pilares-tecnicos.html"><![CDATA[<p>Na Axur, o time de Engineering tem a missão de construir a tecnologia de monitoramento e reação contra riscos digitais que nos posiciona na liderança do mercado brasileiro e nos permite tornar a internet um lugar mais seguro. O desafio imposto por um ambiente online propício a ameaças e fraudes em constante evolução exige uma grande capacidade de entrega de software.</p>

<p>Além de colaborar na busca de soluções inovadoras, precisamos garantir que elas possam ser transformadas em código e integradas aos nossos produtos com rapidez e segurança. Mas não ficamos satisfeitos com qualquer código, que apenas <em>funcione</em>: além de “funcionar”, cumprindo requisitos de produto, o nosso código deve também atender às demandas dos próprios times de desenvolvimento e ajudá-los a atingir uma alta produtividade de forma constante.</p>

<p>Sem perder o foco no presente, pensamos também <em>in the long run</em> (que, aliás, é um de nossos valores culturais). Queremos estar prontos para os desafios do futuro. Poderemos comportar 10 vezes mais desenvolvedores, sem causar gargalos no trabalho de cada um e sem perder o controle sobre os riscos? Poderemos processar 1000 vezes mais informação do que processamos hoje, sem levar a uma explosão desproporcional de custos com infraestrutura?</p>

<p>Para que pudéssemos responder “sim” a essas e outras questões, construímos as fundações da nossa tecnologia sobre três pilares sólidos: entrega contínua, microsserviços autônomos e infraestrutura como código. Além de conjuntamente sustentarem as qualidades que almejamos (como segurança, escalabilidade, rapidez de entrega e outras), cada um desses pilares dá suporte também aos outros dois, gerando uma correlação virtuosa.</p>

<h1 id="entrega-contínua">Entrega contínua</h1>

<p>Entrega contínua (<a href="https://continuousdelivery.com/"><em>continuous delivery</em></a>) é a prática de desenvolver software em pequenos incrementos de funcionalidade, mantendo-o sempre pronto a ser disponibilizado aos usuários finais com as mudanças mais recentes.</p>

<p>Além de times de desenvolvimento disciplinados na prática de <em>integração</em> contínua, o nível de <em>entrega</em> contínua requer também a automação completa do processo de entrega do software. O <a href="https://www.informit.com/articles/article.aspx?p=1621865"><em>deployment pipeline</em></a> - que é a manifestação automatizada desse processo - surge para guiar cada mudança aplicada ao código (<em>commit</em>) por uma série de etapas que incluem compilação e geração de artefatos executáveis, testes e inspeções, validações em ambientes internos e finalmente a instalação em ambiente de produção.</p>

<p>Como benefício mais tangível, o tempo entre o commit e o deployment em produção é reduzido drasticamente, de semanas (ou em alguns casos mais graves até meses) para poucas horas ou minutos. Deployments deixam de ser eventos críticos, carregados de tensão e riscos e agendados com antecedência, e passam a ser corriqueiros. Na Axur, fazemos dezenas de deployments por dia com todo tipo de melhorias, de funcionalidades de interface a novas fontes de detecção de ameaças.</p>

<h1 id="microsserviços-autônomos">Microsserviços autônomos</h1>

<p>Arquiteturas de microsserviços recebem bastante atenção há alguns anos. Apesar do <em>hype</em> inicial, que em alguns casos levou à sua aplicação irrefletida a problemas que comportariam soluções mais simples e diretas, seus benefícios em cenários adequados puderam emergir e se consolidar. A transição arquitetural da Axur para microsserviços teve início no final de 2016 e progrediu gradualmente, com a aplicação do padrão de <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler">estrangulamento</a> do sistema legado. Ao entrarmos no segundo semestre de 2020 estamos concluindo as últimas etapas desse processo.</p>

<p>Temos opiniões formadas sobre o que queremos da nossa rede de microsserviços, baseadas em aprendizados e conhecimentos prévios do domínio dos nossos problemas. Queremos unidades verdadeiramente autônomas, desde deployment e recursos de infraestrutura até a capacidade de atender a requisições sem depender de integrações síncronas com outros microsserviços. Para isso, baseamos nossa arquitetura na <a href="https://aws.amazon.com/event-driven-architecture/">propagação de eventos</a> assíncronos entre contextos, permitindo a cada um formar sua visão interna, local, do estado relevante do sistema. A visão global, portanto, passa a ser <a href="https://cloud.google.com/datastore/docs/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/#what-is-eventual-consistency">eventualmente consistente</a>.</p>

<p>As vantagens dessa abordagem se estendem por todos os estágios do desenvolvimento, da concepção de novos componentes até seu monitoramento contínuo. Torna-se possível fazer grande progresso com ciclos curtos de feedback, em ambientes de desenvolvimento individuais. Já em execução, a propagação de eventos pode ser facilmente rastreada pelo sistema, e requisições externas são sempre atendidas por um único serviço - permitindo a rápida identificação de causas de falhas.</p>

<h1 id="infraestrutura-como-código">Infraestrutura como código</h1>

<p>Como microsserviços, infraestrutura na nuvem há tempos já não é uma novidade. No entanto, são comuns os casos de aproveitamento apenas parcial do que a nuvem, e mais especificamente a <a href="https://azure.microsoft.com/en-us/overview/what-is-iaas/">infraestrutura como serviço</a>, tem de bom para oferecer. No nosso caso, uma capacidade fundamental é usar código para eliminar a necessidade de ferramentas interativas de provisionamento. Todos gostamos de código, então por que faríamos de outra forma?</p>

<p>Como seria esperado, o código responsável pelo gerenciamento de infraestrutura é muito diferente do código de aplicação. Trabalhamos com templates declarativos, que especificam cada recurso necessário em todos os seus detalhes. Bases de dados (relacionais ou não), caches, armazenamento elástico de arquivos - tudo pode ser declarado em código, versionado, testado e reproduzido com confiança em diferentes ambientes.</p>

<h1 id="unindo-as-partes">Unindo as partes</h1>

<p>A arquitetura de microsserviços autônomos beneficia e também é beneficiada pela prática de entrega contínua: por um lado, é impraticável controlar o deployment de dezenas ou centenas de componentes se o processo não for 100% automatizado; no caminho inverso, cada deployment pipeline é simplificado pela limitação de suas atribuições a um contexto isolado e bem definido.</p>

<p>Já a infraestrutura como código é uma necessidade imposta pelos dois primeiros pilares técnicos. Sem a automação do provisionamento de recursos de infraestrutura não seria possível adicionar essa importante responsabilidade ao deployment pipeline, efetivamente impedindo sua aplicação a um grande número de componentes independentes. Cada microsserviço ganha em autonomia por incluir, dentro de seu repositório de código, também seus templates de declaração de dependências de infraestrutura; e, da mesma forma que acontece com o deployment pipeline, cada template ganha em simplicidade por conter apenas o que é preciso para um único microsserviço.</p>

<p>Como um todo, a base técnica fornecida por esses três pilares tem mostrado solidez suficiente para suportar nossas demandas crescentes, mantendo a flexibilidade necessária para comportar e até incentivar mudanças significativas. Durante um período em que o time de Engineering foi expandido e descentralizado (de 6 para 28 colaboradores), conseguimos transformar tanto as práticas de entrega (de deployments agendados com intervalos de 6 a 8 semanas passamos a cerca de 20 deployments por dia) como a organização do próprio sistema (de três grandes componentes executáveis para uma constelação de mais de 100 microsserviços). Além das vantagens evidentes para a Axur e nossos clientes pela maior capacidade e rapidez de ação, a satisfação dos times de desenvolvimento também aumenta com a percepção de progresso contínuo.</p>

<p>Em artigos seguintes, abordaremos em maior profundidade cada um dos temas, com aprendizados acumulados pela Axur durante alguns anos e opções de tecnologias que podem facilitar sua implementação.</p>]]></content><author><name>Rafael Munaretti</name></author><summary type="html"><![CDATA[Na Axur, o time de Engineering tem a missão de construir a tecnologia de monitoramento e reação contra riscos digitais que nos posiciona na liderança do mercado brasileiro e nos permite tornar a internet um lugar mais seguro. O desafio imposto por um ambiente online propício a ameaças e fraudes em constante evolução exige uma grande capacidade de entrega de software.]]></summary></entry></feed>