Acesso a dados – DBAccess

Introdução

Nos posts anteriores dos tópicos de acesso a dados, abordamos o mecanismo ISAM e o mecanismo relacional, e vimos alguma coisa do DBAccess da TOTVS, o Gateway de acesso a dados relacionais usado pelo Protheus Server. Hoje vamos aprofundar um pouco o conhecimento sobre esse Gateway, na forma de um FAQ, com muitas perguntas bem cabeludas …rs… seguidas das respectivas respostas.

O que é o DBAccess ?

O DBAccess é um gateway de acesso a dados para bancos de dados relacionais, utilizado pelo Protheus Server, para fornecer uma camada de abstração de acesso a arquivos usando a abordagem ISAM em um SGDB relacional, e uma camada de acesso de consulta relacional através de Queries.

Por que o DBAccess foi construído ?

As versões de ERP da Microsiga, antes do Protheus, originalmente foram concebidos para acessar os formatos de dados nativos do Clipper, que eram DBFNTX e DBFCDX, onde a aplicação acessava diretamente os dados. Com a utilização do driver do ADS Client no Clipper, foi possível utilizar o ADS Server, um SGDB ISAM com arquitetura client-server para o armazenamento dos dados do ERP.

Com o surgimento e popularização dos Bancos de Dados relacionais para baixa plataforma, onde os SGDBs relacionais permitiam mais recursos, consultas mais fáceis e mais rápidas, e novas funcionalidades, além de estender a capacidade do acesso a dados ISAM/xBase, houve a necessidade da Microsiga criar um Gateway de acesso a dados relacionais, que permitisse a execução do código legado (escrito em uma abordagem ISAM) em um SGDB relacional, e permitir acesso a novas funcionalidades disponíveis apenas no ambiente relacional.

Onde entra o TOPConnect nisso ?

O TopConnect foi a primeira geração de Gateways de acesso a dados, sua primeira versão foi concebida antes do Protheus, através de uma parceria comercial com uma empresa estrangeira de desenvolvimento de software. Ela permitia aplicações em Clipper acessar bancos de dados DB2/400 (IBM AS400), Sybase, Microsoft SQL 6.5, Sybase, PostgreSQL, Informix, DB2 UDB, Oracle 6, e MySQL. O TOPConect era escrito em C, e para cada SGDB existia uma versão específica do Gateway.

A Microsiga optou por desenvolver uma tecnologia própria de acesso, sendo assim desenvolvido o TOPConnect 4. Usando C++ com uma abstração de acesso a dados, o mesmo executável possuía as implementações para usar qualquer um dos SGDBs homologados. Como o Gateway é atrelado a versão de produtos do ERP Microsiga, a partir do Protheus 10 ele passou a chamar-se DBACcess, incorporando as implementações para as novas versões dos SGDBs. Com a fundação da TOTVS, o DBAccess passou a ser responsabilidade do Depto de Tecnologia da TOTVS.

O DBAccess consome muito recurso ?

Como um Gateway de acesso a dados, seu consumo de recursos de Memória é diretamente proporcional ao número de conexões, versus o número total de tabelas e queries abertas, versus o tamanho de registro da tabela. Seu consumo de CPU e Rede é diretamente proporcional à quantidade de requisições realizadas pela aplicação AdvPL ao gateway. O consumo é relativamente pequeno por tabela aberta, mas considerando um ambiente de 3000 conexões, onde cada pode abrir e manter abertas mais de 100 tabelas e queries, o consumo de memória pode atingir ou ultrapassar 4GB de RAM.

Como o DBAccess acessa o SGDB ?

O Acesso ao banco Oracle é feito via OCI (Oracle Client Interface), e todos os demais bancos são acessados via ODBC, usando a interface ODBC fornecida pelo fabricante do SGDB. É necessário que a ODBC ou OCI do SGDB em questão esteja instalada no equipamento onde o DBAccess será utilizado.

Por que o DBAccess não têm balanceamento de carga ?

Bem, como o acesso aos dados é feito apenas pelo SGDB, ele sempre será o destino final de todas as conexões. Então, não faz muito sentido “balancear” as conexões do DBAccess. Porém, em ambientes com mais de 2 mil conexões, pode ser muito interessante você usar mais de uma instância de DBAccess em máquinas distintas. Esta topologia é chamada de “DBAccess Distribuído”. Nesta topologia, um ou mais serviços de Protheus apontam para um DBAccess, e todos os DBACcess devem usar uma ODBC apontando para um único SGDB, além de cada DBAccess apontar para uma instância única, nomeada de “master”, que exerce o papel de servidor de locks de registro e locks virtuais.

O que acontece se eu colocar 2 DBAccess apontando para o mesmo Database ?

Se eles não estiverem configurados na topologia de “DBAccess distribuído”, cada um deles vai olhar para o seu próprio controle de emulação de Lock ISAM de registros. Neste caso, pode haver invasão de lock (duas conexões de instâncias distintas vão conseguir fazer RecLock() no mesmo registro), o que pode levar a quebra de integridade dos dados do registro, pois somente a última atualização irá prevalecer), e pode causar DeadLock em transações no SGDB.

Por que o DBAccess não usa o Lock do Banco de Dados ?

O mecanismo de bloqueio de registros do SGDB é de uso intrínseco do Banco de Dados, e não oferece a flexibilidade exigida para o mecanismo de acesso ISAM emulado. Mesmo se fosse construído um mecanismo de emulação direta no SGBD, a quantidade de IOs e instruções para emular isso no SGDB inivabilizariam seu uso por questões de desempenho. O mecanismo de Locks ISAM e Locks Virtuais é feito na memória do DBAccess, de forma muito rápida e eficiente.

Por quê existem os campos R_E_C_N_O_ e D_E_L_E_T_ ?

Como o TOPConnect surgiu devido a necessidade de executar um código escrito originalmente para engine ISAM, precisamos destes campos para emular o comportamento original do ISAM: Uma coluna interna para registrar a numeração sequencial de inserção (ordem física e identificador único de registro), e o campo “D_E_L_E_T” para marcar os registros que foram “marcados para deleção permanente” através da função DbDelete().

E por quê existe a coluna R_E_C_D_E_L_ em algumas tabelas ?

Quando foi implementado o conceito de criação de índice único nas tabelas do ERP, no Protheus 8 se eu não me engano, caso a tabela possua um índice de chave única definido pelo ERP, eu não posso ter um registro “ativo” na base com a mesma chave única. Porém, como as tabelas do DBAccess trabalham com o conceito de deleção “lógica” de registros, marcando os registros a serem eliminados fisicamente usando a função DbDelete(), eu posso ter um ou mais registros marcados para deleção, com a mesma chave única. Por exemplo, eu crio a tabela TESTE com a coluna CHAVE, e crio um índice de chave única com a coluna CHAVE. Se eu usar apenas esta coluna na minha chave única, se eu inserir um registro “000001”, depois deletá-lo ( campo D_E_L_E_T_ está com um “*”), e depois tentar inserir outro registro com “000001”, o SGDB não vai deixar …

Então, quando o ERP solicita ao DBAccess a criação de um índice de chave única, o DBAccess acrescenta na tabela a coluna de controle “R_E_C_D_E_L_”, coloca ela como último campo da chave única, e as colunas R_E_C_D_E_L_ de todos os registros marcados para deleção ( D_E_L_E_T_ = ‘*’) são alimentadas com o conteúdo do R_E_C_N_O_, e todos os registros não marcados para deleção ( ativos ) ficam com este campo ZERADO. Deste modo, eu posso ter um e apenas um registro não marcado para deleção com uma determinada chave única, mas eu posso ter um ou mais registros com o campo chave com este mesmo valor, caso eles estejam marcados para deleção.

Cada vez que o Protheus pede ao DBAccess para marcar um registro como “Deletado” através da função DbDelete(), o DBAcccess verifica se a tabela possui a coluna R_E_C_D_E_L_ , e atualiza ao mesmo tempo a coluna “D_E_L_E_T_” com ‘*’ e o R_E_C_D_E_L_ com o R_E_C_N_O_. Caso um registro seja recuperado, isto é, seja removida a marca de deleção, usando a função DbRecall(), a coluna R_E_C_D_E_L_ é atualizada para “0” zero. Deste modo, se você tentar inserir um registro ativo com uma chave duplicada, o SGDB não deixa fazer a inserção, e se você tentar desmarcar um registro deletado que tenha uma chave, e já existir um registro ativo (não marcado para deleção) com a mesma chave, o SGDB não permite a recuperação do registro, pois isto viola a chave única definida.

O que é mais rápido : Uma consulta ISAM ou por QUERY ?

Normalmente o acesso por Queries é mais rápido, sendo visivelmente mais rápido em leituras de registros sequencialmente. Um acesso de leitura no resultset de uma Query reflete os dados obtidos no momento que a Query foi executada. Já a leitura por acesso ISAM emulado retorna cada registro sob demanda, no momento que o registro é posicionado. A consulta por Query permite JOINS, para busca de dados de tabelas relacionadas. NO ISAM, você é obrigado a posicionar manualmente cada tabela relacionada e realizar a busca sob demanda.

Verdade que o DBSeek() é lento no DBAccess ?

Um DBSeek() no DBAccess procura posicionar no primeiro registro da ordem informada cujos campos que compõe a ordem do índice em uso que foram informados na instrução atendam a condição de busca. Se você faz um DBSeek() para posicionar no primeiro registro imediatamente superior a ordem desejada ( ou “SoftSeek” ) , DBSeek( cChave , .T. ) … o DBAccess pode submeter internamente várias Queries ao SGDB até achar o registro que satisfaça esta condição. Trocar um DBSeek() simples, que apenas verifica se um registro existe ou não, e trocar isso por uma Query, fatalmente vai ser mais lento, pois a abertua de uma Query para pegar apenas um registro vai usar 3 IOs, enquanto o DBSeek() faz apenas um. No caso da busca pelo último registro de uma determinada chave, uma Query pode ser mais rápida. Por exemplo, para saber qual foi a última data de um determinado evento, onde a tabela é indexada pelo código do evento mais a data, ao invés de pegar o código do evento, acrescentar uma unidade, procurar pelo primeiro registro da próxima sequência com uma chave parcial e SoftSeek, e depois fazer um Skip(-1) — coisa que é muito rápida e comum de ser feita no DBF — podemos simplesmente fazer uma query com “select max(datadoevento) as ultdata from tabela where codigodoevento = ‘xxxxxx’ and D_E_L_E_T != ‘*'”

E as inserções e Updates, são rápidas ?

Ao comparamos inserções via instrução direta “INSERT” e a inserção via DBAppend() — modo ISAM — , ambas são muito rápidas. A inserção tradicional de registros pelo AdvPL, usando DBAppend() e replace(s), possui um mecanismo de otimização que prioriza o envio das informações de inserção em apenas um evento de I/O. Porém, dependendo do que é executado durante a atribuição dos campos, enquanto o registro atual está em estado de inserção, o Protheus Server faz um “Flush” da inserção parcial, com os dados disponíveis até aquele momento, podendo executar mais I/Os de update para o mesmo registro enquanto a inserção não for finalizada. Este assunto será mais detalhado em um tópico específico de dicas de desempenho (Acelerando o AdvPL)

O que é o erro -35 ?

O código -35 é um código de erro genérico retornado pelo DBAccess quando a conexão com o SDGB não foi bem sucedida, OU quando a conexão não atendeu aos requisitos de operação com o SGDB. Para saber o que realmente causou o -35, o monitor do DBAccess deve ser acessado, e o registro de eventos de erro do DBAccess deve ser verificado. Pode ser desde o SGDB estar fora do ar, ou usuário e senha não configurados corretamente no DBAccess, falha de criação ou verificação de alguma tabela interna de controle do DBAccess, etc.

O que é o erro -2 ?

O erro -2 indica uma interrupção inesperada da conexão entre o Protheus Server e o DBAccess. Da mesma forma que o erro -35, o -2 pode ter diversas causas, desde um problema de rede entre o Protheus Server e o DBAccesse, ou entre o DBAccess e o SGDB, até um término anormal do processo que estava atendendo esta conexão no DBAccess (Assert Exception, Out Of Memory, Access Violation). Deve ser verificado o LOG do DBAccess para ver os detalhes do que aconteceu.

E os demais erros ?

Cada um possui um significado geral, para um tipo de operação que não foi bem sucedida. O código do erro apenas informa que uma operação falhou, porém somente descobriremos a causa efetiva da falha olhando o log de registro de erros do DBAccess. A lista de códigos de erro do DBAccess está na TDN, no link (http://tdn.totvs.com/pages/viewpage.action?pageId=6064500).

O DBAccess faz “leitura suja” de registros ?

Sim, para a grande maioria dos bancos. Devido a natureza das transações do SGDB, e da necessidade do comportamento esperado do AdvPL em ter acesso de leitura a qualquer registro sem espera ou bloqueio, mesmo que o dado esteja sendo alterado dentro de uma transação por outro processo, foi necessário definir explicitamente o nível de isolamento “READ UNCOMMITED”. O Banco Oracle não tem a possibilidade de leitura “suja”, ele é endereçado com “Read Commited”, mas ele permite que as demais conexões acessem a última versão committed, mesmo que exista uma transação aberta atualizando aquele dado.

O DBAccess “prende” conexões ?

Uma conexão feita pelo Protheus ao DBAccess faz o DBAccess abrir uma conexão no SGDB. Uma vez aberta, boa parte do tempo a conexão no DBAccess fica aberta, esperando o Protheus pedir alguma coisa ao DBAccess, como por exemplo: abrir uma tabela, uma query, posicionar em um registro, inserir um registro, executar uma Stored Procedure, etc. Ao receber uma requisição de abertura de Query, por exemplo, o DBAccess solicita ao SGDB a abertura da Query, e aguarda do SGDB um retorno ( sucesso ou erro). Enquanto isso, o Protheus fica esperando o DBAccess retornar. A Aplicação AdvPL estabelece a conexão com o DBAccess, e pode encerrar a conexão durante a execução da aplicação, e mesmo que o programa não desconecte, quando a Thread da aplicação AdvPL terminar, qualquer conexão com o DBAccess que tenha sido deixada aberta é encerrada. Existem algumas condições que o SGDB pode demorar a responder, como por exemplo um DeadLock no SGDB, a abertura de uma query muito complexa sobre um número muito grande de tabelas, a execução de uma Stored Procedure que fará muito processamento, etc.

Existe uma condição onde uma conexão pode ficar aberta no DBAccess, dando a impressão de estar “presa”: Caso a thread que está executando uma aplicação AdvPL, que consequentemente conectou no DBAccess, seja interrompida por uma ocorrência crítica de erro, como um Access Violation ou Segment Fault (Invasão de memória), e ocorra falha no destrutor da thread. O Processo em si não está mais no ar, mas a conexão permanecera aberta até que o serviço do Protheus seja finalizado.

E, existe também uma última condição, que pode fazer uma conexão no DBAccess permanecer aberta indefinidamente — ou até que o DBAccess seja finalizado, ou a conexão seja encerrada pelo DBAccess Monitor: Caso exista algum problema de rede entre a aplicação Protheus e o DBAccess, e ocorra uma queda na conexão TCP, quando o DBAccess está “IDLE”, esperando pelo Protheus pedir alguma coisa. Se a informação da perda da conexão não chegar ao Socket do DBAccess, ele vai ficar esperando indefinidamente, mantendo a conexão aberta e os recursos pertinentes alocados.

Existem estudos em andamento para criar novas funcionalidades na ferramenta, como permitir derrubar uma conexão do SGDB através do DBAccess, cancelando uma Query ou Stored Procedure, entre outros que eu não posso comentar agora …rs… Aguardem as próximas versões … 😀

Como o DBAccess encerra uma conexão pelo DBAccess Monitor ?

Um processo de conexão entre o DBAccess e um SGDB é mantido no ar enquanto existe a conexão entre o Protheus e o DBAccess. Logo, o DBAccess fica em um laço de espera por requisições. A cada intervalo de alguns segundos que o Protheus não envia nenhuma requisição, e a cada requisição recebida, ele verifica um flag de controle de processo, que indica se o DBAccess Monitor pediu para aquela conexão ser encerrada. Quando você pede ao DBAccess Monitor para encerrar uma conexão, ele apenas seta este flag no processo, que somente será considerado e avaliado quando o DBAccess está esperando por uma requisição do Protheus. Se  DBAccess pediu pro banco a execução de uma Stored PRocedure, que pode demorar de segundos a minutos, e neste meio tempo você pedir para a conexão ser encerrada através do DBAccess Monitor, ele somente vai encerrar a conexão com o SGDB quando a procedure terminar, e ele voltar ao loop de requisições. Se por um acaso você quer que a procedure ou query que está demorando seja interrompida, somente um DBA com acesso ao SGDB consegue derrubar o processo usando algum mecanismo de administração do SGDB.

E se eu derrubar o processo no SGDB ?

Se o DBAccess está executando algo no SGDB, e aguardando por um retorno, e o processo for finalizado no SGDB, o DBAccess receberá um retorno de erro, e repassa ao Protheus. Se o DBAcccess não estava fazendo nada no SGDB, e você derruba a conexão no SGDB …. O DBaccess somente vai “perceber” que a conexão foi pro espaço quando o PRotheus pedir alguma coisa, e o DBAccess for tentar pedir algo para o SGDB através da conexão que não existe mais …

Por que o DBAccess não traz campos MEMO em Query ?

Um campo MEMO pode conter muito mais dados em um campo do que em muitos registros. Uma tabela DBF por padrão traz todos os campos do registro atualmente posicionado a cada cada DbSkip() ou DBSeek(). Para ganhar desempenho e não onerar os processos do sistema que fazem leituras sequenciais em processamentos, os campos MEMO são trafegados somente sob demanda, quando abrimos a tabela em modo ISAM emulado, e somente quando a aplicação tenta ler ou acessar um campo Memo do registro atualmente posicionado.

Como os campos MEMO foram implementados no DBAccess para garantir a compatibilidade com o campo Memo do DBF, a forma de armazenar esta informação em cada SGDB é escolhida diretamente pelo DBAccess, a critério dele. Por questões de economia e otimização de recursos (estamos falando da aplicação que nasceu em um tempo onde uma rede 100 MBits era um “luxo”), foi decidido não retornar campos MEMO em Queries, o DBAccess não faz o Bind dos dados de campos usados como “Memo”. Por isso, se hoje você precisa da informação de um ou mais campos memo de uma tabela, você pode selecionar os dados necessários por Query, porém você recupera também o número do registro (R_E_C_N_O_) na Query, usando um outro nome para este campo, e mantendo a tabela original aberta em modo ISAM emulado, você posiciona no registro desejado usando DbGoto(), e então faz um Fieldget() do campo MEMO.

Quem alimenta o R_E_C_N_O_ da tabela na inserção de registros ?

O DBAccess possui um cache das estruturas de colunas e índices das tabelas, alimentado sob demanda, e um controle de numeração e locks. Ele guarda o número do ultimo registro inserido em uma lista em memória, e cada nova inserção incrementa o último registro da tabela na lista. Este processo é muito rápido, porém torna algo nada prático você fazer inserções através de uma Query ou Stored Procedure. Foi criado um mecanismo para permitir o DBAccess criar uma tabela com numeração automática de R_E_C_N_O_ pelo SGDB, disponibilizado para o FrameWork AdvPl, onde alguns novos módulos desenvolvidos no ERP Microsiga já se utilizam desta funcionalidade. Porém, isto ainda não é extensível para as demais tabelas dos módulos do ERP por questões de impacto. Todas as rotinas hoje escritas que alimentam tabelas, por exemplo via Stored Procedure, precisariam ser refatoradas para contemplar esta funcionalidade, pois nenhuma delas poderia mais fornecer um número de R_E_C_N_O_ ao fazer inserção na tabela, e imediatamente após a inserção, algumas delas precisam obter do SGDB qual foi o número do registro inserido.

Não é algo simples de ser feito, ainda mais “de uma vez”. Para cada SGDB o DBAccess define uma forma de criar o campo com auto-incremento, algumas usando um tipo de campo de auto-incremento do próprio SGDB, outas através de gatilhos criados internamente pelo DBAccess no momento de criação da tabela com estas características.

Para que serve a tabela TOP_FIELD, criada pelo DBAccess ?

Como a aplicação foi feita para emular ISAM, no momento da criação da tabela pelo Protheus, a estrutura da tabela é informada ISAM/DBF, onde especificamos campos do tipo “C” Caractere, “N” numérico (com precisão inteira ou decimal), “D” Data, “L” Lógico e “M” Memo. Porém, como o DBAccess escolhe cada tipo de campo que se adéqua melhor a necessidade, ele precisa guardar algumas definições que o Protheus forneceu na criação da tabela. E ele faz isso na tabenla TOP_FIELD.  Se você copia uma tabela diretamente de um Ddatabase para outro, no mesmo banco, mas não copia as definições da TOP_FIELD, todos os campos “D” data serão mostrados como “C” Caractere de 8 bytes, cmapos “L” lógicos serão “C” caractere de 1 byte, contendo “T” ou “F”, e todos os campos numéricos vão vir com uma precisão de 15 dígitos com 8 decimais.

Por quê as tabelas do DBAccess usam constraints DEFAULT ?

Não existe o valor “NULL” nas bases ISAM usadas pelo ERP Microsiga. Logo, mesmo que o campo esteja vazio, ele precisa ter o seu conteúdo default ( caracteres em branco, números com 0, data com string em branco, booleano com “F”). A aplicação conta com este comportamento, e as queries e joins foram construídas baseadas nesta premissa.

Como o DBAccess faz alteração estrutural na tabela ?

Através da função TC_Alter(), o DBAccess recebe a estrutura atual da tabela e a nova estrutura desejada, e determina para cada SGDB a sequência de operações necessárias para ajustar a tabela para ela ficar com a definição da nova estrutura, através do cruzamento das estruturas. Campos existentes na estrutura antiga e não existentes na nova são removidos, campos existentes na nova e não existentes na antiga são criados, e campos existentes nas duas podem ter suas características alteradas. Apenas as trocas de tipo de “C” Caractere para “N” numérico e vice-versa suportam manter os dados nos campos.

Como funciona a transação no DBAccess ?

Cada SGSB homologado possui transacionamento atômico por instrução. Isto significa que, caso seja disparado uma execução SQL de um Update que afete várias linhas, se uma não pode ser alterada, nenhuma será. Quando precisamos garantir que várias operações em um bloco sejam completas em conjunto, usamos as instruções BEGIN TRANSACTION e END TRANSACTION do Advpl, onde todas as instruções executadas dentro deste bloco não vão fazer COMMIT das informações no SGDB, isto será feito apenas no END TRANSACTION. Se ocorrer algum erro durante o processo, entre o Begin e o End transaction, todas as operações feitas a partir do BEGIN TRANSACTION serão descartadas. Por baixo destas instruções existe uma implementação que depende do ambiente ERP Microsiga, isto é, o processo em execução precisa ser um programa do ERP chamado a partir do Menu, ou um Job que faça a inicialização do ambiente ERP usando por exemplo o comando PREPARE ENVIRONMENT ou a função RcpSetEnv(). Estes tratamentos também estão atrelados às funções RecLock() e MsUnlock() do Framework AdvPL do ERP Microsiga.

O DBAccess pode conectar com outros SGDBs ?

Sim, ele pode. Porém, esta conexão é feita via uma conexão ODBC genérica, que não permite a emulação ISAM. Praticamente qualquer ODBC que você possa registrar como fonte de dados de ODBC no Windows pode ser acessada. Usando a build mais atual do DBAccess, existe uma aba de configuração de ODBC genérica. Você pode estabelecer a conexão usando a função TClink(), informando o banco “ODBC/” mais o alias da fonte de dados cadastrada no Gerenciador de Fontes ODBC do sistema operacional. Com esta conexão, voce pode abrir Queries, que devem ser montadas de acordo com a capacidade e regras da ODBC utilzada, onde os dados retornados podem ser char/varchar ou numéricos, usando DbUseArea() com TcGenWry(), e pode executar instruções diretamente no SGDB através da função AdvPL TcSqlExec(). Isto pode ser muito útil para realizar integrações com outras fontes de dados.

Conclusão

Eu acho que com estes parágrafos, dá pra matar um pouco a curiosidade sobre o DBAccess e seu papel no acesso a dados do ERP Microsiga. Caso algúem tenha mais alguma pergunta a acrescentar sobre este assunto, insira a sua pergunta como um comentário deste post 😀 PAra dúvidas e sugestões de outros assuntos, me envie um e-mail com o assunto “BLOG” para siga0984@gmail.com 😀

Novamente, agradeço a audiência, e desejo a todos TERABYTES de sucesso 😉

Até o próximo post, pessoal 😀

Escalabilidade e Performance – Segredos

Introdução

Outo dia li um post muito interessante, onde o autor menciona alguns “segredos” para uma aplicação escalável, com algumas técnicas comuns utilizadas em aplicações WEB ( Vide post original nas referências no final do Post). Resolvi me basear nele para exemplificar o que cada um dos tópicos elencados poderia agregar ao sistema, e levantar algumas questões de aplicabilidade de cada um, dentro do ambiente AdvPL.

“STATELESS”

“Se você quer um sistema ou um serviço escalável, com certeza você quer que todas as requisições para este serviço sejam stateless.
Mas por que? Simplesmente por que caso em um futuro próximo você precise rodar a sua aplicação em um cluster, você não prende um cliente a um nó do cluster, cada requisição pode ir para o nó do cluster que estiver com a menor carga naquele momento, fazendo com que o tempo de resposta aquela requisição seja o menor possível, mantendo o nó do cluster que vai atender a esta requisição ocupado o menor tempo possível.” (Sobre Código: Os 5 segredos para um sistema altamente escalável, por Rodrigo Urubatan)

Esta afirmação casa com os princípios de escalabilidade e performance já mencionados em posts anteriores, mais especificamente sobre uma regra que diz “não estabeleça afinidade”. Basicamente, o “stateless” significa que uma requisição de processamento não deve depender de um estado específico de uma requisição anterior. Isto significa que o agente de processamento da requisição não deve reter nenhuma informação da requisição anterior, o que torna possível distribuir uma requisição de processamento para qualquer agente disponível no cluster.

Isto pode ser aplicado a diversos tipos de processamento, mas normalmente requer que a aplicação seja desenhada para trabalhar desta forma. Por exemplo, hoje quando desenvolvemos uma aplicação AdvPL para ser acessada através da interface do SmartClient, a aplicação trabalha com uma conexão TCP persistente, onde o programa AdvPL responsável pelas instruções de interface, também é responsável por montar o ambiente de execução e executar as aplicações de processamento e acesso a SGDB e meta-dados no mesmo processo.

Por exemplo, uma inclusão de um cliente através de um programa de interface SmartClient no AdvPL, é iniciada ao acionarmos um botão na janela, que executa uma função do código AdvPL pelo Application Server dentro do próprio processo, que acessa o SGDB e executa efetivamente a inserção e demais integrações e gatilhos sistêmicos relacionados (pontos de entrada do ERP), mantendo a interface em “espera” enquanto o processo é executado.

Para esta operação ser executada em um outro nó do cluster, a aplicação precisaria ser desenhada para, no momento de submeter a inclusão de um cliente, a função deveria gerar um request de inclusão de cliente, e despachá-la para um agente de distribuição de processos, que iria alocar um processo em espera no cluster de processamento, para este processo dedicado realizar o acesso ao banco e concluir a operação, liberando a interface para iniciar uma nova inclusão ou outra operação. A dificuldade de lidar com estes eventos é desenvolver um mecanismo resiliente, que permita gerar informações e um “BackLog” do que foi feito, pois procurar um erro de processamento ou comportamento em um ambiente assim, sem rastreabilidade das operações, não será uma tarefa fácil.

Um WebService por natureza é concebido como STATELESS. Existem algumas tecnologias que permitem criar afinidade e persistência em WebServices, porém isto foge ao objetivo principal da natureza das requisições e dos serviços que a implementação original se propõe a fazer. Se uma aplicação em WebServices precisa de afinidade e persistência, o uso de WebServices não parece uma boa escolha. Nem toda a operação pode ser totalmente assíncrona, mas muitas etapas de processo compensam o custo da transformação, revertida em escalabilidade.

“REQUEST RESPONSE TIME” (RRT)

Partindo de um ambiente de serviços distribuídos, onde vários processos podem realizar operações específicas, é fundamental que as requisições importantes ( ou prioritárias) sejam atendidas com o menor tempo de resposta possível. É o mesmo princípio dos caixas de um banco: Quando temos mais clientes do que caixas disponíveis, é formada uma fila, e o primeiro caixa que terminar o atendimento torna-se disponível e chama o primeiro da fila.

Se os caixas forem lentos, quando começar uma fila, as pessoas desta fila vão demorar para serem atendidas. Então, ou você coloca mais caixas, ou você melhora o desempenho dos caixas. Pensando por exemplo na priorização de atendimento a idosos, portadores de necessidades especiais, gestantes e mães com crianças de colo, são criadas duas filas de atendimento, vamos chamá-las de “normal” e “prioritária”. Todos os caixas podem atender a qualquer pessoa da fila, porém ocorre um escalonamento do atendimento.

Quanto mais rápido e eficiente for o atendimento de um caixa, mais clientes por hora ele será capaz de atender. A mesma regra vale para requisições de processamento (SOA/RPC/SOAP/REST). Se você tem um volume diário de X requisições, sujeitas a variações ou picos, e cada caixa aberto consegue atender em média N requisições por minuto, e você tem Y caixas abertos, se todas as X requisições chegarem ao mesmo tempo, as Y primeiras são atendidas imediatamente, e as demais entram na fila. Com todos os caixas atendendo, se ninguém mais entrar na fila, voce vai manter todos os caixas ocupados e trabalhando por M minutos até não ter mais ninguém na fila.

Pegamos o total de requisições (ou clientes) X , dividimos pelo numero de processos disponíveis (ou caixas abertos) Y, e temos uma média de quantas requisições devem ser processadas por cada processo (quantos atendimentos cada caixa deve fazer). Multiplicamos este número pelo tempo médio de processamento da requisição (tempo de atendimento), e teremos uma ideia de quanto tempo será necessário para eliminar a fila. Se chegam 20 requisições, e existem 4 processos disponíveis, cada um deles vai realizar 5 processamentos. Se cada processamento tem um tempo médio de 3 segundos, cada processo vai permanecer ocupado processando por 5×3 = 15 segundos. Se neste meio tempo não chegar mais ninguém na fila, em 15 segundos a fila acaba. Um caixa trabalhando sem parar em um minuto (60 segundos) , consegue atender (sem pausa ou intervalo) 60/3 = 20 requisições em um minuto. Como temos 4 processos, todos juntos conseguem atender 20 x 4 = 80 requisições por minuto.

Se dobrarmos o número de processos (8), conseguimos atender 160 requisições por minuto. Se o tempo de cada processamento (3 s.) diminuir pela metade (1,5 s.), conseguimos atender a 160 requisições por minuto com apenas 4 processos dedicados. Muitas vezes não temos como aumentar mais os processos, por limites de sistema ou esgotamento de recursos, então quanto mais leve e rápido for cada processo, melhor.

Usando AdvPL, existe flexibilidade em se criar pools de processos nativos, e dependendo de sua carga e consumo de memória, colocar múltiplos processos para atender requisições simultâneas. Porém, devem ser observados se os processos aderem aos princípios de paralelismo, senão a aplicação vai jogar memória fora e não vai escalar.

“CACHE”

Lembrando de uma das regras básicas de desempenho de tráfego de informações, buscar uma informação pela rede costuma ser mais lento que ler do disco, e pegar ela da memória é mais rápido do que ler do disco. Mas como não dá pra colocar tudo na memória, e estamos falando de paralelismo em sistemas distribuídos, usar um CACHE das informações mais repetidamente lidas na memória de cada máquina é uma forma bem eficiente de reduzir o RRT. Mas, lembre-se: Não é tudo que voce precisa colocar em um cache … apenas as informações mais acessadas, e com baixa volatilidade. Usar um cache local na memória do servidor, compartilhado pelos demais processos, pode economizar muitas idas e vindas de requisições pela rede.

Um exemplo disso se aplica mais facilmente a uma aplicação WEB dinâmica, onde uma página de promoções acessa o SGBD para mostrar os produtos em promoção. Se as promoções são alteradas diariamente, vale a pena fazer um cache dessa página, e removê-la do cache apenas quando houver alteração das promoções. Para um ERP, um cache interessante seria os meta-dados de algumas tabelas. Se boa parte dos usuários do sistema abre as mesmas tabelas e telas, a leitura destas definições poderiam ser colocadas em um cache na máquina dos slaves, alimentado sob demanda e com uma validade em tempo pré-determinada. O primeiro usuário que abre uma tela de cadastro de produtos ou pedidos alimenta o cache, e todos os demais usuários conectados em serviços naquela máquina pegam as definições do cache local, muito mais rápido, e sem consumir recurso de rede.

Existem meios de se criar caches em AdvPL, usando por exemplo variáveis globais, ou mesmo utilizar um cache de mercado (MemCacheDB, por exemplo), desde que seja escrito um Client em AdvPL que use a api client de Socket’s do Advpl (Classe tSocketClient) para fazer a comunicação. RPC e variáveis globais podem fazer uma bela dupla, mas vão precisar de um certo nível de controle e gerenciamento implementado manualmente, mas isto não inviabiliza esta alternativa.

“REMOTE DATA”

Quando falamos de aplicação Client-Server em Cluster, devemos ter em mente que as informações comuns a determinados processos precisam estar em um lugar onde todos os nós de processamento tenham acesso. Parte da premissa de não criar afinidade. Fica mais fácil citar um exemplo de servidor WEB com Upload de imagens, onde você grava a imagem em uma pasta do servidor: Ao invés de compartilhar esta pasta pelo sistema de arquivos, é possível criar um servidor dedicado a conteúdo estático, e fazer um mecanismo de cache ou réplica nos nós, se ou quando necessário.

A abordagem do AdvPL parte da premissa que cada serviço slave tenha acesso ao RootPath do ambiente, compartilhado pelo sistema operacional do equipamento. Definitivamente um excesso de acessos concorrentes pode prejudicar uma escalabilidade.

“REVERSE PROXY”

O exemplo de proxy reverso também se aplica mais em escalabilidade de ambientes WEB. Ele serve de “Front-End” da sua aplicação, permite a utilização de técnicas de Cache, mascaramento de IP, “esconde” a sua infra-estrutura interna, entre outas funcionalidades. O detalhe importante disso é que um Proxy Reverso, pelo fato de estar “na frente” de tudo, ele passa a ser um SPOF (Single Point of Failure). Se o Proxy Reverso “morrer”, sua aplicação morre junto.

Para aplicações SOA, uma alternativa interessante é usar o já mencionado “controlador” de requisições. Cada nó do cluster pergunta para um controlador de serviço onde está o nó mais próximo disponível, e cada serviço de controle fala com os demais. Assim, você pode deixar serviços ativos em vários nós, e caso um nó saia do ar, o controlador direciona a requisição para outro serviço disponível.

Resumo geral dos conceitos

Cada uma das técnicas ou “segredos” abordados exige que a aplicação seja desenvolvida para aderir a estes paradigmas. Uma aplicação Client-Server monolítica e de conexão persistente não se encaixaria neste modelo sem uma grande refatoração. É mais complicado gerenciar múltiplos recursos em múltiplos equipamentos, é mais complicado desenvolver pensando nestas características, a administração deste tipo de ambiente requer instrumentações, alertas e procedimentos da forma mais automatizada possível. Imagine você ter que conectar e abrir cada uma das máquinas para procurar por um LOG de erro específico, ou tentar descobrir por que ou como uma informação inconsistente foi parar na base de dados. Cada camada precisa estar “cercada” por mecanismos que visem minimizar inconsistências e evitar que elas se propaguem, além de permitir rastrear os caminhos da informação.

No AdvPL são fornecidos recursos como os WebServices (SOAP e REST), RPC nativo entre servidores, IPC entre processos da mesma instância de Application Server. A junção de todos eles pode tornar real e segura uma implementação desta natureza. Agrupando requisições por funcionalidade, e tendo um painel de gerenciamento e monitoramento dos recursos, você consegue medir quanto “custa” cada pedaço do sistema em consumo de recursos, e consegue lidar melhor com estimativas de crescimento, identificação de pontos críticos de melhoria, tornando assim possível a tão sonhada “escalabilidade horizontal” — em grande estilo.

Conclusão

A automatização de processos de Build e testes têm se mostrado muito efetiva em cenários de desenvolvimento e roll-out de produtos. Eu acredito que já esteja mais que na hora de criar mecanismos assistidos de deploy e mecanismos nativos de painéis de gestão de configuração e ambiente. Uma vez estabelecida a confiabilidade nos mecanismos de monitoramento, ações podem ser programadas e realizadas automaticamente, minimizando impactos e reduzindo os riscos de ter uma indisponibilidade total do ambiente computacional.

Criar uma aplicação que desde a sua concepção já considere estes pontos, será tão trabalhosa quando migrar uma aplicação que conta com comportamentos sistêmicos persistentes, mas ambos os esforços devem compensar o potencial de expansão que pode ser adquirido com esta abordagem.

Novamente, agradeço a audiência, e desejo a todos TERABYTES de sucesso 😀

Até o próximo post, pessoal 😉

Referências

Sobre Código: Os 5 segredos para um sistema altamente escalável, por Rodrigo Urubatan. Acessado em 06/12/2015 em <http://sobrecodigo.com/os-4-segredos-para-um-sistema-altamente-escalavel/>.

Stateless protocol. (2015, August 20). In Wikipedia, The Free Encyclopedia. Retrieved 01:33, October 25, 2015, from https://en.wikipedia.org/w/index.php?title=Stateless_protocol&oldid=677029705