Identificando Problemas – Queries lentas – Parte 04

Introdução

Continuando o assunto de identificação de problemas, vamos ver agora o que e como lidar com queries que não apresentam um bom desempenho. Antes de chamar um DBA, existem alguns procedimentos investigativos e algumas ações que ajudam a resolver uma boa parte destas ocorrências.

Queries, de onde vêm?

Quando utilizamos um Banco de Dados relacional homologado com o ERP Microsiga / Protheus, todas as requisições de acesso a dados passam pela aplicação DBAccess. Como eu havia mencionado em um post anterior, o DBAccess serve de “gateway” de acesso ao Banco de Dados, e serve para emular as instruções ISAM do AdvPL em um banco relacional.

Logo, as queries submetidas pelo DBAccess ao Banco de Dados em uso podem ser de de dois tipos:

  • Queries emitidas (geradas) pelo DBAccess para atender a uma requisição ISAM — DBGoTop(), DBGoBottom(), DbSkip(), DBSeek().
  • Queries abertas pela aplicação AdvPL para recuperar dados diretamente do Banco de Dados.

Quando utilizamos o DBAccess Monitor, para realizar um Trace de uma conexão do Protheus com o DBAccess, podemos visualizar as operações solicitadas do Protheus ao DBAccess, e as respectivas queries submetidas ao Banco de Dados.

Como identificar uma query “lenta”?

Normalmente encontramos uma ou mais queries com baixo desempenho quando estamos procurando a causa da demora de um determinado processo. A descoberta acaba sendo realizada durante a análise de um Log Profiler obtido durante a execução da rotina, ou também através de um trace da conexão do DBAccess usando o DBMonitor.

Agora, podemos também usar o próprio DBAccess para gerar um log das queries que demoraram mais para serem abertas pelo Banco de Dados. Basta utilizar a configuração MaxOperationTimer, onde podemos especificar um número de segundos limite, a partir do qual um log de advertência deve ser gerado pelo DBAccess, caso o retorno da abertura de uma Query ultrapasse o tempo definido.

O que é uma query lenta?

Definir lentidão normalmente não está ligado apenas a medida absoluta de tempo, mas sim é relativa a urgência ou necessidade de obter a informação rapidamente, versus a quantidade de informações a serem avaliadas e retornadas.

Por exemplo, quando a aplicação AdvPL executa uma instrução como DBGoto(N), onde N é o número de um registro da base de dados, o DBAccess vai montar e submeter uma Query contra o banco de dados, para selecionar todas as colunas da tabela em questão, onde o campo R_E_C_N_O_ é igual ao número N informado.

SELECT CPO1,CPO2,CPO3,...N FROM TABELA WHERE R_E_C_N_O_ = N

Meu chapa, essa Query deve rodar normalmente em menos de 1 milissegundo no banco de dados, por duas razões: A primeira é que a coluna R_E_C_N_O_ é a chave primária (Primary Key) de todas as tabelas criadas pelo DBAccess, então naturalmente existe um índice no Banco de Dados usando internamente para achar em qual posição do Banco estão gravadas as colunas correspondentes a esta linha. E a segunda é que, apenas uma linha será retornada.

Se não existisse um índice para a coluna R_E_C_N_O_, o Banco de Dados teria que sair lento a tabela inteira, sequencialmente, até encontrar a linha da tabela que atendesse esta condição de busca. Esta busca na tabela de dados inteira sem uso de índice é conhecida pelo termo “FULL TABLE SCAN”.

Agora, imagine um SELECT com UNION de mais quatro selects, finalizado com um UNION ALL, onde cada Query faz SUB-SELECTS e JOINS no Banco de Dados, e o resultado disso não vai ser pequeno … Mesmo em condições ideais de configuração do Banco de Dados, não é plausível exigir que uma operação deste tamanho seja apenas alguns segundos.

Causas mais comuns de degradação de desempenho em Queries

Entre as mais comuns, podemos mencionar:

  1. Ausência de um ou mais índices — simples ou compostos — no Banco de Dados, que favoreçam um plano de execução otimizado do Banco de Dados para recuperar as informações desejadas.
  2. Estatísticas do Banco de Dados desatualizadas.
  3. Picos de CPU , Disco ou Rede, na máquina onde está o DBAccess e/ou na máquina onde está o Banco de Dados.
  4. Problemas de hardware na máquina do Banco de Dados ou em algum dos componentes da infra-estrutura.
  5. Problemas de configuração ou de comportamento do próprio Banco de Dados sob determinadas condições.
  6. Excesso de operações ou etapas do plano de execução, relacionadas a complexidade da Query ou da forma que a Query foi escrita para chegar ao resultado esperado.

Recomendo adicionalmente uma pesquisa sobre “Full Table Scan” e outras causas possíveis de baixo desempenho em Queries. Quanto mais infirmação, melhor. E, a propósito, mesmo que a tabela não tenha um índice adequado para a busca, se ela for  realmente pequena (poucas linhas) , o Banco de Dados internamente acaba fazendo CACHE da tabela inteira em, memória, então um Full Table Scan acaba sendo muito rápido, quando a tabela é pequena. Esta é mais uma razão por que muitas ocorrências de desempenho relacionados a este evento somente são descobertas após a massa de dados crescer representativamente no ambiente.

Troubleshooting e boas práticas

Normalmente as melhores ferramentas que podem dar pistas sobre as causas do baixo desempenho de uma Query são ferramentas nativas ou ligadas diretamente ao Banco de Dados, onde a ferramenta é capaz de desenhar e retornar — algumas inclusive em modo gráfico — o PLANO DE EXECUÇÃO da Query. Neste plano normalmente as ferramentas de diagnóstico informam quando está havendo FULL TABLE SCAN, e quais são as partes da Query que consomem mais recursos no  plano de execução. Algumas destas ferramentas inclusive são capazes de sugerir a criação de um ou mais índices para optimizar a busca dos dados desejados.

Mesmo sem ter uma ferramenta destas nas mãos, normalmente necessária para analisar queries grandes e mais complexas, podemos avaliar alguns pontos em queries menores “apenas olhando”, por exemplo:

  1. Ao estabelecer os critérios de busca — condições e comparações usadas na cláusula WHERE — procure usar diretamente os nomes dos campos, comparando com um conteúdo fixo,  evitando o uso de funções. É clado, vão existir exceções, mas via de regra procure estar atento neste ponto.
  2. Evite concatenações de campos nas expressões condicionais de busca. Imagine que você tem uma tabela com dois campos, e você tem nas mãos, para fazer a busca, uma string correspondendo a concatenação destes dois valores. Muto prático você fazer SELECT X FROM TABELA WHERE CPO1 || CPO2 = ‘0000010100’, certo ? Sim, mas mesmo que você tenha um índice com os campos CPO1 e CPO2, o Banco de Dados não vai conseguir usar o índice para ajudar nesta Query — e corre o risco de fazer FULLSCAN. Agora, se ao inves disso, você quebrar a informação para as duas colunas, e escrever SELECT X FROM TABELA WHERE CPO1 = ‘000001’ AND CPO2  = ‘01000’ , o Banco de Dados vai descobrir durante a montagem do plano de execução que ele pode usar um índice para estas buscas, e vai selecionar as linhas que atendem esta condição rapidinho.
  3. O Banco de Dados vai analisar a sua Query, e tentar criar um plano de acesso (ou plano de execução) para recuperar as informações desejadas o mais rápido possível. Se todas as condições usadas na cláusula WHERE forem atendidas por um mesmo índice, você ajuda o Banco de Dados a tomar a decisão mais rapidamente de qual índice internamente usar, se você fizer as comparações com os campos na ordem de declaração do índice. Por exemplo, para o índice CPO1, CPO2, CPO3, eu sugiro  uma Query com SELECT XYZ from TABELA WHERE CPO1 = ‘X’ AND CPO2 = ‘Y’ AND CPOC3 >=  ‘Z’

Queries emitidas pelo DBAccess

As queries emitidas pelo DBAccess no Banco de Dados para emular o comportamento de navegação ISAM são por natureza optimizadas. Para emular a navegação de dados em uma determinada ordem de índice, o DBAccess emite queries para preencher um cache de registros — não de dados — usando os dados dos campos como condições de busca, na mesma sequência da criação do índice. E, para recuperar o conteúdo (colunas) de um registro (linha), ele usa um statement preparado, onde o Banco faz o parser da requisição apenas na primeira chamada, e as demais usam o mesmo plano de execução.

Porém, isto não impede de uma ou mais queries emitidas pelo DBAccess acabem ficando lentas. Normalmente isso acontece quando é realizada uma condição de filtro na tabela em AdvPL, onde nem todos — ou nenhum —  os campos utilizados não possuem um índice que favoreça uma busca indexada, fazendo com que o Banco acabe varrendo a tabela inteira — FULL SCAN — para recuperar os dados desejados.

Este tipo de ocorrência também é solúvel, uma vez determinado qual seria a chave de índice que tornaria a navegação com esta condição de filtro optimizada, é possível de duas formas criar este índice.

Criando um índice auxiliar

A forma recomendada de se criar um índice auxiliar é acrescentá-lo via Configurador no arquivo de índices do ERP (SIX), para que ele seja criado e mantido pelas rotinas padrão do sistema. Porém, para isso este índice não pode conter campos de controle do DBAccess no meio das colunas do índice, e para se adequar ao padrão do ERP, seu primeiro campo deveria sempre ser o campo XX_FILIAL da tabela.

Quando esta alternativa não for possível, existe a possibilidade de criar este índice diretamente no Banco de Dados. Porém, a existência desse índice não deve interferir na identificação de índices de emulação ISAM que o DBAccess faz quando qualquer tabela é aberta. Para isso, a escolha do NOME DO ÍNDICE é fundamental.

Um índice criado diretamente no Banco de Dados para estes casos deve ter um nome que seja alfabeticamente POSTERIOR aos índices declarados no dicionário de dados (SIX). Por exemplo, existe uma tabela ABC990, que possui 8 índices. O ERP Microsiga nomeia os índices da tabela no padrão usando o nome da tabela e mais um número ou letra, em ordem alfabética. Logo, os oito primeiros indices da tabela chamam-se, respectivamente, ABC9901, ABC9902 … ABC9908.

Nomeie seu primeiro índice customizado com o nome de ABC990Z1. Caso seja necessário mais um índice, ABC990Z2, e assim sucessivamente. Chegou no 9, precisa de mais um índice, coloque a letra “A” — ABC990ZA. Dessa forma, estes índices ficarão por último na identificação do DBAccess, e por eles provavelmente não se encaixarem no padrão do ERP, você não vai — e não deve — usá-los dentro de fontes customizados AdvPL — Estes índices vão existir apenas no Banco de Dados, para favorecer a execução de queries especificas ou filtros específicos de navegação.

Precauções

Uma vez que um ou mais índices sejam criados dentro do Banco de Dados, sem usar o dicionário ativo de dados (SIX), qualquer alteração estrutural na tabela feita por uma atualização de versão ou outro programa pode apagar estes índices a qualquer momento, caso seja necessário, e isso vai novamente impactar o desempenho da aplicação. Para evitar isto, é possível escrever uma rotina em AdvPL — customização — para verificar usando Queries no banco de dados se os índices auxiliares criados por fora ainda existem, e até recriá-los se for o caso. Pode ser usado para isso o Ponto de Entrada CHKFILE() , junto das funções TCCanOpen() — para testar a existência do índice — e TcSqlExec() — para criar o índice customizado usando uma instrução SQL diretamente no banco de dados.

Outras causas de degradação de desempenho

Existem ocorrências específicas, que podem estar relacionadas desde a configuração do Banco de Dados, até mesmo problemas no mecanismo de montagem ou cache dos planos de acesso criados pelo Banco para resolver as Queries. Outas ocorrências podem estar relacionadas a execução de rotinas automáticas ou agendadas no próprio servidor de Banco de Dados. Normalmente o efeito destas interferências são percebidos como uma lentidão momentânea porém generalizada, por um período determinado de tempo.

Conclusão

Use a tecnologia ao seu favor, e em caso de pânico, chame um DBA! Muitas vezes existe uma forma mais simples ou com menos etapas para trazer as informações desejadas. Outras vezes é mais elegante, rápido e prático escrever mais de uma query menor, do que uma Super-Query-Megazord.

Desejo a todos um ótimo final de semana, e muitos TERABYTES DE SUCESSO 😀

Referências

 

Identificando Problemas – Congelamento e Conexões Presas – Parte 03

Introdução

No post anterior (https://siga0984.wordpress.com/2018/11/07/identificando-problemas-congelamento-e-conexoes-presas-parte-02), demos uma boa olhada sobre travamentos e congelamentos, desde a percepção do usuário, até algumas possíveis causas e alguns procedimentos de diagnóstico. Neste post, vou apresentar mais algumas possibilidades, e complementar alguns casos já vistos, e ver mais de perto o “temível” DEADLOCK 😀

Dicas para Todos os Casos

  1. Começamos procurando o processo no Protheus Monitor,e verificando se o total de instruções está sendo atualizado. Se não estiver sendo atualizado, o processo está esperando por “algo”.
  2. O processo tem conexão com o DBAccess? Verifique o que a conexão está fazendo. Se o DBAccess está “IDLE” faz algum tempo, seja lá o que o processo estiver esperando, não é um retorno do DBAccess. Elimine a conexão do DBAccess e espere ela sair do DBAccess monitor — isso pode demorar até 30 segundos, inclusive devido ao fato da aplicação não estar fazendo requisições para o DBAccess, ele somente verifica o flag de “derrubar a conexão” em intervalos de 30 segundos.
  3. O processo ainda está com o SmartClient aberto? Se tiver algum problema no SmartClient, e o server está esperando algo que deveria vir do SmartClient, derrubar o SmartClient também faria o processo terminar — porém com uma mensagem de erro de sincronismo, sem gerar log. Deixemos isso como ultima alternativa.
  4. Podemos também tentar derrubar o processo pelo Monitor do Protheus, mas lembre-se de não usar a opção “derrubar imediatamente”, senão o processo some do monitor, e você somente vai saber se ou quando ele saiu, depois de verificar o console.log do Application Server.
  5. Ao investigar ocorrências estranhas e com poucas pistas, procure obter mais informações, inclusive verifique os logs e configurações das aplicações envolvidas — DBAccess, LockServer (linux), License Server, Protheus Master, Slave(s), etc. — principalmente verifique se nestes serviços não está acontecendo algum ACCESS VIOLATION e FAILURE ON THREAD DELETE. Depois de ocorrências desta natureza, o comportamento da aplicação é imprevisível — mas normalmente os efeitos mais comuns são: Recursos bloqueados ou em uso por um processo que não está mais no Monitor, crescimento do uso da memória ao longo do tempo, inclusive congelamentos.

NÃO ACHEI … E AGORA ?

Beleza, você já olhou com uma lupa e não achou onde travou, ou pior, cada hora trava em um lugar diferente, só acontece na produção, ninguém reproduz no ambiente de desenvolvimento ou na homologação …

Abra um chamado na TOTVS, forneça os detalhes pertinentes, o analista de suporte pode pedir mais algumas informações, e se mesmo assim não for descoberto a causa ou o que está acontecendo, ainda assim é possível usar uma build Debug ou RWD (Release com informações de Debug) do Application Server, fornecida para a análise desta ocorrência, junto com um procedimento para gerar um “Core Dump” manualmente do Protheus Server, ou da aplicação em questão — no momento em que o travamento for reproduzido.

Através de um “Core Dump” gerado nestas condições, o time de Tecnologia consegue abrir este arquivo para análise, e determinar onde e o que cada processo dentro do servidor de aplicação estava fazendo no momento que o Dump foi gerado. Isso ajuda muito no diagnóstico, quando os demais procedimentos não deram resultados satisfatórios.

Outros Casos

Lembrando o caso clássico de “Impressão no Servidor” usando um Driver de geração de PDF, rodando o Protheus como serviço do Windows … O processo atual simplesmente TRAVA dentro do servidor. Este caso está bem detalhado no primeiro post — https://siga0984.wordpress.com/2015/08/01/identificando-problemas-memoria-no-advpl-parte-01/ — vale a pena dar uma lida nele, pois além de travar ele mantém vários recursos ocupados e abertos, como a conexão com o DBAccess , License Server, c-Tree, etc.

Existe também a possibilidade de haver algum erro de lógica ou falha de tratamento de eventos ou um estado de interface não previsto, onde o Loop ou o Travamento pode estar dentro do Application Server, ou mesmo dentro do SmartClient, disparados por alguma condição particular. São erros mais difíceis de serem diagnosticados, principalmente quando não existe — ou ninguém sabe como faz — uma receita de bolo para fazer o problema “aparecer” e ser reproduzido. Reproduzir bug em cativeiro é “de rosca”… Não tem como fugir das etapas do processo investigativo, e se nada deu certo, quando a ocorrência chegar até esta camada, cada caso é estudado individualmente no atendimento, onde outras medidas podem ser adotadas, desde uma build Debug, até uma build com uma instrumentação específica para levantar mais informações sobre a ocorrência pode ser fornecida para o cliente no ambiente em questão.

Outros tipos de Loop Infinito – O DEADLOCK

Esse é um dos tipos de ocorrência que dá mais trabalho de investigar, e seus efeitos são desastrosos … A aplicação AdvPL realiza as alterações de registro na base de dados obrigatoriamente solicitando um Lock de Registro, tratado pelo DBAccess. No ERP, usamos a função RecLock(), do FrameWork AdvPL, que possui um tratamento de retry para a obtenção do bloqueio.

Porém, uma vez que uma determinada aplicação esteja em JOB — Como um Scheduler ou um WebService, por default este retry é reiniciado em caso de falha. Caso dois processos diferentes tenham obtido cada um um determinado lock, e no momento atual um processo tenta obter o lock do registro que está com o outro processo, e vice-versa, temos um DEADLOCK na aplicação AdvPL.

Neste caso, se os dois programas estão em JOB — sem interface — ambos ficam tentando pegar cada um o lock que está com o outro processo, e como nenhum deles vai “desistir”, ambos ficam em loop até que um deles seja identificado e derrubado — pelo Monitor do Protheus ou do DBAccess.

Identificando os processos envolvidos

Normalmente dois ou mais processos entram em loop, fazendo várias tentativas de bloqueio de registro, e ninguém sai do lugar. Nestes casos, como eu não sei o que está acontecendo, uma das alternativas é verificar no DBAccess os processos com transação aberta a muito tempo — existe uma coluna nova para indicar isso — e então, usando o DBAccess Monitor, fazemos um TRACE de alguns segundos da conexão, para ver se ela está tentando pegar um lock e não está conseguindo. Depois de saber a tabela e registro envolvidos, você pode procurar quem é o dono do lock no Monitor de Locks do DBAccess, e ver o que esta conexão esta fazendo. Se ela também está tentando pegar outro lock, isso pode indicar um cenário de deadlock, onde basta chutar um dos processos para que o outro tenha a chance de ser finalizado.

WebServices e DEADLOCKs

Os WebServices do Protheus possuem uma configuração especial para fazer com que o retry para obter o lock seja executado apenas por um período de tempo determinado, e em caso de falha, o JOB do WEBSERVICE é encerrado com uma ocorrência de error.log, indicando que não foi possível obter o bloqueio de um determinado registro de uma tabela, inclusive fornecendo detalhes de qual era o processo que estava “segurando” este lock. A configuração chama-se ABENDLOCK=1, definida na seção de configuração das WORKING THREADS dos WEBSERVICES. De qualquer modo, a partir de Dezembro de 2017, esta configuração foi habilitada por DEFAULT nos WebServices, vide a nota de release da TDN.

DBAccess e DEADLOCKs

Devido a dificuldade de identificar os processos e registros envolvidos em uma ocorrência de DEADLOCK, seria muito interessante se o próprio DBAccess conseguisse identificar uma situação como essa, e avisar a um dos programas envolvidos que ele está envolvido em um DEADLOCK com um ou mais processos, onde a aplicação pode tratar a mensagem do DBAccess e finalizar-se automaticamente, gerando o log de erro correspondente e soltando os bloqueios obtidos, ou deixar que o DBAccess finalize uma das conexões automaticamente, para que as outras tenham chance de terminar.

Conclusão

Por hora, deixo as conclusões com vocês, eu apenas vou concluir este POST 🙂

Agradeço a todos novamente pela audiência, e desejo a todos TERABYTES de SUCESSO 😀

 

 

Identificando Problemas – Congelamento e Conexões Presas – Parte 02

Introdução

No primeiro post sobre identificação de problemas — Identificando Problemas  – Memória no AdvPL – Parte 01 — falamos sobre uso de memória e Leaks de memória. Hoje, vamos obter mais detalhes sobre travamentos, congelamentos, conexões e licenças “presas”, e ocorrências desta natureza.

IGH, TRAVOU…

Bem, um operador do ERP executa uma rotina ou sub-rotina qualquer, normalmente através do SmartClient, certo? Por sua vez, o SmartClient em si é uma aplicação em C++ que, em poucas palavras, foi feita para desenhar os componentes que a aplicação AdvPL criou dentro de uma caixa de diálogo ou Janela, e uma vez que a janela torna-se ativa — método ::Activate() do diálogo — os controles de entrada de dados e interação com a aplicação estão do lado do cliente, e o servidor aguarda pelo disparo de ações a partir dos componentes de tela, como clicar em um botão ou preencher um campo com dados.

Já o Protheus Server, executando um programa AdvPL, ao receber uma ação do SmartClient, executa o bloco de código correspondente a ação, que pode chamar rotinas e sub-rotinas, interagir com a interface atual, e até mesmo montar uma nova caixa de dialogo sobre a interface atual e torná-la ativa.

Do lado do usuário, no SmartClient, a percepção do usuário de “travamento” ou “congelamento” normalmente é percebida como “eu apertei um botão, que deveria fazer X, e nada aconteceu, e eu não consigo clicar ou fazer mais nada”.

Onde, quando, como e o quê travou ?

A resposta de cada uma destas perguntas leva para a próxima pergunta. Onde travou, a informação de qual botão em qual tela que foi apertado e percebido o travamento. Quando, é a informação sobre a periodicidade que isso ocorre. Sempre trava ao apertar este botão? Se não trava sempre, como faz para ele travar? Somente trava quando um campo da tela estava com o valor Y? Ou é uma ocorrência esporádica?  O botão funciona o dia inteiro, e de repente trava…

A resposta da última pergunta é a questão de um milhão …  risos … existem muitas coisas que podem ter acontecido. Vamos enumerar aqui boa parte das possibilidades.

Loop Infinito

Aquele botão dispara uma rotina, com um determinado conjunto de parâmetros, e existe um erro de lógica na rotina, onde uma parte do código entra em loop, realizando um determinado processamento e esperando por uma determinada condição para finalizar o loop. Por exemplo, a rotina abriu uma Query, e está lendo campos da Query e acrescentando um valor em uma variável, até que a Query termine, mas dentro do laço o programador esqueceu de colocar um DBSkip() — ou fez esta operação sem querer em um outro ALIAS, ao invés de fazer na QUERY —  ou ainda um laço FOR … NEXT que usou uma variável N, que inadvertidamente foi alterada dentro do loop, para um outro valor menor, fazendo com que o laço não termine.

Nestes casos, ao abrir o Monitor de Processos do Protheus, e localizar o usuário, a coluna que indica o número total de instruções está sempre crescendo, normalmente a CPU fica mais alta no serviço do Protheus que está executando este processo, e o número de instruções por segundo mostrado no Monitor do Protheus também é alto.

Esse é um caso simples de descobrir onde está o problema, basta finalizar o processo pelo Monitor do Protheus, preferencialmente sem marcar a opção “derrubar imediatamente”. A aplicação AdvPL em loop vai perceber entre uma instrução e outra que ela foi marcada para ser finalizada — é isso que o Monitor do Protheus faz quando você manda finalizar um processo. No momento que a aplicação perceber isso, ela finaliza o processo, com uma ocorrência de erro “Process terminated by Administrator” ou algo assim, gerando uma ocorrência de erro com o stack ou pilha de chamada de funções, para mostrar onde foi que o Protheus “percebeu” que o processo foi marcado para ser finalizado.

Nesta situação, quando o Programa AdvPL está em LOOP e não está interagindo com a Interface, a cada 10 segundos o Protheus Server verifica se o SmartClient ainda está lá, aguardando pela resposta. Caso o Server perceba que o SmartCient caiu, ou a conexão de rede foi interrompida, ele finaliza o processo atual com a ocorrência de erro fatal em AdvPL “Remote Connection BROKEN”.

Latência alta de rede entre Protheus e SmartClient

Normalmente quando isso acontece, o processo dá a impressão de ter “travado”, porém em alguns segundos a tela que deveria aparecer simplesmente “aparece”. Imagine que o programa em AdvPL está desenhando uma nova caixa de diálogo, com muitos componentes, e durante a montagem da tela o programa pede ao SmartClient coordenadas de tela e informações sobre as dimensões dos componentes em fase de montagem. Uma latência de rede momentânea de 500 ms (milissegundos) pode fazer uma tela que, durante sua montagem, faça 10 requisições ao SmartClient, demorar quase 5 segundos para ser finalizada. Num caso como esse, o monitor do Protheus mostra um numero de instruções por segundo perto de zero, e o número de instruções total sobe bem devagar.

Instrução em execução no Banco de Dados

Imaginem o cenário, onde a aplicação AdvPL monta uma Query dinâmica, porém devido a um erro de lógica ou validação de parâmetros, a Query fez um produto cartesiano da tabela, ou um INNER JOIN sem WHERE …. ou uma daquelas queries rebuscadas, que fazem múltiplas buscas em uma tabela enorme, usando um ou mais campos que não possuem um índice para o Banco de Dados otimizar a busca, e o Banco de Dados precisa fazer FULL SCAN (ler a tabela inteira) para retornar os dados solicitados.

Quando a aplicação AdvPL submeter a Query ao DBAccess, e este por sua vez submeter a query ao Banco de Dados, o AdvPL espera o retorno da API do DBAccess, e o DBAccess por sua vez está esperando o Banco de Dados. Isso também pode acontecer, por exemplo, com a chamada de uma Stored Procedure de processamento, quando parametrizada para rodar sobre grandes volumes de dados, ou mesmo falta de optimização de índices no banco para roda as queries submetidas por dentro da Stored Procedure.

No monitor do Protheus, será mostrado que o número total de instruções não aumenta, e o número de instruções por segundo permanece em 0 (zero). Ao abrir o Monitor do DBAccess — última versão do Portal — nós temos duas novas colunas de monitoramento muito úteis para casos como esse: A coluna “IDLE” e a “RUNNING”. A coluna IDLE indica a quantos segundos esta conexão do DBAccess não recebeu nenhum pedido de dados do programa AdvPL que a abriu, e a coluna “RUNNING” mostra naquele instante se e qual a rotina do DBAccess que está sendo executada.

Com isso, se a Query está ainda rodado dendo do Banco de Dados, a coluna RUNNING deve mostrar a operação OP_QUERY. No caso de uma Stored Procedure, se eu não me engano é a operação OP_SPEXEC.

Uma das colunas do DBACCESS Monitor — se eu não me engano DBSID ou apenas SID — mostra um identificador da conexão do DBAccess junto ao Banco de Dados. Esse identificador permite o DBA ou o Administrador do Ambiente a abrir uma conexão diretamente com o Banco de Dados, usando uma ferramenta de monitoramento nativa do Banco, e associar uma conexão mostrada pelo Monitor do SGDB com uma conexão do DBAccess.

Em um caso como esse, não adianta tentar matar a conexão do DBAccess com o Banco de Dados pelo Monitor do DBAccess, vai cair no mesmo problema do Protheus: Enquanto o DBAccess não receber um retorno da API do Banco, nada acontece … Mesma coisa derrubar com o Protheus Monitor … mesmo que você use a opção “derrubar imediatamente”, o SmartClient pode ser finalizado na hora, ao perceber que a conexão dele com o Protheus foi encerrada do lado do servidor, mas o programa AdvPL ainda vai estar esperando um retorno do DBAccess.

Neste caso, a última alternativa — antes de derrubar o serviço do Protheus e do DBAccess — é pegar o número ou identificador da conexão com o Banco de Dados, e usando uma ferramenta administrativa do Banco de Dados, pedir para o SGDB encerrar esse processo. Assim que isso foi feito, o SGDB retorna um erro de “Processo Interrompido” para o DBAccess, que por sua vez retorna este erro ao programa AdvPL.

Instrução em execução no SmartCient

Ao clicar naquele botão, o programa AdvPL em execução no Protheus Server pediu para o SmartClient abrir, por exemplo, uma URL a partir da estação onde o SmartClient está sedo executado — função HTTPCGet() — e o endereço solicitado está congestionado de requisições e coloca a sua na fila … Esta função tem um time-out de 120 segundos por default, o que pode “brecar o sistema” onde por 2 minutos.

Em um caso como esse ou similar, onde o Application Server está esperando por alguma coisa do SmartClient, quando você derruba (chuta) o SmartClient, finalizando o processo, o Protheus Server identifica que a interface de rede entre eles foi interrompida, e finaliza o programa AdvPL em execução com aquela ocorrência “Erro de Sincronismo”.

Conexão parcialmente fechada

Este é um cenário bem ingrato … você abriu um SmartClient, e iniciou aquele relatório que demora pelo menos uma hora … Você manda imprimir no SPOOL, e vai tomar um café. O programa que emite o relatório atualiza de vez em quando uma régua de processamento na tela do SmartClient. Passou uma hora, e a régua nem se mexeu … Você vai no Monitor do Protheus … e não encontra esse usuário. Vai no DBAccess, e também não acha nada … mas o SmartClient está ali, aberto, e a régua parada … e você não consegue nem clicar no botão cancelar … o que aconteceu?

Um caso como esse pode indicar uma conexão de rede parcialmente fechada. Durante o processamento do relatório, houve uma falha na rede, porém o encerramento da conexão TCP somente foi percebido pelo Protheus Server, quando ele foi atualizar a régua. Já o SmartClient, que fica somente esperando o Protheus pedir alguma coisa quando o controle de interface não está com ele, e existe um processamento no Protheus Server em andamento, caso a conexão TCP do lado do SmartClient não acuse o erro ou fechamento da outra ponta, o SmartClient fica esperando pra sempre um retorno que nunca vai chegar.

Cenários como esse podem ser contornados com a utilização de um aplicativo fornecido pela TOTVS para o Protheus Server chamado BROKER — ele serve de ponte e proxy reverso para as conexões do SmartClient para o(s) Protheus Server, inclusive para fazer balanceamento de carga. Ele entra na frente das conexões, tanto no SmartClient como no Application Server, e mantem uma conversa “constante” entre as pontas, sendo capaz de detectar com maior precisão quando uma das pontas foi desconectada,  e inclusive pode conseguir reconectar uma conexão encerrada devido a eventual e momentânea instabilidade na rede, sem que nenhuma das pontas (APPServer ou SmartClient) perceba o que aconteceu.

Ocorrências críticas

Um cenário difícil, mas plausível. Uma aplicação AdvPL reproduz um erro no Protheus Server, mostra uma caixa de diálogo com detalhes do erro no SmartClient, e quando você clica no “Ok” para fechar a janela, ela fecha. Então, você vai no DBAccess, e a conexão está lá .. e no license Server também … mas no Monitor do Protheus, esse usuário “sumiu”, e você tem certeza que ninguém usou aquele recurso de “derrubar imediatamente” aquele processo. O que pode ter acontecido?

Ao consultar o log de console do Protheus Server (console.log), você encontra o registro do Erro Advpl, logo depois uma mensagem parecida com “Critical Error”, seguida por “Falha no Delete da Thread” ou similar. Isto significa que, durante a descarga do ambiente, programas e recursos, ocorreu um erro critico na aplicação AdvPL, como pr exemplo invasão de memória, justamente enquanto aquele contexto de execução estava sendo limpo — executando os destrutores internos do Protheus. Se isso acontecer, uma parte dos recursos que seu processo continuam ativos neste processo mas o processo parcialmente não existe, somente o próprio processo consegue finalizar-se de modo elegante. Então, o processo some da lista de monitoramento, mas cai na malha dos processos com falha no destrutor.

Ocorrências desta natureza devem ser reportadas para a TOTVS, para a melhoria contínua do software. Normalmente a utilização de uma build DEBUG nestes casos ajuda a gerar um arquivo de CORE DUMP no momento que uma ocorrência crítica aconteça, gerando postas sólidas do que pode ter acontecido.

Conclusão

Espeto que estas poucas linhas ajudem aos analistas que procuram fantasmas nos ambientes do Protheus pelo mundo afora !!

Desejo novamente a todos TERABYTES DE SUCESSO !!! 

 

Identificando Problemas – Memória no AdvPL – Parte 01

Introdução

Normalmente identificar um problema em um ambiente de produção é uma mistura de talentos. Você deve ser um pouco garimpeiro, um pouco psiquiatra, um pouco detetive … Um dos primeiros posts a respeito de “problemas” está no link https://siga0984.wordpress.com/2014/12/14/pense-fora-da-caixa-e-resolva-problemas/ . No post de hoje, vamos arranhar a tampa da caixa de Pandora …

Problemas

“Um problema é uma dificuldade na obtenção de um determinado objectivo. Em certos contextos pode ter um significado especial” — Wikipédia

No contexto da tecnologia da informação, um problema é um nome bonito e simples para ilustrar que “algo não está funcionando como deveria funcionar”. A questão está em resolver esse “algo errado”, seguido das duas perguntas mais intrigantes da humanidade: “POR QUÊ ?” e “COMO FAZ PRA RESOLVER”.

O quê, onde, (desde) quando e como

São as quatro perguntas iniciais de qualquer problema. Primeiro, o que foi identificado ou nomeado como sendo um problema. A resposta desta pergunta também ajuda a identificar a criticidade do problema. Normalmente qualquer departamento de suporte está sujeito a receber uma ligação com um cliente ou analista de campo esbaforido, que logo diz o seguinte: “PelamordeDeus, o sistema caiu…”.

Um problema pode ser desde uma diferença de valores mostrados em dois relatórios, que em tese deveriam chegar a um mesmo número, uma impossibilidade de uso de uma determinada rotina devido a uma validação de dados que não está permitindo a continuidade do processo, uma finalização do programa ou rotina com uma mensagem de erro seguido do fechamento da aplicação de interface, uma etapa de processo que ao ser acionada “trava” a aplicação, ou um determinado processo (ou o sistema inteiro) demora muito para ser concluído ( lentidão isolada ou generalizada), enfim, tudo é “problema”.

Normalmente a criticidade de um problema não está ligado tecnicamente a dificuldade de resolver ou entender o problema, mas sim no que isso causa para o cliente. Não conseguir imprimir uma Fatura ou Nota de Saída de mercadorias, quando o cliente está com um caminhão de mercadorias fechando a saída da empresa esperando a nota sair, para o cliente isso é um problema de criticidade máxima.

Os demais detalhes (onde , quando e como) são muito importantes, pois vão dar uma idéia de localização do problema, (desde) quando ele passou a acontecer, e “como” ele acontece — ou melhor, o que precisa ser feito e de que forma para que o “problema” se manifeste. O “como” é a receita de bolo para fazer o problema acontecer. Você precisa saber de todos os ingredientes, a ordem que foram colocados na panela, se o fogo estava ligado antes dos ingredientes serem colocados lá, e por aí vai.

E, uma observação muito bem lembrada pelo meu amigo Alan Cândido… Normalmente o quando (desde quando), deve vir junto com a pergunta: Qual foi a última vez que funcionou, e o que aconteceu no ambiente entre a última vez que funcionou e foi percebido que a rotina / programa / sistema apresentou o problema? Houve alteração de customização atualização de ambiente, binário, RPO, update do Windows, deploy de serviço, alteração de parametrização ou configuração, etc…?  Todos os dados são relevantes, nem sempre eles parecem estar conectados, mas são relevantes.

O Contorno

Muitas vezes precisamos arrumar um contorno para o problema para depois resolvê-lo. A nota fiscal não está saindo na Impressora 01 ? Tente na impressora 02. A rotina não começa a imprimir pois acontece um erro de execução ? Começou a acontecer ontem, depois de uma atualização de sistema ? Verifique se dá pra voltar a versão anterior do ambiente (caso exista). Existem problemas que não tem contorno, ou que o contorno pode causar mais problemas. É necessário ser criterioso na avaliação e para propor uma alternativa viável.

Descobrindo a causa

Bem, para cada tipo de problema, existe um roteiro para descobrir a causa. Alguns problemas não tem causa aparente, então tudo em volta do problema deve ser verificado. Esta investigação e o registro dela vão ajudar os responsáveis por arrumar o problema a entender tudo o que já foi verificado e qual é o cenário onde o problema se manifesta.

Partindo para a solução

Uma vez que o problema e sua causa foi entendida, a solução do problema pode ser um procedimento, executado pelo cliente ou por um analista, ou DBA, ou uma atualização de um programa, ou de um executável ou DLL, da mesma forma que um problema é “algo”, a solução é “fazer alguma coisa” — o leque de opções é extremamente amplo. Um ponto importante ao fornecer uma solução é sinalizar ao cliente se isso realmente vai ( ou deveria resolver o problema ), ou se é uma ação sobre uma provável causa. Normalmente depois da terceira vez que você pede para o cliente fazer mais alguma coisa no ambiente dando a entender que aquilo é a solução, e a ação proposta não resolve, fica muito mais difícil dar suporte a um cliente que não acredita mais no suporte.

Entrando no AdvPL

Até agora, todos os parágrafos anteriores não traz nenhuma novidade, é o “Be-a-bá” do Analista de Suporte. Agora, vamos entrar em uma análise de ocorrência que costuma deixar todos de cabelo em pé … “Consumo de memória”, no contexto de uma aplicação AdvPL. Vamos “fatiar” esse assunto, para ser possível devorá-lo sem se engasgar.

Definição de Consumo de Memória

Quando usamos a expressão “consumo de memória”, entendemos que isto significa a quantidade total de memória RAM (também conhecida por memória volátil) alocada por uma aplicação em execução pelo sistema operacional em um equipamento, usada internamente para armazenar o próprio programa em execução, juntamente com os dados que são ou podem ser carregados para a memória para serem mostrados na interface, gravados ou lidos de uma unidade de disco ou de um banco de dados, e assim por diante.

Todo e qualquer programa apresenta um consumo de memória de acordo com “o que” o programa está carregando na memória. Um programa AdvPL pode criar um array e ler registros de uma Query do banco de dados, e isso vai consumir memória proporcionalmente em razão da quantidade de linhas e colunas populadas desse Array.

Memória total do equipamento

Em um computador, temos uma quantidade limitada de memória RAM. Um equipamento Desktop pode vir com 4 GB (gigabytes) de memória, ou mais. Equipamentos servidores, dependendo do porte do servidor, podem ter de 32 GB a 128 GB de memória. Este recurso será dividido entre todos os programas e serviços que estão sendo executados no equipamento, inclusive o próprio sistema operacional do equipamento.

Tendo essa perspectiva em vista, antes de mais nada é necessário saber quanto de memória a versão do sistema operacional do equipamento precisa para operar com “folga”. Caso a soma dos aplicativos carregados na memória mais o sistema operacional cheguem perto do limite físico de memória, o sistema operacional começa a transferir áreas da memória para o disco rígido (HD) do equipamento, e isso definitivamente têm um impacto muito grande no desempenho das aplicações em execução.

Memória por aplicativo

Cada aplicativo pode possuir uma característica diferenciada de consumo de memória. SGBDs normalmente consomem o tamanho necessário ou disponível (sob demanda ou pré-reservado) para manter os dados e índices mais acessados espelhados em memória, para proporcionar maior desempenho — é mais rápido buscar uma informação na memória do que no disco. Normalmente você configura um limite de memória que o SGBD pode usar, para que ele não “morda” toda a memória do servidor. Uma vez que este limite seja atingido, o SGBD “se vira” para tirar do cache as informações menos acessadas.

Quando vamos colocar mais aplicativos para serem executados no mesmo equipamento, precisamos tomar o cuidado de não deixar eles tomarem posse de mais memória do que a disponível para uso, senão já sabemos o que acontece …

AdvPL e Consumo sob demanda

Como acabamos de ver, um SGBD normalmente consome a memória montando caches de dados e índices conforme a base de dados é acessada mediante solicitações de uma aplicação. Porém, uma vez definido ou atingido um limite, o SGBD pode manter na memória os caches dos dados mais utilizados, e descartar os caches pouco acessados. Conforme a aplicação vai solicitando informações, o SGBD “se vira” para gerenciar os caches da melhor forma possível.

Quando temos uma aplicação que consome memória para um contexto de execução, normalmente tudo o que está sendo executado precisa estar na memória RAM, por questões de desempenho. O servidor de aplicação AdvPL – Totvs Application Server – consome memória sob demanda, para cada contexto de execução. Uma nova conexão vinda de um SmartClient ou um novo JOB iniciado no AdvPL (processo sem interface) vão consumir memória para carregar o(s) programa(s) do repositório de objetos (RPO), e memória para armazenar os conteúdos das variáveis do programa alimentadas durante a sua execução ( Instâncias de classes, strings, etc). Por ser uma aplicação multi-thread, isto é, com múltiplos processos internos, cada thread possui uma reserva mínima de memória feita pelo sistema operacional, mas toda a memória alocada por todos os processos são vistas como uma coisa só para o sistema operacional do equipamento. Tudo fica “em nome” do executável.

Quando uma aplicação qualquer, como por exemplo um SmartClient, realiza a carga de uma DLL para integração com um periférico ou serviço, caso os programas e funções da DLL precisem de memória, eles vão pedir para o sistema operacional, porém esta memória vai ser contabilizada para o executável do SmartClient, que fez a carga da DLL. A mesma coisa acontece para o DBAccess (Gateway de acesso a bancos de dados relacionais) e o TOTVS Application Server. Cada driver ( DLL/SO) de acesso a dados que consumir memória vai usar a área de endereçamento do executável.

Consumo de memória por Thread

O TOTVS Application Server possui um mecanismo de monitoramento de memória por thread, que pode ser habilitado utilizando a chave DebugThreadUsedMemory=1 na seção [GENERAL] do arquivo de configuração (appserver.ini). — Vide TDN no link http://tdn.totvs.com/display/tec/DebugThreadUsedMemory. Com este recurso habilitado, o TOTVS Application Server passa a registrar as alocações e liberações de memória de cada processo (thread) em execução, e mostra essa informação em uma coluna adicional na interface de monitoramento (TOTVS Monitor e/ou TDS Server Monitor).

Cada processo ou thread AdvPL em execução mantém na memória os fontes das funções chamadas durante a execução do programa, e como os programas podem alimentar variáveis de memória de tamanho dinâmico, alguns programas podem apresentar um consumo de memória maior ou menor em relação a outros. Inclusive, como o primeiro programa a ser executado provavelmente fará a carga das DLLS de acesso ao c-Tree e ao DBAccess, e mantém estas DLLs alocadas até o serviço do TOTVS Application Server ser finalizado, a primeira thread que entra no sistema para executar código AdvPL pode passar uma falsa sensação de consumo mais elevado que as demais.

Onde um programa aloca a memória ?

Além das variáveis de memória, criadas pelo programa e que podem ser alimentadas dinamicamente, contendo strings, objetos, arrays e afins, existem outros recursos que também consomem memória. Cada tabela aberta e em uso pelo sistema consome alguns KB de memória. Este consumo não é proporcional a quantidade de registros da tabela, mas é proporcional a quantidade de colunas, e a soma do tamanho de todas as colunas ( tamanho do registro). Cada aplicação de interface AdvPL, acessada pelo SmartClient, aloca e mantém na memória uma instância de cada classe de objeto de interface criado pelo programa em execução até que a janela ou container destes componentes seja fechado.

Logo, uma janela de diálogo de interface com mais componentes vai consumir mais memória do que um programa com menos componentes. Um processo com mais tabelas abertas tende a consumir mais do que um processo com menos tabelas e queries mantidas abertas.

Processo dedicado de monitoramento de memória

A partir do TOTVS Application Server, Build 7.00.100812P, foi introduzido no servidor de aplicação um processo interno dedicado ao monitoramento de memória. Vide TDN no link http://tdn.totvs.com/pages/viewpage.action?pageId=6065765. Com este processo “de olho” no consumo de memória do servidor AdvPL, caso a memória endereçada pelo serviço ultrapasse um limite seguro, automaticamente as novas conexões e nocas inicializações de Jobs são desabilitados. Em um ambiente de balanceamento de carga, o servidor “Master” ou “Balance” identifica este cenário, e não redireciona novas conexões para este serviço.

Para facilitar a identificação de onde a memória está sendo utilizada por cada programa, e inclusive para evitar que um programa consuma memória acima de um determinado limite, foram criadas configurações adicionais para monitoramento de memória, onde é possível definir por thread um limite de advertência e uum limite máximo de consumo. Neste caso, uma vez que o limite seja atingido, aquele processo é derrubado do sistema. Os detalhes desta implementação estão na TDN, no link http://tdn.totvs.com/pages/viewpage.action?pageId=6065765

Adicionalmente, caso seja definido um limite de advertência de consumo por processo, quando este limite for atingido, será gerado um error.log de advertência, onde são informados no stack de execução quais variáveis estão na memória e qual a estimativa de consumo para cada uma.

O que é “LEAK” de memória ?

O termo “memory leak”, ou ‘vazamento de memória’ é aplicado a um processo, que durante a sua execução realiza alocações de memória, mas não devolve ao sistema operacional toda a memória que foi utilizada. Normalmente isto gera no Totvs Application server o seguinte comportamento:

Após iniciar o serviço do Protheus, um ou mais usuários se conectam nele usando o SmartClient, e executam diversas aplicações durante um período de tempo. NO final do dia, depois que todos os usuários desconectarem do servidor, e não houver mais nenhum Job rodando, verificamos a memória ocupada pelo executável usando o Task Manager do Windows, e verificamos que, mesmo sem nenhuma conexão, o serviço do Application Server mantém de 200 a 300 MB de mempória alocada.

Até aqui, este é um comportamento normal, afinal as DLLs de acesso a dados não serão descarregadas até que o serviço seja finalizado, e internamente o servidor de aplicação utiliza um mecanismo de gerenciamento de memória que prioriza manter uma reserva de memória pré-alocada, que não é devolvida ao sistema operacional, o que agiliza as próximas execuções de aplicações.

Porém, após mais um dia de trabalho, depois que mais usuários executaram mais rotinas e programas, depois que o último usuário desconectou do servidor de aplicação, ele está ocupando 500 MB, e no outro dia 700 MB, e no outro dia 1 GB … Este crescimento da memória ao longo do tempo, medido depois que todos os usuários desconectaram do sistema, indica um comportamento de Leak de memória.

As causas do Leak

Em um programa AdvPL, você não tem acesso a nenhum mecanismo de alocação direta de memória explicitamente, as variáveis que você cria vão alocar memória dinamicamente para armazenar os conteúdos destas variáveis, e o servidor de aplicação têm mapas e listas de controle desses recursos. Porém, quando a execução de um processo termina, todos os recursos, variáveis, caches e etc usados por aquele processo são desalocados e a memória é (parcialmente) devolvida ao sistema operacional. Quando algum recurso interno da linguagem não fez a devolução de todas as áreas de memória, devido a uma ocorrência de processamento ou desvio lógico não previsto, uma ou mais áreas de memória formam “esquecidas” de serem devolvidas, e após o final do programa, permanecem alocadas da memória, sem referencia ao programa que as utilizava. Trata-se de uma área “perdida”, que o servidor de aplicação não vai conseguir reutilizar, e que ocupa espaço na memoria alocada.

Neste cenário, se um servidor de aplicação estiver deixando 200 Mg de Leak por dia na memória, após uma semana, a capacidade de atender conexões deste serviço vai ser prejudicada, pois ele já tem uma quantidade muito grande de memoria alocada e não utilizada, e se antes cabiam 20 usuários conectados neste serviço, com a memória ocupada, este servidor passa a aguentar apenas 15, 10 ou menos usuário, atingindo os limites de alocação de memória com menos usuários e podendo afetar a disponibilidade do ambiente ou deste serviço.

Na prática, não há nada em AdvPL que propositalmente deixe um leak na memória. Normalmente alguém esqueceu de devolver o conteúdo de um ponteiro, ou não foi prevista uma possibilidade de amarração de dados na memória, em uma condição específica, que não foi tratada adequadamente. Agora, uma coisa que é possível e relativamente comum de acontecer, é o consumo crescente de memória.

Definindo consumo crescente de memória

A determinação de um Leak é feita baseado no crescimento do saldo residual de memória alocada pelo executável de uma aplicação após os processos de execução terem sido finalizados. O Consumo crescente é caracterizado pela execução da mesma rotina, onde cada interação ou repetição do mesmo processo aloca mais memória durante o seu uso. Este consumo pode ser necessário ou intencional, ou pode ser uma falha de projeto ou lógica em um determinado componente.

Por exemplo, uma rotina de interface de consulta qualquer, que permite que o operador do sistema defina uma faixa de datas para pesquisa, e que disponibiliza estas informações na tela usando um Array AdvPL, ao invés de abrir um Browse sobre uma tabela de dados ou uma tabela temporária, fatalmente vai ocupar mais memória durante a sua execução, caso existam muitas informações a serem manipuladas.
Neste caso o desenvolvedor precisa tomar cuidado para criticar o tamanho do array, ou as informações que o usuário especifica para a pesquisa, pois o Array no AdvPL não têm limite definido de número de elementos. Ele pode se extender até ocupar toda a memória endereçável pelo servidor de aplicação AdvPL até derrubar o serviço — e todos os outros processos que nele estão sendo executados. Neste caso o consumo é intencional e necessário, mas deve ser dimensionado para não causar transtornos.

O outro cenário (falha de processo ou de lógica) pode ser caracterizado pelo seguinte cenário: Uma determinada função dentro de um fonte utiliza um array de escopo STATiC para realizar um cache de alguma coisa, e o programador esqueceu de fazer uma verificação para evitar que a mesma informação seja colocada no cache. Conforme a aplicação vai sendo executada, este array vai crescendo … após algumas horas trabalhando com a aplicação, aquele processo pode estar consumindo sozinho 400, 500 MB de memória. Porém, quando o programa é finalizado, toda a memoria ocupada (desnecessariamente) vai ser limpa.

Estes cenários são difíceis de serem identificados, requerem a habilitação dos mecanismos de monitoramento e acompanhamento dos logs gerados para levantar informações sobre quais rotinas e sob quais condições este consumo crescente é reproduzida.

Identificando o Leak

A forma mais elegante de confirmar se um consumo excessivo de memória é realmente um Leak, é utilizar as chaves DebugThreadUsedMemory=1 em conjunto da chave ServerMemoryInfo=1 ( ambas na seção [GENERAL] do appserver.ini). A primeira chave serve para ligar o consumo de memoria por processo, e a segunda faz com que o TOTVS Application Server grave no log de console ( console.log ) uma “foto” dos processos e da memória em uso em intervalos de 1 minuto, além de mostrar um status de alguns acumuladores de memória internos, depois que você finaliza o TOTVS Application Server, e todos os processos internos de execução foram finalizados.

Partindo para a prática

Insira as chaves mencionadas na seção [general] do appserver.ini, e suba o TOTVS Aplication Server em modo console. Você deve ver as seguintes informações:

Win NT/2000
[INFO ][SERVER] [Thread 7900] [SMARTHEAP] Registering Tasks...
[INFO ][SERVER] [Thread 7900] [SMARTHEAP] Version 8.0.0
*** TOTVS S.A. ***
*** www.totvs.com.br ***
TOTVS - Build 7.00.131227A - Jul 23 2015 - 06:50:20
'. TOTVS AppServer x32' console mode.
Press Ctrl+Break to terminate.
*** SERVER BUILD WITH EXTENDED PROFILER INFORMATION
*** SERVER BUILD WITH DUAL-STACK SOCKETS FOR IPV6 IMPLEMENTATION
*** DEFAULT SOCKET BIND IS IPV4
*** STARTING SERVER WITH DEBUG OF USED MEMORY PER THREAD
*** STARTING SERVER WITH PRECISION FLOATING POINT ARITHMETIC
---------------- OS System Info -----------------------------------------------
OS Version .........: Windows 8 [Version 6.2.9200]
OS Platform ........: Windows NT Based (x64)
OS Version Info ....:
-------------------------------------------------------------------------------
---------------- OS Memory Info -----------------------------------------------
Physical memory . 8084.27 MB. Used 3592.04 MB. Free 4492.23 MB.
Paging file ..... 10004.27 MB. Used 5486.12 MB. Free 4518.15 MB.
-------------------------------------------------------------------------------
[INFO ][SERVER] [Thread 7900] APP Virtual Address Allocation Limit .... 4095.88 MB.
[INFO ][SERVER] [Thread 7900] Memory Monitor Virtual Address LIMIT .... 4095.88 MB.
Http server is ready.
 Root path is c:\protheus11\http\
 Listening port 80 (default)
[INFO ][SERVER] Application PID ......... [8788]
[INFO ][SERVER] Application Main Thread .. [7900]
[INFO ][SERVER] [Thread 7900] Application Server started on port 6010
[31/07/2015 21:31:48] Server started.
----------- Total Thread Count ------------
 Total Threads ... 18
 Thread ... 3
 WThread ... 10
 SockServer ... 1
 HttpThread ... 3
 HttpServer ... 1
-------------- APO Map List ---------------
*** EMPTY ***
----------- Detailed Process List ----
[ 9156][ 0.00 MB][ Thread][ tMemoryMonitor][]
[ 4116][ 0.00 MB][ HttpServer][ tHttpServer][]
[ 8804][ 0.00 MB][ HttpThread][ tHttpWThread][]
[ 7532][ 0.00 MB][ HttpThread][ tHttpWThread][]
[ 10540][ 0.00 MB][ HttpThread][ tHttpWThread][]
[ 2072][ 0.00 MB][ Thread][ GenProcCleaner][]
[ 3252][ 0.01 MB][ SockServer][ tMainServer][]
[ 5988][ 0.00 MB][ WThread][ tWThreadBase][]
[ 3800][ 0.00 MB][ WThread][ tWThreadBase][]
[ 6472][ 0.00 MB][ WThread][ tWThreadBase][]
[ 7708][ 0.00 MB][ WThread][ tWThreadBase][]
[ 2444][ 0.00 MB][ WThread][ tWThreadBase][]
[ 10216][ 0.00 MB][ WThread][ tWThreadBase][]
[ 2892][ 0.00 MB][ WThread][ tWThreadBase][]
[ 10232][ 0.00 MB][ Thread][ tJobManager][]
[ 10864][ 0.00 MB][ WThread][ tWThreadBase][]
[ 8176][ 0.00 MB][ WThread][ tWThreadBase][]
[ 4264][ 0.00 MB][ WThread][ tWThreadBase][]
----------- Global List Info --------------
 IMAP Glb List ... 0.00 kB. Count 0
 Variable Glb List ... 0.00 kB. Count 0
 Session Glb List ... 0.00 kB. Count 0
 SymTab List ... 7.11 kB. Count 725 Hits 1035
----------- SmartHeap Pools Info ----------
 pooltString ... 67.97 kB. Count 18 Ok
 poolrContent ... 71.95 kB. Count 1 Ok
 pooltInstrVar ... 71.95 kB. Count 1 Ok
 pooltConst ... 3.98 kB. Count 0 Ok
 poolrContentTypeSimple ... 3.98 kB. Count 0 Ok
 poolrContentTypeDateTime ... 3.98 kB. Count 0 Ok
 poolrContentTypeInterval ... 3.98 kB. Count 0 Ok
 pooltDecimal ... 3.98 kB. Count 0 Ok
 poolt4GLInterFunctionCall ... 3.98 kB. Count 0 Ok
 pooltNamedVar ... 3.98 kB. Count 0 Ok
 pooltSum ... 3.98 kB. Count 0 Ok
 pooltCompare ... 3.98 kB. Count 0 Ok
 pooltLocalEnv ... 3.98 kB. Count 0 Ok
 pooltInterFunctionCall ... 3.98 kB. Count 0 Ok
 pooltDirectFunctionCall ... 3.98 kB. Count 0 Ok
 pooltApoReg ... 3.98 kB. Count 0 Ok
 pooltAssignBase ... 3.98 kB. Count 0 Ok
 pooltCodeBlock ... 7.97 kB. Count 0 Ok
 pooltCodeBlockEnv ... 3.98 kB. Count 0 Ok
 pooltCodeBlockRef ... 3.98 kB. Count 0 Ok
 pooltCodeBlockInstr ... 3.98 kB. Count 0 Ok
 pooltAPXmlDocInterface ... 3.98 kB. Count 0 Ok
 pooltClassData ... 71.95 kB. Count 380 Ok
 pooltAPXmlItem ... 3.98 kB. Count 0 Ok
 pooltPrgFunctXRefEle ... 3.98 kB. Count 0 Ok
 pooltIdentifier ... 3.98 kB. Count 0 Ok
 pooltNamedVarXVarRef ... 3.98 kB. Count 0 Ok
 pooltParmDefDet ... 3.98 kB. Count 0 Ok
 pooltVarList ... 3.98 kB. Count 0 Ok
 pooltStatList ... 7.97 kB. Count 0 Ok
 pooltParmList ... 3.98 kB. Count 0 Ok
 pooltArrayElement ... 3.98 kB. Count 0 Ok
 pooltBaseIf ... 3.98 kB. Count 0 Ok
 pooltMethod ... 71.95 kB. Count 758 Ok
 pooltFindObject ... 3.98 kB. Count 0 Ok
 pooltSelfVar ... 3.98 kB. Count 0 Ok
 pooltClassProp ... 3.98 kB. Count 0 Ok
 pooltClassInstance ... 3.98 kB. Count 0 Ok
 pooltMethodCall ... 3.98 kB. Count 0 Ok
 pooltReturn ... 3.98 kB. Count 0 Ok
 pooltStaticEnv ... 3.98 kB. Count 0 Ok
 pooltPrivateEnv ... 3.98 kB. Count 0 Ok
 Default Memory Pool ... 1663.33 kB. Count 3437 Ok
TOTAL POOLED MEMORY ... 2174.50 kB.
----------- OS Memory Summary -------------
Physical memory . 8084.27 MB. Used 3590.72 MB. Free 4493.55 MB.
Paging file ..... 10004.27 MB. Used 5503.69 MB. Free 4500.58 MB.
----------- APP Memory Summary ------------
 Server Memory Capacity ... 4095.88 MB.
 Service Used Memory ... 101.06 MB.
 Service Resident Memory ... 28.12 MB.
[INFO ][SERVER] [Thread 9156] [MEMORY] V-Load 0.02 - Peak 101.06 MB. - SL 4095.88 MB.
[INFO ][SERVER] [Thread 9156] [MEMORY] R-Load 1.00 - Peak 28.12 MB. - SL 28.12 MB.

Logo após iniciar o serviço, finalize ele usando Control+C e digitando SIM ou YES e pressionando ENTER.
Deve ser gravado um log parecido com o mostrado abaixo:

Digite SIM [Enter] (maiusculo) para sair
Type YES [Enter] (uppercase) to exit:
Wait...
[INFO ][SERVER] [Thread 9368] Application Server Console Stopped...
[INFO ][SERVER] [Thread 7900] Application Server (WIN32 version) in shutdown....
[INFO ][SERVER] [Thread 7900] [31/07/2015 21:33:07] Application SHUTDOWN in progress...
----------- Total Thread Count ------------
 Total Threads ... 18
 Thread ... 3
 WThread ... 10
 SockServer ... 1
 HttpThread ... 3
 HttpServer ... 1
-------------- APO Map List ---------------
*** EMPTY ***
----------- Detailed Process List ----
[ 9156][ 0.00 MB][ Thread][ tMemoryMonitor][]
[ 4116][ 0.00 MB][ HttpServer][ tHttpServer][]
[ 8804][ 0.00 MB][ HttpThread][ tHttpWThread][]
[ 7532][ 0.00 MB][ HttpThread][ tHttpWThread][]
[ 10540][ 0.00 MB][ HttpThread][ tHttpWThread][]
[ 2072][ 0.00 MB][ Thread][ GenProcCleaner][]
[ 3252][ 0.01 MB][ SockServer][ tMainServer][]
[ 5988][ 0.00 MB][ WThread][ tWThreadBase][]
[ 3800][ 0.00 MB][ WThread][ tWThreadBase][]
[ 6472][ 0.00 MB][ WThread][ tWThreadBase][]
[ 7708][ 0.00 MB][ WThread][ tWThreadBase][]
[ 2444][ 0.00 MB][ WThread][ tWThreadBase][]
[ 10216][ 0.00 MB][ WThread][ tWThreadBase][]
[ 2892][ 0.00 MB][ WThread][ tWThreadBase][]
[ 10232][ 0.00 MB][ Thread][ tJobManager][]
[ 10864][ 0.00 MB][ WThread][ tWThreadBase][]
[ 8176][ 0.00 MB][ WThread][ tWThreadBase][]
[ 4264][ 0.00 MB][ WThread][ tWThreadBase][]
----------- Global List Info --------------
 IMAP Glb List ... 0.00 kB. Count 0
 Variable Glb List ... 0.00 kB. Count 0
 Session Glb List ... 0.00 kB. Count 0
 SymTab List ... 7.11 kB. Count 725 Hits 1035
----------- SmartHeap Pools Info ----------
 pooltString ... 71.95 kB. Count 18 Ok
 poolrContent ... 71.95 kB. Count 1 Ok
 pooltInstrVar ... 71.95 kB. Count 1 Ok
 pooltConst ... 3.98 kB. Count 0 Ok
 poolrContentTypeSimple ... 3.98 kB. Count 0 Ok
 poolrContentTypeDateTime ... 3.98 kB. Count 0 Ok
 poolrContentTypeInterval ... 3.98 kB. Count 0 Ok
 pooltDecimal ... 3.98 kB. Count 0 Ok
 poolt4GLInterFunctionCall ... 3.98 kB. Count 0 Ok
 pooltNamedVar ... 3.98 kB. Count 0 Ok
 pooltSum ... 3.98 kB. Count 0 Ok
 pooltCompare ... 3.98 kB. Count 0 Ok
 pooltLocalEnv ... 3.98 kB. Count 0 Ok
 pooltInterFunctionCall ... 3.98 kB. Count 0 Ok
 pooltDirectFunctionCall ... 3.98 kB. Count 0 Ok
 pooltApoReg ... 3.98 kB. Count 0 Ok
 pooltAssignBase ... 3.98 kB. Count 0 Ok
 pooltCodeBlock ... 7.97 kB. Count 0 Ok
 pooltCodeBlockEnv ... 3.98 kB. Count 0 Ok
 pooltCodeBlockRef ... 3.98 kB. Count 0 Ok
 pooltCodeBlockInstr ... 3.98 kB. Count 0 Ok
 pooltAPXmlDocInterface ... 3.98 kB. Count 0 Ok
 pooltClassData ... 71.95 kB. Count 380 Ok
 pooltAPXmlItem ... 3.98 kB. Count 0 Ok
 pooltPrgFunctXRefEle ... 3.98 kB. Count 0 Ok
 pooltIdentifier ... 3.98 kB. Count 0 Ok
 pooltNamedVarXVarRef ... 3.98 kB. Count 0 Ok
 pooltParmDefDet ... 3.98 kB. Count 0 Ok
 pooltVarList ... 3.98 kB. Count 0 Ok
 pooltStatList ... 7.97 kB. Count 0 Ok
 pooltParmList ... 3.98 kB. Count 0 Ok
 pooltArrayElement ... 3.98 kB. Count 0 Ok
 pooltBaseIf ... 3.98 kB. Count 0 Ok
 pooltMethod ... 71.95 kB. Count 758 Ok
 pooltFindObject ... 3.98 kB. Count 0 Ok
 pooltSelfVar ... 3.98 kB. Count 0 Ok
 pooltClassProp ... 3.98 kB. Count 0 Ok
 pooltClassInstance ... 3.98 kB. Count 0 Ok
 pooltMethodCall ... 3.98 kB. Count 0 Ok
 pooltReturn ... 3.98 kB. Count 0 Ok
 pooltStaticEnv ... 3.98 kB. Count 0 Ok
 pooltPrivateEnv ... 3.98 kB. Count 0 Ok
 Default Memory Pool ... 1663.33 kB. Count 3439 Ok
TOTAL POOLED MEMORY ... 2178.48 kB.
----------- OS Memory Summary -------------
Physical memory . 8084.27 MB. Used 3592.28 MB. Free 4492.00 MB.
Paging file ..... 10004.27 MB. Used 5494.38 MB. Free 4509.89 MB.
----------- APP Memory Summary ------------
 Server Memory Capacity ... 4095.88 MB.
 Service Used Memory ... 100.67 MB.
 Service Resident Memory ... 28.04 MB.
**
[INFO ][SERVER] [Thread 9156] Memory Monitor END
...
/* =========================================================================
*** SMARTHEAP POOL STATUS AFTER FINISHED PROCESSES ***
pooltString ... 71.95 kB. Count 0 Ok
 poolrContent ... 71.95 kB. Count 0 Ok
 pooltInstrVar ... 71.95 kB. Count 0 Ok
 pooltConst ... 3.98 kB. Count 0 Ok
 poolrContentTypeSimple ... 3.98 kB. Count 0 Ok
 poolrContentTypeDateTime ... 3.98 kB. Count 0 Ok
 poolrContentTypeInterval ... 3.98 kB. Count 0 Ok
 pooltDecimal ... 3.98 kB. Count 0 Ok
 poolt4GLInterFunctionCall ... 3.98 kB. Count 0 Ok
 pooltNamedVar ... 3.98 kB. Count 0 Ok
 pooltSum ... 3.98 kB. Count 0 Ok
 pooltCompare ... 3.98 kB. Count 0 Ok
 pooltLocalEnv ... 3.98 kB. Count 0 Ok
 pooltInterFunctionCall ... 3.98 kB. Count 0 Ok
 pooltDirectFunctionCall ... 3.98 kB. Count 0 Ok
 pooltApoReg ... 3.98 kB. Count 0 Ok
 pooltAssignBase ... 3.98 kB. Count 0 Ok
 pooltCodeBlock ... 7.97 kB. Count 0 Ok
 pooltCodeBlockEnv ... 3.98 kB. Count 0 Ok
 pooltCodeBlockRef ... 3.98 kB. Count 0 Ok
 pooltCodeBlockInstr ... 3.98 kB. Count 0 Ok
 pooltAPXmlDocInterface ... 3.98 kB. Count 0 Ok
 pooltClassData ... 71.95 kB. Count 0 Ok
 pooltAPXmlItem ... 3.98 kB. Count 0 Ok
 pooltPrgFunctXRefEle ... 3.98 kB. Count 0 Ok
 pooltIdentifier ... 3.98 kB. Count 0 Ok
 pooltNamedVarXVarRef ... 3.98 kB. Count 0 Ok
 pooltParmDefDet ... 3.98 kB. Count 0 Ok
 pooltVarList ... 3.98 kB. Count 0 Ok
 pooltStatList ... 7.97 kB. Count 0 Ok
 pooltParmList ... 3.98 kB. Count 0 Ok
 pooltArrayElement ... 3.98 kB. Count 0 Ok
 pooltBaseIf ... 3.98 kB. Count 0 Ok
 pooltMethod ... 71.95 kB. Count 0 Ok
 pooltFindObject ... 3.98 kB. Count 0 Ok
 pooltSelfVar ... 3.98 kB. Count 0 Ok
 pooltClassProp ... 3.98 kB. Count 0 Ok
 pooltClassInstance ... 3.98 kB. Count 0 Ok
 pooltMethodCall ... 3.98 kB. Count 0 Ok
 pooltReturn ... 3.98 kB. Count 0 Ok
 pooltStaticEnv ... 3.98 kB. Count 0 Ok
 pooltPrivateEnv ... 3.98 kB. Count 0 Ok
 Default Memory Pool ... 1003.36 kB. Count 156 Ok
TOTAL POOLED MEMORY ... 1518.52 kB.
========================================================================= */

No momento que o Totvs Application Server inicia o “shutdown” do serviço, são mostrados os processos de execução AdvPL ( no nosso caso nenhum ), os processos internos ( Detailed Process List ), e os acumuladores de memória internos do Application Server em uso. Depois que todos os processos foram encerrados internamente, é mostrada uma nova foto dos acumuladores de memória, logo após a mensagem “*** SMARTHEAP POOL STATUS AFTER FINISHED PROCESSES ***”

Reparem que todos os acumuladores estão com o contador ZERO, exceto o Default Memory Pool, com 156 buffers. Este é o comportamento esperado de um servidor de aplicação sem Leak. O acumulador do Default Memory Pool pode variar um pouco, entre 180 e 220 elementos, as vezes mais ou menos … porém os demais acumuladores precisam estar com o contador ZERO após a mensagem “*** SMARTHEAP POOL STATUS AFTER FINISHED PROCESSES ***”. Caso exista um ou mais ( quando aparecem são sempre mais de um ) acumuladores com contador maior que zero, algum programa durante a sua execução deixou ponteiros de memória não referenciados sem desalocar a memória. Isso caracteriza a existência de Leak.

Achando o “culpado”

Agora vêm a parte difícil … Normalmente um servidor de aplicação AdvPL executa diversos códigos e rotinas de vários usuários, diariamente. Uma ou mais destas rotinas durante a sua execução não liberaram alguma porção da memória. Descobrir qual delas é o desafio. Normalmente a garimpagem desses dados é realizada com procedimentos de paradas mais frequentes dos serviços do TOTVS Application Server, e a geração do log de console desde o momento que o serviço foi iniciado.

A cada parada, verificamos se houve leak registrado no final, e verificamos pelos logs as rotinas executadas, e verificamos também se alguns erros críticos específicos foram registrados durante a execução do serviço.

Culpado No 1 : Access Violation e “Failure on Thread Delete”

Qualquer ocorrência de Access Violation ( ou violação de endereço de memória ) registrada pode interromper ou impedir a execução da finalização normal de um processo, o que pode deixar memória alocada após o final do processo, ou pior: Conexões com o DBAccess e com o License Server , arquivos abertos no disco, registros bloqueados no DBAccess, etc… PAra as aplicações endereçadas por estas conexões, a conexão ainda está ativa, pois o processo não foi totalmente finalizado. Neste caso, estes recursos somente serão liberados quando você finalizar o servidor de aplicação — parar o serviço do Tots Application Server.

Quando temos uma ocorrência de Access Violation, devemos verificar onde ela é reproduzida, se ela acontece em um ou mais programas, e tentar fazer uma receita de bolo para reproduzir a ocorrência, abrindo um chamado na Totvs. Caso não seja possível determinar uma receita de bolo para reproduzir o problema, o que caracteriza uma ocorrência esporádica, normalmente é disponibilizado ao cliente uma build “Debug” do TOTVs Application SErver. Esta build é capaz de gerar um arquivo chamado “core_nnnn.dmp”, contendo uma foto da memória inteira do TOTVS Application Server, no momento em que aconteceu um Access Violation. A análise deste log ajuda o departamento de tecnologia a identificar o que aconteceu.

Normalmente cada nova liberação dos executáveis e DLLS que compõe o TOTVS Application Server possuem correções, melhorias e proteções das situações encontradas entre os releases que podem ser a causa provável de uma ocorrência desta natureza. Por isso a TOTVS recomenda que o cliente esteja com a última build atualizada e liberada no Portal. Caso o Access Violation aconteça na última build, é gerada uma Build DEBUG do Application Server da última build para a avaliação da ocorrência.

Culpado No 2 : Impressões no servidor usando um Driver com interface

Caso você use a impressão do ERP para o Pool de impressão do Windows no servidor (e não no SmartClient), e você tenha no servidor um driver de geração de PDF (por exemplo), e o TOTVS Application Server sendo executado como serviço… caso você dispare uma impressão para este driver, ele vai tentar abrir uma caixa de diálogo na máquina onde está sendo executado o Totvs Application Server … mas como o APPServer está sendo executado como serviço, além disso não ser possível, isso literalmente TRAVA aquele processo, mantendo todos os recursos abertos, conexão com o DBAccess, processo preso no Protheus Monitor, conexão aberta no License Server, etc… Fatalmente, quando você tentar parar o serviço do Totvs Application Server, ele não vai sair … você vai precisar literalmente “chutar” o processo pelo TasK Manager do Windows. Cada processo que ficou travado vai permanecer como um “zumbi” no APPServer, e o acúmulo destes processos vai afetar a memória disponível para a execução dos programas.

Culpado No 3 : Falha de algum recurso do AdvPL

Durante a execução de um programa, alguma das funções ou classes chamadas não desalocou um ou mais ponteiros de memória, normalmente isso acontece quando uma situação particular nos destrutores não foi prevista. Neste caso, deve ser feita aquela garimpagem nos logs gerados durante a execução do Appllication Server, inclusive comparando logs onde houve a geração de leak e onde não houve a geração de leak, para tentar identificar quais programas são os possiveis candidatos, para fazer um teste pontual: Subir um TOTVs Application Server isolado, onde apenas um usuário conecta, e testa um programa. Após o usuário terminar o programa, o servidor é baixado, e o log deve ser verificado para ver se houve Leak.

Houve umm caso de Leak de memória que somente era reproduzido quando o usuário entrava no ERP pelo SIGAADV, e após o Menu do ERP ser mostrado, o usuário clicava e um “cadeado” na interface para trocar de módulo, e após realizar a troca de módulo pelo menos uma vez, a primeira janela da troca de módulo com todos os seus componentes e variáveis era “largada” na memória, ocupando um espaço significativo, obrigando os administradores do ambiente a reiniciar os serviços do Protheus antes do inicio do expediente.

A descoberta da causa desta ocorrência somente foi possível após analisar os logs de execução de vários serviços, e perguntando para os usuários qual foi o roteiro de trabalho deles durante o dia, comparando com o roteiro de trabalho de outros usuários que executaram as mesmas rotinas, mas não houve registro de leak no servidor usados por eles. Após fazer um teste usando uma rotina e trocando de módulo, foi percebido que somente neste caso o Leak era gerado.

Não é fácil encontrar um Leak nem um Access Violation. Uma vez que exista uma receita de bolo que sempre reproduza sempre o problema, o departamento de tecnologia consegue atuar, e muitas vezes propor um contorno. O difícil está em encontrar qual é a receita.

Uma vez que seja comprovada a existência de um Leak, é possível a geração de uma build de gaveta ( normalmente partindo da ultima versão liberada) com uma instrumentação específica para ajudar a encontrar ou a chegar perto do ponto que “sobrou” na memória, porém esta instrumentação deixa o processamento um pouco mais pesado.

Conclusão

Existem caminhos para chegar até a raiz do problema, alguns não são tão fáceis, tudo é diretamente proporcional a complexidade do problema. Espero que estas informações deem a todos alguma “luz” no fim do túnel e um ponto sólido de partida.

Abraços, e até o próximo post, pessoal 😀

Referências

PROBLEMA. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2014. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Problema&oldid=40832056>. Acesso em: 29 jul. 2015.

Performance e escalabilidade – Profiler AdvPL

Introdução

Qualquer rotina de um sistema, mais cedo ou mais tarde, e por inúmeras razões, pode apresentar em um ou mais instantes, uma perda de desempenho. E, geralmente quando isto acontece, principalmente em rotinas onde uma eventual demora impacta na produtividade ou na operação do cliente, problemas desta natureza saltam para o nível “crítico” muito rápido.

Mesmo lidando apenas com as causas prováveis, existem muitas variáveis envolvidas que atuam direta ou indiretamente nas causas. As mais comuns são: Chamada de um procedimento ou rotina com parâmetros de execução que consideram uma faixa muito grande de dados, quantidade de dados na base para o período de processamento muito acima da média (causas sazonais, picos), processos adicionais que podem ou não pertencer ao sistema levando o ambiente a picos de utilização de recursos ou esgotamento de recursos computacionais, defasagem do “sizing” de máquina devido a aumento de usuários ou módulos adicionais implantados, troca da versão do ERP sem reavaliar o “sizing” das máquinas, até razões ligadas ao desenvolvimento da rotina, como por exemplo um algoritmo projetado sem levar em conta o crescimento da base, e por aí vai.

Identificando as causas

Dada uma determinada situação de perda de desempenho, nomalmente avaliamos se algum dos recursos computacionais do ambiente está perto de seu limite, como utilização de disco, fragmentação de arquivos, consumo de banda de rede, cpu das máquinas de processamento e do banco de dados, além de levantar os detalhes de como a perda de desempenho foi percebida, qual era o tempo médio antes e agora, e se houve alguma alteração no ambiente ( infra, repositórios, binário) entre o cenário antes da lentidão ser percebida e a partir do momento que ela passou a ser reproduzida, além de avaliar se ela ocorre sempre a qualquer momento ou especificamente em momentos de muitos acessos (usuários) na aplicação.

Como eu já havia mencionado, normalmente as causas prováveis envolvem o excesso de consumo de um ou mais recursos, prejudicando o desempenho de algumas rotinas. Quando mais genérico e de uso comum o recurso em questão, mais rotinas ou até mesmo o ambiente inteiro pode ser afetado.

Como os algoritmos normalmente consomem não especificamente um recurso, mas múltiplos recursos simultaneamente, onde a quantidade e os recursos consumidos podem oscilar dentro da mesma aplicação, mesmo que você determine usando uma ferramenta de monitoramento de sistema operacional, que uma das causas da lentidão seja uma fila excessiva de espera por requisições de leitura ou gravação em disco, ainda assim existe a necessidade de se avaliar qual ou quais programas estão contribuindo para que este recurso seja esgotado.

Profiler AdvPL

Neste momento, existe um recurso que se faz muito útil, chamado Profiler (ou Profile) de exexcução de aplicações, que nada mais é do que um registro de log detalhado de quais recursos foram utilizados por um processo, e em qual quantidade, avaliando o prorcesso do início ao final.

Para auxiliar a avaliação de aplicações nestes momentos, ou mesmo para auxiliar o desenvolvedor para avaliar o impacto de alterar uma determinada rotina, foi criado já faz alguns anos o recurso de Profiler AdvPL. Este recurso inicialmente estava disponível no IDE do Protheus, onde a geração do log detalhado de execução era feito apenas para programas iniciados a partir de um processo de depuração. Este recurso também foi incorporado no novo IDE (TDS – Totvs Development Studio), inclusive está documentado no link “Desempenho de Programas (Profile)” — http://tdn.totvs.com/pages/viewpage.action?pageId=24347198

Posteriormente, foi criado uma parametrização de ambiente, para permitir a geração deste log detalhado para jobs, e sem a necessidade de iniciar o processo usando o TDS. Este recurso está documentado no link “LogProfiler – Profiler de execução de programas AdvPL” — http://tdn.totvs.com/pages/viewpage.action?pageId=6065063

Resumo da Ópera

Nos links acima, principalmente no último link, são detalhados não apenas como o recurso é habilitado, mas quais informações são geradas no log, e como elas devem ser interpretadas, inclusive são mencionados os casos mais comuns encontrados em análises de execuções já realizadas e identificadas durante o desenvolvimento e testes dos produtos.

A vantagem destes recursos de Profiler é a possibilidade de serem executados no mesmo ambiente em momentos distintos, onde o log de duas execuções completas que apresentaram diferenças gritantes de desempenho podem ser comparados, tornando mais claro qual ou quais pontos ou funções usadas na aplicação apresentaram diferenças de tempo de resposta. Esta análise, associada a um resultado de monitoramento de recursos do ambiente, pode gerar justamente a rastreabilidade entre as aplicações e os recursos que estão sendo consumidos (e esgotados) do ambiente.

Normalmente, após o código e o ambiente ser analisado, pode-se chegar a algumas conclusões: O algoritmo pode ser melhorado para usar menos recursos para obter o mesmo resultado esperado, ou o algoritmo já está optimizado segundo o paradigma original do processo, e precisa ser refatorado para atender aquela demanda, ou ainda devem ser revisadas e refeitas as distribuições das aplicações no ambiente, e em determinados pontos pode haver a necessidade de acrescentar mais recursos computacionais ( Mais servidores para distribuir a carga, ou escalar verticalmente alguma das máquinas envolvidas que não está dando conta da carga de requisições em questão.

Podem ser necessárias mais de uma ação ao mesmo tempo, dependendo de onde está (ou estão) o(s) gargalo(s) de desempenho da aplicação.

Conclusão

Avaliar um log de Profile de aplicação não é necessariamente complexo, porém o log de execução gerado é muito grande. Neste log, devem ser identificados os pontos realmente significantes, para então decidir onde e como atuar no ambiente ou na aplicação. Uma boa parte da teoria está nos documentos publicados na TDN, com exemplos práticos de análise e tudo mais. O que vai fazer a diferença na sua eficácia de análise é pegar um problema real, e analizá-lo com uma lupa. Quanto mais você meter a mão na massa e avaliar cenários diferentes, mais eficiente será a sua análise. Não têm mágica, você precisa sujar um pouco a mão de graxa e fritar alguns neurônios. Não se acanhe, não é um bicho de sete cabeças … esse aí têm no máximo duas !!!

E, para encerrar 2014, desejo a todos vocês um Feliz Natal atrasado, e um próspero Ano Novo adiantado !!!

Até o próximo post, pessoal 😉