<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Leonardo Carmo</title>
    <description>The latest articles on DEV Community by Leonardo Carmo (@leocarmo).</description>
    <link>https://dev.to/leocarmo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F385381%2Fb1daa2ab-327b-4b44-a023-f0617bdc4298.png</url>
      <title>DEV Community: Leonardo Carmo</title>
      <link>https://dev.to/leocarmo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leocarmo"/>
    <language>en</language>
    <item>
      <title>Graceful Shutdown com PHP - Evitando data corruption</title>
      <dc:creator>Leonardo Carmo</dc:creator>
      <pubDate>Mon, 10 May 2021 13:36:55 +0000</pubDate>
      <link>https://dev.to/leocarmo/graceful-shutdown-com-php-evitando-data-corruption-2bo3</link>
      <guid>https://dev.to/leocarmo/graceful-shutdown-com-php-evitando-data-corruption-2bo3</guid>
      <description>&lt;p&gt;Quando trabalhamos com processos críticos, seja via &lt;em&gt;script&lt;/em&gt; em &lt;em&gt;background&lt;/em&gt; ou via o fluxo transacional de APIs, temos algo muito importante para considerar: &lt;strong&gt;a possibilidade de interromper um processamento antes que ele termine, causando uma corrupção de dados (data corruption)&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Certo, mas o que isso significa e quais os impactos?
&lt;/h3&gt;

&lt;p&gt;Vamos pensar em um fluxo que conhecemos muito bem hoje: as transações em bancos de dados relacionais. Usamos isso quando precisamos garantir que dados sejam inseridos/alterados/deletados em conjunto, garantindo assim a consistência dos mesmos. Ou seja, se em algum momento da transação algo der errado, realizamos o rollback, caso contrario, o commit da transação.&lt;/p&gt;

&lt;p&gt;Com isso em mente, vamos mudar o contexto para um script em background que precisa realizar uma determinada tarefa com vários steps, normalmente um &lt;code&gt;while(true)&lt;/code&gt; que assina um tópico/fila e fica processando sem tempo definido. Mais conhecido como &lt;em&gt;"moedor de carne"&lt;/em&gt; rs…&lt;/p&gt;

&lt;p&gt;Agora vamos pensar que cada loop demore cerca de 1s para realizar a tarefa e este script está em um container que receberá uma nova versão de código, assim, o mesmo precisará ser destruído e um novo assumirá.&lt;/p&gt;

&lt;p&gt;No momento do deploy, o container será forçado a desligar e poderá estar no meio de um processamento, que leva cerca de 1s, lembra?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O que pode acontecer com o que estava sendo feito no loop? Tente pensar antes de continuar!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskn7xdunplk556tf5n9z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskn7xdunplk556tf5n9z.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Resposta: não sabemos! Ele pode ter forçado o desligamento em qualquer step. Pode ter sido no meio de uma transação de banco onde a mesma vai ficar presa até expirar, seja no envio de dados incompletos para outros serviços, geração de eventos "fantasmas", e por aí vai…&lt;/p&gt;

&lt;p&gt;Evitar essa corrupção de dados é uma tarefa bem simples, basta entender esse fluxo de desligamento e terminar de fazer o que estava sendo feito antes de realmente desligar o serviço. Lembrando que isso também vale para APIs, precisamos terminar uma requisição em andamento antes de desligar o serviço.&lt;/p&gt;




&lt;h3&gt;
  
  
  Processo de shutdown no Kubernetes
&lt;/h3&gt;

&lt;p&gt;Basicamente podemos resumir em dois momentos, primeiro um aviso de desligamento, conhecido como &lt;code&gt;SIGTERM&lt;/code&gt;, e o desligamento forçado, conhecido como &lt;code&gt;SIGKILL&lt;/code&gt;, que será enviado caso o container não tenha terminado após o aviso.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Caso queira entender um pouco mais sobre estes sinais, &lt;a href="https://major.io/2010/03/18/sigterm-vs-sigkill/" rel="noopener noreferrer"&gt;veja este artigo&lt;/a&gt; que explica bem a diferença entre cada um deles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbiklp0fzncyl382bzuk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbiklp0fzncyl382bzuk.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Podemos ver cada estágio do lifecycle do Pod nesta imagem, onde nosso foco está na última caixinha da direita, onde temos o &lt;code&gt;pre stop hook&lt;/code&gt;, que emite o sinal de &lt;code&gt;SIGTERM&lt;/code&gt; e aguarda o desligamento do container. Veja o &lt;a href="https://www.youtube.com/watch?v=lnAwa8IFaLU" rel="noopener noreferrer"&gt;video completo&lt;/a&gt; deste diagrama para mais detalhes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Para entender cada detalhe deste processo, você pode acessar a &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination" rel="noopener noreferrer"&gt;documentação oficial&lt;/a&gt;. Caso você não tenha sua arquitetura rodando com essa tecnologia não se preocupe, o processo é similar e existem vários artigos que você pode consultar, o importante é que você entenda o fluxo de shutdown e aplique conforme sua realidade.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sendo assim, o que devemos fazer é "escutar" esse aviso de desligamento, terminar o que estamos fazendo e desligar o container com a segurança de que nenhum processo estava em andamento, evitando a corrupção dos dados.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe39g09iiyscpb6dsp1t4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe39g09iiyscpb6dsp1t4.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Com tudo isso em mente, vamos focar na solução!
&lt;/h3&gt;

&lt;p&gt;Começando pelo web server, utilizando o &lt;code&gt;php-fpm&lt;/code&gt; esta tarefa é simples. Temos um parâmetro para configurar quanto tempo os &lt;code&gt;child processes&lt;/code&gt; possuem para finalizar o processo atual antes de desligarem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process_control_timeout = 10s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conforme a &lt;a href="https://www.php.net/manual/en/install.fpm.configuration.php#process-control-timeout" rel="noopener noreferrer"&gt;documentação oficial&lt;/a&gt;, o valor padrão deste parâmetro é zero, com isso, assim que os &lt;code&gt;child processes&lt;/code&gt; recebem o sinal eles encerram, o que pode ser bastante danoso.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;É importante lembrar que no Kubernetes, depois do alerta, caso o processo não tenha sido encerrado, ele é forçado a encerrar, então o valor deste parâmetro precisa dar match com o tempo máximo de uma requisição processada com sucesso e o tempo de espera do Kubernetes até forçar o desligamento.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Scripts de processamento em background, via PHP CLI
&lt;/h3&gt;

&lt;p&gt;Agora vamos falar dos scripts que trabalham em &lt;code&gt;while(true)&lt;/code&gt; e não possuem nada para avisar o loop de que um sinal de desligamento foi enviado.&lt;/p&gt;

&lt;p&gt;Para resolver este problema, temos uma extensão para o PHP chamada de &lt;code&gt;pcntl&lt;/code&gt; que consegue registrar &lt;em&gt;handlers&lt;/em&gt; para sinais enviados ao processo, ou seja, conseguimos "escutar" quando o Kubernetes nos avisar que devemos encerrar.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante&lt;/strong&gt;: esta funcionalidade não deve ser habilitada em contextos de web server, exemplo do php-fpm, pois pode ocasionar em comportamentos inesperados. O seu uso correto é para scripts utilizando o PHP CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para facilitar a implementação e utilização desta extensão, criei uma lib bem simples que faz toda essa mágica acontecer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/leocarmo/graceful-shutdown-php" rel="noopener noreferrer"&gt;leocarmo/graceful-shutdown-php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Com ela, você consegue garantir que nenhum dado será corrompido caso o desligamento seja necessário e, caso seja necessário, registrar um callback para ser executado antes do desligamento.&lt;/p&gt;

&lt;p&gt;Se código ficará parecido com este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;LeoCarmo\GracefulShutdown\GracefulShutdown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$shutdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GracefulShutdown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;$shutdown&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;signalReceived&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Start long task...'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// --&amp;gt; when a signal is sent, sleep returns the number of seconds left&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'End long task...'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Graceful shutdown!'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por ora é isso, espero poder te ajudar a construir aplicações mais consistentes e evitar a corrupção de dados, tanto no fluxo de requisições, quanto via scripts.&lt;/p&gt;

</description>
      <category>php</category>
      <category>programming</category>
      <category>braziliandevs</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
