Limites do AdvPL – Parte 01

Introdução

Quanto mais informações nós temos sobre a ferramenta que nós utilizamos, mais assertivas e robustas serão as soluções que projetamos nela. Partindo desta premissa, vamos dar uma beliscada em alguns limites do AdvPL, relacionados a strings e precisão numérica.

Strings em AdvPL

A linguagem AdvPL permite que, uma variável que contenha o tipo “C” (Caractere) seja capaz de armazenar e lidar com uma sequência de zero ou mais bytes, onde cada byte têm o valor de 0 a 255. Na prática, uma variável caractere no AdvPL é um container de valor variável para números inteiros de 0 a 255. Por exemplo, uma string contendo apenas a letra “A” (maiúscula) , é representada na memória do servidor de aplicação por um byte contendo o valor decimal “65”. Da mesma forma, qualquer arquivo no disco, em qualquer formato ou extensão, nada mais é do que uma sequência de bytes. A questão toda reside na interpretação da sequência de bytes para cada formato de arquivo, e para isso devemos considerar a interface em questão.

Porém, o foco no momento são limites, certo? Então, uma variável do tipo Caractere no AdvPL pode conter 1MB -1 bytes. São 1048575 caracteres, desde a primeira versão do AP5 em 1999. Caso seja necessário manipular strings maiores em memória, é possivel fragmentar a string e colocá-la em elementos de um array.

Recentemente foi implementada uma configuração no TOTVS Application Server, disponível a partir da Build 7.00.131227A (Vide nota de documentação na TDN no link http://tdn.totvs.com/pages/viewpage.action?pageId=161349880), para permitir aumentar o tamanho máximo de uma string no AdvPL.

POrém, devemos lembrar que, quanto maior a String na memória, mais lentas e pesadas vão ficar as operações com ela, como buscas e trocas ( at, substr, stuff ), e o limite aumentado vale apenas para o TOTVS Application Server: Configurar o limite máximo de String para 2 MB não vai permitir você gravar essa string “inteira” em um registro no banco de dados, mesmo usando uma configuração para aumentar o tamanho de bytes suportados por um campo “M” Memo usando um banco relacional através do DBAccess, o limite de campo Memo é 1 MB. Essa mesma string com mais de 1 MB, você não vai conseguir trazer ela inteira pra dentro de um componente GET de múltiplas linhas na interface para edição.

Números em AdvPL

Já uma variável numérica no AdvPL é representada em memória usando uma variável de ponto flutuante de precisão dupla. São usados 8 bytes, e o número armazenado pode ser imenso, porém a precisão máxima respeitada por este numero são 15 digitos significativos. Isto é, um número inteiro na casa dos fantastilhões, a partir do 16o dígito da esqueda para a direita, não é mais “confiável”, e isto não vai causar erro de estouro no AdvPL, apenas o número não reflete a realidade.

Por exemplo: A divisão 321654987023 / 13, têm com resultado 24742691309.46153846 (…) Neste calculo, foi utilizada a calculadora do Windows, e considerando o resultado até a 8a casa decimal. Agora, vamos contar quantos dígitos tem este numero da esquerda para a direita, ignorando o ponto decimal: São 11 dígitos inteiros, e 8 decimais, somando 19 dígitos. Podemos afirmar no Advpl que o resultado até o 15o dígito é íntegro. Segure abaixo um fonte de exemplo realizando esta conta com AdvPL:

user function pocn1()
local n1 := 321654987023
local n2 := 13
local n3
n3 := n1 / n2 
conout(str(n3,20,6))
return

Numero mostrado no console : “24742691309.46149800”
Numero correto : “24742691309.46153846”

O fonte AdvPL converte o numero para string, para poder mostrá-lo no console, porém o tamanho do número extrapola a precisão do AdvPL, até a 3a casa decimal os resultados estão iguais, e a partir da 4a casa decimal o resultado difere. Se este número tivesse menos dígitos inteiros, a precisão decimal poderia ser maior, mas ainda limitada a 10 casas decimais. Logo, quanto maior a parte inteira do número em ponto flutuante, menor é a sua parte decimal que mantém a precisão no cálculo.

Para lidar corretamente com estas aproximações, dentro dos limites suportados em um cálculo, limitamos a precisão do resultado usando as funções Round() e NoRound(), para truncar ou arredondar um resultado de uma divisão, por exemplo.

Decimais de ponto fixo

Para suprir a necessidade de sistemas específicos, que precisam de uma precisão numérica maior do que a nativa do AdvPL, foi criado um novo tipo numérico “F” , conhecido como Decimal de Ponto Fixo , ou Fixed Size Decimal. Através de uma série de funções ( http://tdn.totvs.com/display/tec/Decimais+de+Ponto+Fixo ), podemos criar e manipular números com até 128 dígitos significativos. Porém, a aritmética destes números também é feita através destas funções. Não foi implementada interoperabilidade com os operadores aritméticos padrão da linguagem, apenas algumas funções numéricas nativas da linguagem suportam diretamente um argumento do tipo “F”.

Vamos ver o mesmo exemplo acima, reescrito para usar decimais de ponto fixo:

user function pocn2()
local n1 := DEC_CREATE ( "321654987023", 12,0 ) 
local n2 := DEC_CREATE ( "13", 2,0 ) 
local n3 := DEC_DIV(n1,n2) 
Local n4 := DEC_RESCALE(n3,8,2)
conout(cvaltochar(n4))
return

A conta é realizada, e o resultado da divisão cria um novo numero decimal de ponto fixo com uma precisão “imensa”. Como nós queremos o resultado pelo menos até a 8a casa decimal, usamos DEC_RESCALE() para re-definir a precisão máxima de decimais do número, e cValToChar() para convertê-lo para string, para ser mostrado no log de console. O Resultado esperado é : “24742691309.46153846”

Uma coisa interessante sobre a utilização das funções DEC_ … o valor decimal de ponto fixo no AdvPL é criado dentro do runtime da linguagem mediante estas funções. Este valor não é suportado para gravação no DBAccess no formato “F” Fized-Size. A camada de dados do AdvPL provê ainda os dados numéricos com os mesmos limites originais. Neste caso, se for necessário gravar um número com esta precisão, por hora ele deve ser gravado na base de dados como uma string, e re-convertido para “F” na memória caso necessário realizar operações aritméticas com ele.

Conclusão

Existem vários limites no AdvPL, apenas alguns estão documentados na TDN, e outros limites serão explorados em tópicos posteriores. Alguns limites são muito grandes, mas não é uma boa prática chegar perto desses limites, sob pena da aplicação tornar-se muito pesada. Quanto menores e mais bem organizadas são as informações e os algoritmos, mais leve fica a aplicação.

Até o próximo post, pessoal 😉

Performance e Escalabilidade – Processos e prioridades

Introdução

Desde os tempos neolíticos da Informática, as técnicas de optimização, os “mandamentos” e boas práticas continuam as mesmas, com pequenas variações. Levando em conta que qualquer ambiente computacional, não importa seu tamanho, possui capacidade finita de recursos, e em cada post sobre este assunto descemos (ou subimos) um degrau, podemos nos aprofundar um pouco mais nos processos do sistema.

Conceito

Antes de mais nada, vamos conceituar um processo: Cada programa em execução pode ser chamado de “processo”, e basicamente nós temos 2 tipos de processos: Interativos (processos com interface, síncrona ou assíncrona), e processos batch (sem interface visual), como WebServices, Jobs e Schedulers.

Priorizando

Nem sempre você terá recursos computacionais para rodar todos os processos ao mesmo tempo, isto pode fatalmente acontecer em um momento de pico. E, neste caso, você têm que “se virar” com os recursos disponíveis. Se todos os processos possuem a mesma prioridade para o sistema operacional, quando a soma de processos em execução bater 100% do consumo de CPU, todos os processos em execução entram na mesma fila de prioridade para usar uma fatia de tempo do processador. Logo, todos os processos vão ficar lentos, dividindo recursos computacionais.

Quando definimos prioridades diferentes para os processos em um servidor, quando houver mais processos brigando por processamento do que CPUs disponiveis, os processos de menor prioridade serão mais interrompidos e terão fatias de tempo menores do que os processos de maior prioridade, e sendo assim, apenas os processos de menor prioridade serão penalizados, e consequentemente os processos de prioridade maior vão continuar rodando com o mesmo desempenho. Você apenas vai sentir que o sistema está se “arrastando” quando não houver CPU o bastante para os processos de prioridade mais elevada, e neste caso não têm como fugir, eles vão ficar mais lentos.

Quem deve ser priorizado

Normalmente são deixados com menor prioridade os processos em batch e scheduler, que costumam têm alto consumo de CPU, mas que podem ser sacrificados com um maior número de interrupções que os demais, ficando obviamente mais lentos. Um resultado de uma consulta de pendência financeira on-line ou verificação de status para liberação de crédito é mais importante, ainda mais se este procedimento está sendo executado por um operador de caixa, com o cliente e um carrinho de compras cheio de itens na frente dele. Caso este processo fique mais lento, o operador de caixa têm que dar um “veja bem” no cliente, perguntar o resultado do último jogo de algum campeonato de futebol, se ele sabe a previsão do tempo para amanhã, se ele encontrou na loja tudo o que ele precisava, … , e depois de alguns segundos de um silêncio constrangedor, se mesmo assim o sistema ainda não tiver terminado, ele vai ter que pedir desculpas ao cliente e dizer que “o sistema está lento”.

Mexendo na prioridade de processos

Normalmente, os sistemas operacionais de mercado oferecem mecanismos para alterarmos a prioridade de um processo de uma aplicação em execução. Em uma aplicação multi-thread, ao mexer na prioridade do processo principal, consequentemente todas as threads deste processo serão afetadas.

Ao mexer na prioridade de processos, não recomendo nunca subir a prioridade para algo acima de “normal”, mas sempre diminuir a prioridade dos processos que podem ser penalizados em caso de sobrecarga. Ao elevar a prioridade de um ou mais processos, caso estes se tornem ávidos consumidores de CPU, os processos de prioridade menor serão severamente penalizados, e isto pode inclusive levar o equipamento a um estado de indisponibilidade (igh, o servidor travou).

Fugindo da afinidade

Um dos princípios de escalabilidade e performance mencionava algo sobre o estabelecimento de afinidade. A partir do momento que você elege um servidor apartado para rodar um tipo de tarefa, se este servidor der problema, você terá dois problemas. Normalmente acaba sendo utilizado algum tipo de segregação de processos em equipamentos específicos para processos pesados, que ao serem executados, “sugam” o que podem do servidor onde estão sendo executados, tornando a execução de outros processos no mesmo impraticável, e isto acontece apenas em alguns períodos do dia.

Usando a priorização de processos, podemos colocar estes processos pesados em mais de um equipamento, e diminuindo a prioridade destes processos, eles podem conviver na mesma máquina sem prejudicar os processos de maior prioridade. Para receber processos de menor prioridade, um determinado servidor precisa ter uma “folga” de CPU, a máquina não pode estar trabalhando “no talo”, caso contrário os processos de menor prioridade não vão ser executados.

Aplicando este conceito

Os experimentos com esta funcionalidade ainda estão em andamento, inicialmente em ambientes Windows. Na prática, um equipamento bem utilizado é aquele onde realmente usamos tudo o que a máquina têm a oferecer de recursos, porém dentro de um limite de enfileiramento em que os tempos de processamento e retorno fiquem dentro de um patamar aceitável, e tomando cuidado para não deixar os recursos se esgotarem.

Pensando neste cenário, podemos colocar serviços de prioridade “normal” com afinidade por core da maquina, e serviços de menor prioridade com afinidade para um ou múltiplos cores da maquina, e deixamos um core fora das regras de afinidades dos serviços, para uso livre do sistema operacional.

Deste modo, mesmo que todos os processos de todos os serviços entrem em um pico de consumo de CPU, os processos de menor prioridade vão praticamente parar para os de prioridade normal continuarem operando, e mesmo que os processos de prioridade normal coloquem cada um a sua CPU “na tampa”, o equipamento servidor não deve tornar-se indisponível, pois um core está “fora” da regra de consumo.

Conclusão

O esboço deste artigo estava a algum tempo da lista de publicações, porém os testes ainda não foram concluídos. De qualquer modo, é um tema interessante a ser pesquisado, e pode render bons frutos ao ponto que compreendemos um pouco mais como os recursos de um equipamento são provisionados. Este artigo terá continuação, assim que os testes pertinentes sejam concluídos.

Até a próxima, pessoal 😉

Escalabilidade e Performance – SOA

Introdução

Retornando ao tema de escalabilidade e performance, já vimos em tópicos anteriores abordagens sobre Grid Computing, Jobs , Hash Map e Filas, além de alguma abordagem teórica sobre escalabilidade e performance, técnicas e abordagens.

SOA

Uma destas abordagens, baseada nos princípios da computação distribuída (Grid) é chamada de SOA – Services Oriented Architecture. Esta abordagem de desenvolvimento propicia facilidade na integração de sistemas, e por ser embasada nos princípios da computação distribuída, pode ajudar a tornar partes críticas do sistema mais escaláveis.

Arroz com feijão

Imagine que dentro do seu software, cada bloco de operações seja encapsulado — por exemplo — em um Web Service, ou qualquer outro meio de comunicação de dados tipo request/response. Por exemplo, a classe de cadastro e operações com clientes pode ser chamada facilmente pelo próprio sistema ou por um sistema de tereiros através de uma API padronizada.

Incluir um título a receber passa a ser uma operação que utiliza uma API Client do módulo financeiro, para enviar a requisição do título a ser incluído, e todas as demais operações relacionadas a um título de recebimento também pode ser implementada nesta API. Você pode colocar várias APIs em um ou mais servidores, e utilizar gateways ou controladores, para enfileirar requisições ou distribuir as requisições entre várias instâncias da mesma API distribuidas no ambiente.

Prós e Contras

Como cada parte de um sistema possui suas particularidades, e nem todas podem ser apropriadas para serem paralelizáveis, escolhe-se esta abordagem onde ela é mais necessária e terá ganhos visíveis. Distribuir as aplicações exigem um nivel de controle maior e domínio na aplicação, pois como as camadas são interdependentes, se uma camada não funcionar direito, ela deve prover os detalhes do que entrou e do que saiu, pois quando um procedimento de depuração for necessário, todas as camadas envolvidas serão verificadas para ver qual camada não apresentou o comportamento sistêmico esperado.

Porém, uma vez encontrada a camada, basta subir apenas o serviço desejado no seu ambiente de depuração, refazer a chamada desta API com os mesmos parâmetros, e depurar apenas esta camada. As escolhas dos protocolos de comunicação são importantes nos quesitos de praticidade e desempenho. XML para requisições curtas é ótimo, o overhead não é significativo, JSON também é uma boa alternativa.

Já para requisições maiores, pode ser cogitado o uso do sistema de arquivos, ou um FTP SErver como servidor de sistemas de arquivos para funcionalidades especificas.

Exemplo de uso

Vamos pegar por exemplo uma funcionalidade comum entre todos os sistemas ERP que utilizam Workflow: O Envio de e-mails. Quando cada programa usa uma API direta para envio de e-mails, cada uma é responsável pelo envio e tratamento de erro de conexão ou envio, e normalmente o processamento do envio do e-mail é feito no mesmo processo do programa, logo o usuário aguarda a resposta do servidor de e-mail ou do programa de conexão, e é informado se o e-mail foi enviado ou se houve alguma falha ( problema de conexão, senha inválida, falha de envio ).

Agora, imagine quantos casos de envio de e-mail que o operador realmente precisa saber na hora se o e-mail foi realmente enviado. Existem muitos casos onde a criação de um serviço independente e remoto, dedicado a envio de e-mails, pode ser útil e dar agilidade ao sistema.

Neste serviço, você poderá configurar um ou mais servidores SMTP alternativos para envio de e-mais, e poderá colocar um ou mais processos dedicados a envio. Cada programa que usar a API client deste serviço simplesmente chama a API informando o e-mail a ser enviado. O Serviço de envio pode colocar este e-mail em uma fila em memória ou no próprio banco de dados, e os processos dedicados ao envio desempilham os e-mails e os enviam. Quem chama a API pode fornecer informações de rastreabilidade, e caso a API falhe ao enviar, o e-mail continua na fila, e o serviço deve prover uma interface de monitoramento e administração, para saber se e quais e-mails não foram enviados, e por quê.

Caso seja necessária uma confirmação de envio, você pode construir um mecanismo de “gatilho”, onde a API de e-mails, assim que conseguir enviar um e-mail, execute uma função fornecida como parâmetro, para por exemplo atualizar um flag de uma tabela de confirmação, relacionada ao programa que originalmente fez a requisição.

Para garantir a disponibilidade do sistema, você pode subir dois ou mais serviços de envio de e-mail em duas maquinas distintas, e configurar estes serviços em um cluster. Caso o servidor onde esteja o serviço de pool de e-mails caia, o cluster subirá a outra instância do serviço de envio em outro nó do cluster.

Conclusão

Sempre deve haver um meio-termo na escolha de determinados recursos, e isto normalmente deve ser guiado pela necessidade. Quanto mais especialista for uma parte do sistema, mais complexa será a manutenção desta. Normalmente distribuir a aplicação exige mais skill no desenho e implementação, mas as abordagens de desenvolvimento buscam dividir coisas complexas em coisas simples interligadas, e cada coisa no seu lugar, o que torna naturalmente mais fácil reaproveitar código e componentes, mas aumenta o número de partes e coisas envolvidas.

Para o próximo post, estou separando algumas sugestões de assunto, e teremos um pouco mais de código AdvPL. Até o próximo, pessoal 😀

Referências

SERVICE-ORIENTED ARCHITECTURE. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2014. Disponível em: <http://pt.wikipedia.org/w/index.php?title=Service-oriented_architecture&oldid=39359250&gt;. Acesso em: 3 maio 2015.