Acesso a Dados AdvPL com c-Tree – Parte 01

Introdução

Depois de tantas introduções sobre acesso a dados em AdvPL, vamos descer um degrau na implementação dos drivers c-Tree no Application Server AdvPL. Vamos entender o que é, como funciona, o que exatamente ele faz, do que se alimenta, como cuidar, etc…

O que é e como funciona

A empresa de software norte americana Faircom(R) disponibiliza uma engine multi plataforma de acesso a dados ISAM e relacional, conhecida por c-Tree. A Microsiga fez uma parceria comercial com a Faircom, para adotar o driver em suas aplicações, como uma alternativa ao suporte de arquivos locais, temporários e meta-dados (dicionários do ERP).

A Faircom(r) fornece as três versões do c-Tree Server ( Stand-alone ou “ctree local”), c-Tree Server, e c-Tree Embedded ( c-Tree BoundServer ou CtreeServer DLL), em uma build exclusiva, compatível apenas com o Application Server.

As funcionalidades são fornecidas por um conjunto de APIs, de alto e baixo nível, que permitem a criação de arquivos ISAM, com transações ACID, registros de tamanho fixo e variável, e um forte controle de integridade. Uma tabela em formato DBF parece um “txt” perto do nível de controle de uma tabela c-Tree.

APIS fornecidas junto do Application Server

Junto do pacote de executáveis do Application Server, para todas as plataformas homologadas, são fornecidas as dlls do cTree “local” (ctreestd.dll), API client do c-Tree Server (mtclient.dll), e um c-Tree BoundServer (ctreecsv.dll + arquivos auxiliares).

c-Tree Local

Trata-se de uma versão stand-alone da API de acesso a dados, sem transacionamento ou cache, que faz leitura e gravação diretamente nos arquivos em disco. Usada pelo ERP Microsiga a partir da versão AP6 (Advanced Protheus 6), para acessar o arquivo de senhas do ERP (sigapss.spf) e os arquivos de help de campo por idioma (sigahl?.hl?). Estes arquivos são conhecidos por “SuperFiles”, eles possuem uma estrutura interna fixa, onde o mesmo container de arquivo armazena dados de tamanho variável e índices. A API de acesso a estes arquivos não é publicada por razões de segurança, e não é possível chamar diretamente a API de dentro de User Functions.

Em instalações de instância única de Application Server, como por exemplo o módulo FrontLoja do ERP (onde é instalado um Application Server e um SmartClient nas estações de trabalho), ele pode ser usado para gerenciar uma base local da estação, sincronizada pela aplicação com a base principal na retaguarda do sistema.

Não deve ser usada para ambientes com mais de um Application Server acessando o mesmo diretório raiz de ambiente (rootpath), como por exemplo em balanceamento de carga. Caso mais de um Application Server acesse o mesmo RootPath, é necessário utilizar um c-Tree Server.

c-Tree Server

Serviço dedicado de acesso a arquivos c-Tree, multi-thread e multi-plataforma. Deve ser instalado na máquina física onde está armazenado o RootPath do ambiente. Sua utilização envolve a instalação do serviço do c-Tree Server, e alterar todos os arquivos de configuração do Application Server (appserver.ini) para indicar que o Protheus está configurado em modo client-server com o c-Tree(chave ctreemode=server na seção [general]), e devemos informar a URN da instância do c-Tree Server em seção especifica (chave CTServerName na seção [ctreeserver]). Caso não especificada, o default é FAIRCOMS@localhost.

c-Tree BoundServer (ou c-tree Server DLL, c-Tree Embedded)

Trata-se de uma compilação do c-Tree Server em DLL (creeecsv.dll), que possui todas as funcionalidades de um c-Tree Server, mas pode ser carregado diretamente pelo Application Server, eliminando a camada de rede no acesso a dados. Passou a ser distribuído junto do pacote de executáveis do Application Server a partir da build 7.00.121227P, para ser utilizada para a geração de arquivos temporários “locais” na máquina onde está sendo executada a instância do Application Server. Pode ser usada para gerenciar as tabelas locais e meta-dados (dicionários do ERP), desde que a instalação não possua outro Application Server usando a mesma pasta raiz do ambiente (rootpath).

E como o Application Server usa isso ?

Através da configuração ctreemode, na seção [general] do arquivo de configuração do Application Server (appserver.ini), definimos qual API será usada para acesso aos arquivos c-Tree daquela instância do servidor. Caso não especificada, o default é “local”.

Quando o ctreemode é “local”, cada acesso a um arquivo no formato c-Tree, seja o “superfile”, ou os meta-dados dos ambientes configurados com localfiles=ctree, ou o driver “CTREECDX”, vão ser requisitados para a DLL do c-Tree Local (ctreestd.dll).

Quando informamos que o ctreemode é “server”, o Protheus carrega a dll client do c-Tree Server (mtclient.dll), e todas as requisições para acesso aos arquivos c-tree são realizadas para o c-tree server configurado na seção [ctreeserver].

Quando informamos que o ctreemode é “boundserver”, o protheus carrega a c-Tree Server DLL (ctreecsv.dll), e faz todo o acesso através dela. A vantagem da c-tree server DLL em relação ao c-Tree local, usadas em ambiente de instância única de Application Server, é que o c-Tree Server DLL possui os caches de leitura e escrita, que dão maior desempenho, escalabilidade e segurança para a aplicação.

E o que ele faz de diferente de um DBF ?

Em uma palavra … TUDO. A estrutura interna de um arquivo DBF armazena apenas dados de tamanho fixo em blocos sequenciais dentro do arquivo, e a área de header apenas guarda a estrutura da tabela e um contador de registros. Um arquivo DBF é praticamente um “TXT melhorado” por dentro. Não há amarração explícita de um DBF com um arquivo indexador criado no disco. Se você cria um índice no disco, e esquece de abrir o índice ao fazer uma inserção ou atualização na tabela, o índice fica desatualizado, e quando você for utilizá-lo, ene não apresenta erro, apenas deixa de encontrar as informações e se comporta de forma errática.

Um arquivo c-Tree possui um header com os detalhes completos do arquivo, inclusive o path completo em disco onde o mesmo foi criado. Não é uma “tabelinha”, é um container de persistência de dados transacionados. Cada índice permanente criado para a tabela também é registrado no header do arquivo de dados do c-Tree. Quando você abre uma tabela c-Tree, se ela foi trocada de diretório, ou se algum índice permanente não estiver no disco ou não estiver sincronizado, a API retorna erro de abertura por inconsistência.

Como era possível pintar e bordar com um DBF, o Application Server verifica se existe procedimento automático para recuperar a tabela para ela ser aberta novamente. Se houve mudança de diretório, os índices permanentes do header são excluídos e os paths internos são ajustados. Se apenas algum índice permanente não foi encontrado, mas não houve mudança no path, os índices que faltam são recriados automaticamente. Caso a tabela apresente alguma inconsistência de abertura, como um fechamento inesperado, o Protheus solicita um rebuild da tabela ao c-Tree.

Questões de Desempenho

Junto da segurança adicional do c-Tree, a camada de transacionamento de arquivos provê um overhead em operações de inserção e update. Mesmo as transações atômicas são primeiro gravadas em LOG, para depois serem efetivadas nas tabelas. A dll do c-tree “local” não tem esta camada de transacionamento, tudo é escrito no disco direto. Isto passa a impressão que o c-tree local é muito mais rápido.

E, realmente, se você coloca até 8 ou 10 conexões concorrentes em um Application Server usando c-Tree local, o desempenho seria melhor. Porém, ao passar de 12, 13 conexões, o desempenho dos processos começa a cair, a partir do momento que o sistema operacional começa a enfileirar as requisições de disco, o desempenho cai proporcionalmente conforme o volume de requisições cresce. Quando usamos um c-Tree Server, os caches de leitura e gravação permitem a aplicação aguentar mais requisições sem que a queda no desempenho seja proporcional.

No ponto onde a curva de desempenho de ambos se encontra (por exemplo, com 12 processos simultâneos c-tree local e c-tree server “empatam” no desempenho), se dobramos a quantidade de processos, o ctree local vai ficar duas vezes mais lento (50 % de perda) , enquanto o c-tree server perdeu apenas 15% do desempenho.

Este cenário fica muito pior quando utilizamos o c-Tree Server pela rede, pois quanto o Application Server e o c-Tree Server estão na mesma máquina, eles estabelecem uma conexão TCP por “loopback”, sem efetivamente usar a interface de rede. Quando colocamos um serviço slave do Protheus em outra máquina, cada I/O realizado para, por exemplo, ler um próximo registro da tabela, é enviado pela rede física em um pequeno pacote TCP, e por mais rápido que seja o seu retorno, a camada de comunicação física pode interferir no desempenho de uma sequência de milhares de requisições em até 8 vezes. A natureza das operações é altamente sensível a latência de rede.

Exemplo prático: Uma leitura sequencial de 20 mil registros de um determinado arquivo, vai gerar 20 mil requisições para o c-Tree Server. Se a minha latência de rede for 0,001 segundo (um milissegundo), mesmo que o tempo de leitura dos dados no cache do c-Tree server seja 0 ( zero ), foram consumidos 20 segundos na camada de comunicação. Qualquer aumento da latência para 0,002 ou 0,003 já são suficientes para dobrar ou triplicar este tempo.

Quando uma aplicação lê um bloco de registros retornados por uma Query do SGDB, e vai criar uma tabela temporária no ERP (usando a CriaTrab() por exemplo), esta tabela é criada dentro da pasta “\system\” do diretório raiz do ambiente, onde cada inserção de registro faz um I/O pela rede ao c-Tree Server, que cria no disco uma tabela com transacionamento completo (chamado pela Faircom de acesso “TRNLOG”). Então, além do overhead da camada de rede, você têm um transacionamento completo com o dobro de escrita em disco.

Outro ponto é a escalabilidade de leitura. Mesmo com caches de leitura, a rede representa um overhead significativo. Se conseguimos jogar tudo na mesma máquina, tudo é lindo. Porém, se o seu ambiente cresce e você precisa suportar mais conexões e mais instância de processamento do Application Server, escalar esta máquina verticalmente não é viável, e mais cedo ou mais tarde você vai esbarrar em um limite físico. A sua super-máquina teria que ter várias placas de rede para evitar contenção, muitos cores, e uma placa mãe com barramentos “sinistros” pra aproveitar o que for possível de paralelismo…

E agora ? O que fazer ?

Primeiramente, pensando em escalabilidade horizontal, ao colocarmos mais máquinas físicas em um cluster, quanto mais servidores forem colocados, mais processos vão realizar requisições simultâneas de acesso de leitura dos meta-dados do ERP pela rede. Em um ambiente com 100, 120 conexões simultâneas, isto pode não apresentar contenção. Porém, quando passamos de 150 conexões fazendo I/O simultaneamente, a rede pode se tornar um gargalo.

Muita atenção neste ponto: Não é a banda de rede que torna-se o gargalo, mas sim a quantidade de IOs, o enfileiramento dos pacotes para transmissão e recepção atinge um limite físico. Mesmo que a sua banda de rede suporte 1 GB por segundo, e o consumo de banda chega a menos de 20% deste valor, a quantidade de pacotes é tão grande que a rede não dá conta dos eventos e começa a “enfileirar”.

Pensando neste cenário, a TOTVS homologou junto da Faircom um mecanismo de cache dos meta-dados (dicionários) do ERP sincronizado em memória. A idéia é simples, mas a implementação por baixo do capô exigiu um mecanismo de sincronismo complexo, mas de uma eficiência impressionante.

Dicionários em Memória usando c-Tree

Vamos partir de um ambiente padrão, onde a máquina que contém a unidade de disco onde estão os arquivos da pasta raiz do sistema (RootPath) possui o c-tree Server instalado, e uma segunda máquina possua um serviço do Application Server, configurado com ctreemode=server. Todos os IOs de leitura passam pela rede.

Agora, imagine que este Application Server possa ser iniciado usando a DLL do c-Tree Server (BoundServer), e você configure no INI deste Application Server quais serão os arquivos do seu ambiente que serão cacheados na memória, e este cache possui um mecanismo de notificação de alterações de dados, para manter todos os nós sincronizados caso algum processo de algum slave faça alguma alteração em um registro de uma tabela no c-Tree SErver que esteja em cache em um ou mais nós ? Isto seria lindo, pois todas as leituras seriam feitas diretamente da cópia da tabela feita em memória no Ctree BoundServer que o Application Server carregou … Escalabilidade horizontal “plena”, pode enfiar nó nesse cluster, que uma vez que o cache esteja carregado, tudo é lido da memória.

É exatamente isso que o mecanismo de cache e de notificação fazem. Cada Application Server configurado com o modo BoundServer, mais a configuração de cache em memória — definida no appserver.ini na seção [ctreeservermaster] — vai conectar com o c-Tree Server no momento da subida do serviço do Application Server, vai verificar quais as tabelas que devem ser trazidas para a memória, vai abrir uma a uma e copiar os dados para a memória local do c-Tree BoundServer, e vai registrar-se no c-Tree Server como um nó cliente de cache. A partir daí, todas as leituras das tabelas que entraram no cache são feitas da memória local, e qualquer alteração que qualquer um dos nós faça nesta tabela vai refletir instantaneamente na cópia da tabela na memória do Protheus Server que alterou u registro, e na tabela principal no disco. Quando o c-Tree Server gravar a alteração da tabela no disco, ele envia uma notificação para todos os demais serviços de Protheus configurados como bound-Servers, avisando que eles devem pegar uma cópia atualizada do registro alterado para manterem os seus caches sincronizados. Para isso, um Application Server configurado desta forma vai abrir uma conexão pela rede com o c-Tree Server, para receber as notificações de atualizações de cache, e cada tabela que está na memória deste BoundServer vai ser aberta por este Application Server pela API do boundServer, e pela conexão de rede com o c-Tree Server, onde esta conexão somente será utilizada se a aplicação fizer um insert ou lock e update em algum registro.

Mas, quanto isso consome de recurso ?

Bem, cada tabela configurada no mecanismo de cache em memória precisa ser carregada inteira para a memória, bem como os seus índices. A carga não é sob demanda, é feita na subida do Application Server. O espaço médio em memória do Application Server utilizado é aproximadamente duas vezes o tamanho do arquivo de dados mais os índices. Por exemplo, um arquivo SX3990.DTC com 169 MB e seu índice de 10 MB, vão ocupar 350 MB da memória deste Application Server. Por esta razão, a configuração de réplica realmente deve priorizar apenas os dicionários do ERP mais acessados, e no caso do ambiente ter múltiplas empresas (e consequentemente múltiplos arquivos de dicionário), devem ser priorizados apenas os arquivos das empresas mais acessadas.

Devido ao alto consumo de memória por instância de Application Server, e devido a cada serviço de Protheus criar um cache apenas para si mesmo, quando você possui máquinas maiores, que comportam por exemplo 8 instâncias de Application Server na mesma máquina, isso gerada dois inconvenientes: O primeiro é que o startup do ambiente iniciava uma cópia maciça de dados para cada um dos serviços da mesma máquina, onde o serviço poderia demorar muito para estar disponível para a execução dos programas AdvPL. E, como cada serviço criava uma cópia para si mesmo, você teria oito vezes o consumo de memória para um cache no mesmo equipamento.

Pensando nisso, foi criado um modo de configuração chamado “boundclient”. Você elege um dos outro serviços para ser retirado do balanceamento de carga, para ele não receber requisições de conexão de SmartClient ou fazer qualquer processamento Advpl. Este serviço você configura como um BoundServer com o cache de arquivos na memória, e habilita a conexão TCP no c-Tree BoundServer desta instância (depois te explico como). Para todos os outros demais serviços, eles serão configurados exatamente como este primeiro boundServer, apenas trocando a chave ctreemode=boundserver para ctreemode=boundclient

Com este cenário, o primeiro serviço que deve ser colocado no ar é o Application Server configurado para ser o BoundServer. Somente ele vai fazer a cópia dos arquivos para a memória, uma vez. E, todos os demais serviços de Protheus desta máquina vão conectar-se neste BoundServer para ler a cópia dos dados em memória. Isso economiza horrores no tempo de iniciar o ambiente, e economiza a memoria da máquina, pois nela haverá apenas uma instância de serviço dedicada a prover o cache e mantê-lo sincronizado.

E isso resolve tudo ?

Não. Como o c-Tree Server também vai ser usado como o driver para a criação de arquivos locais de trabalho, que por default são criados dentro da pasta ‘\system\’ do diretório raiz do ambiente (rootpath), todos os IOs de leitura e gravação ainda passam pela rede … Por isso, também na Build 7.00.121227, foi criado um novo driver, chamado “CTREETMP”, encapsulado pelas funções FWOpenTemp() e FwCloseTemp(), para permitir criar um arquivo temporário “puro”, aberto e acessado exclusivamente pelo processo que o criou em modo exclusivo, e que é criado na pasta temporária do servidor onde o Application Server está sendo executado, carregando para isso em memória uma instância do c-Tree BoundServer, e criando uma tabela sem nenhum controle de transacionamento. Com isso, todos os IOs feitos para esta tabela temporária serão tão ou mais rápidos que um c-Tree “local”.

O driver “CTREETMP” têm uma diferença em relação aos demais recursos de acesso do c-Tree. Mesmo que o seu application server esteja configurado com ctreemode=local ou ctreemode=server, quando um programa AdvPL usar o Driver temporário CTREETMP pela primeira vez, o protheus vai carregar uma instância do c-Tree BoundServer para gerenciar os arquivos temporários acessados por este driver. Todo o resto fica inalterado. Caso o seu modo de trabalho já seja ctreemode=boundserver, o protheus já aproveita a instância do BoundServer carregada para gerenciar os arquivos do driver temporário “CTREETMP”.

Aí sim, com os programas AdvPL criando as tabelas temporárias de trabalho usando o encapsulamento do Framework para o Driver temporário, e a leitura dos meta-dados do ERP em um cache de memória distribuído e sincronizado, a coisa muda de figura !

E, o que eu preciso ?

Não basta configurar o circo todo … Existe a necessidade de adquirir uma licença “Enterprise” do c-Tree Server para conseguir habilitar este cache. O c-Tree Server e o c-Tree BoundServer distribuídos pela TOTVS possuem uma licença de uso apenas para arquivos locais, não é possível usá-lo como base principal do ERP (RPODB=CTREE) ou com o sistema de cache de dicionários em memória. Uma vez adquirida e aplicada uma licença “Enterprise” no c-Tree Server, é possível usufruir deste mecanismo de cache.

Conclusão

Como o assunto é extenso e cheio de detalhes, este post foi só para “abrir” o apetite. A documentação oficial do recurso de cache de dicionários em memória foi recentemente disponibilizada na TDN, veja os links de referência do post no final do artigo !

Espero que estas informações sejam de alguma forma valiosas e aproveitadas para que possamos cada vez mais utilizar todo o potencial computacional disponível graças ao avanço da micro-informática.

Desejo a todos TERABYTES de sucesso 😀

Referências

C-treeACE. (2014, November 29). In Wikipedia, The Free Encyclopedia. Retrieved 16:14, October 25, 2015, from https://en.wikipedia.org/w/index.php?title=C-treeACE&oldid=635850139

Documentação da TDN

c-Tree Server com SXS em memória
Seção cTreeServerMaster
Seção ctreeServer
ctServerName
cTreeMode
c-tree BoundServer

9 respostas em “Acesso a Dados AdvPL com c-Tree – Parte 01

  1. Hey JL. Esse cTree é um monstro (e às vezes me da(va) medo) mas, com um discurso igual a esse, alguns mitos sobre o cTree foram elucidados.

    P.S.: Tem alguns errinhos de portugues no “post”. Dê uma revisada.

    []s

    Curtido por 1 pessoa

  2. Excelente trabalho, Julio!
    Acho que você pode criar uma coluna periódica no blog do tipo “Coisas que você sempre quis saber sobre a arquitetura do Protheus e não tinha para quem perguntar” 😉
    A propósito, quando você mencionou o uso do c-Tree para gerenciar os dicionários, lembrei-me que sempre tem algum analista que pergunta “mas não dava para colocar o dicionário no banco de dados de uma vez?”.
    Sabe se tem algum benefício em se manter os dicionários em DBF ou c-Tree em vez de se usar uma SGBD?
    Abraços!

    Curtido por 1 pessoa

    • Bem, tudo é possível, mas depende de uma reengenharia que envolveria várias áreas. O Framework AdvPL precisaria criar uma série de APIs de acesso aos dicionários para encapsular as consultas, os programas que abrem dicionarios “na unha” e os compatibilizadores de dicionários teriam que ser reescritos para usar estas APIs, e um novo tipo de cache precisaria ser estudado, afinal hoje a carga de IOs é dividida entre dados da base ( via DBAccess ) e SXS ( via c-Tree Server). Colocar todos no mesmo balaio é possível, mas vai exigir mais throwput do SGDB, isto pode mudar a métrica de dimensionamento de Hardware. 😀

      Curtido por 1 pessoa

  3. Oi Julio, qual o tamanho máximo do sigapss.spf, o de um cliente esta com 9MB e esta apresentando alguns erros do tipo:
    Dois usuários com um determinado acesso habilitado, sendo que um foi cópia do outro…
    Usuário 1 -> acessa a rotina perfeitamente
    Usuário 2 -> dá acesso não habilitado

    Solução:
    Copiei o usuário novamente e da segunda vez deu certo…

    Já tive 3 ocorrências desta…

    Curtido por 1 pessoa

    • Rapaz, o limite do arquivo de senhas, se eu não me engano são 4 GB … O problema mencionado não aparenta ter relação com o tamanho do arquivo de senhas 😀

      Abraços

      Curtir

  4. Oi Julio, estamos enfrentando um problema muito chato com o Locks em tabelas do SGBD, temos uma infra muito boa para o appserver, dbconnect e o banco, sabe me disse se o c-tree server nos ajudaria em algo?

    Curtido por 1 pessoa

    • Olá Leo,

      O Ctree e O DBAccess possuem mecanismos de Lock distintos. A avaliação de um cenário de locks deve inicialmente verificar qual é o tipo de lock que está sendo mantido por muito tempo : Locks ISAM controlados pelo DBAccess ( Reclock ) ou lock implicito dentro do SGDB em momento de atualização com transacionamento. De qqer forma, o depto. de suporte da Totvs poderá auxiliá-lo mediante chamado, ou os demais fóruns de AdvPL — inclusive alguns que eu menciono no Blog — podem ter alguma informação adicional ou já podem ter passado por situação similar 🙂

      Abraços e sucesso

      Curtir

Deixe um comentário