CRUD em AdvPL – Parte 05

Introdução

No post anterior, vimos algumas considerações sobre uso de MUTEX em AdvPL. Agora, vamos aplicar uma delas na Agenda, e ver algumas implementações interessantes, como índice único e a utilização dos índices para realizar buscas rápidas.

Novo ID para a Agenda

Devido ao porte do programa, e prevendo um cenário inicial onde apenas um servidor de aplicação Protheus seria o suficiente para vários usuários realizarem por exemplo operações de inclusão de dados concorrentemente, optei por usar um MUTEX com escopo apenas da instância atual do servidor de aplicação Protheus — GlbNmLock() e GlbNmUnlock(). Vamos ver abaixo como o fonte de criação de novo código ficou:

STATIC Function GetNewID()
Local cNewId
Local nRetry := 0
While !GlbNmLock("AGENDA_ID")
  nRetry++
  If nRetry > 20
    // 2 segundos sem conseguir lock ?
    // retorna uma string vazia
    return ""
  Endif
Sleep(100)
Enddo

// Se eu vou incluir, pega o Ultimo ID e soma 1
DBSelectArea("AGENDA")
DbsetOrder(1)
DBGobottom()

// Pega o valor do ultimo registro e soma 1
cNewId := StrZero( val(AGENDA->ID) + 1 , 6 )

// Solta o lock para outro processo poder pegar um novo ID
GlbNmUnlock("AGENDA_ID")

Return cNewId

Na prática, houve uma alteração no comportamento da função. Caso ela não consiga obter o bloqueio do identificador “AGENDA_ID”, a função espera 1/10 de segundo (100 ms) e tenta novamente, por até 20 vezes. Caso ela não consiga, ela apenas retorna uma string em branco. Esta mudança deve ser tratada em quem consome esta função, vide abaixo um trecho do novo código de confirmação da inserção de novo registro na Agenda:

cNewID := GetNewID()

// Release 1.2
// Se o ID está vazio, não foi possível obter o bloqueio nomeado
// para a geração do ID -- Muitas operações de inserção concorrentes

While empty(cNewID)
  If MsgInfo("Falha ao obter um novo ID para inclusão."+;
             "Deseja tentar novamente ?")
    cNewID := GetNewID()
    LOOP
  Endif 
  MsgStop("Não é possível incluir na agenda neste momento."+;
         "Tente novamente mais tarde.")
  Return
Enddo

Agora sim, com as alterações acima, podemos tratar concorrência entre inserções, dentro do mesmo servidor de aplicação, sem que um processo gere o mesmo ID de inclusão de outro processo. Agora, vamos deixar esse fonte mais ninja ainda ? Vamos criar no banco de dados um índice único, o que fará o próprio banco de dados recusar uma inserção de registro, caso seja usado um ID já existente na tabela.

Índice único no DBAccess

Usamos a função TCUnique() para criar um índice de chave única — ou índice único — no banco de dados para uma determinada tabela. Por default, o índice único é criado pelo DBAccess no Banco de Dados, usando o nome da tabela mais o sufixo “_UNQ”. Logo, para uma tabela chamada  “AGENDA”, o índice único correspondente será “AGENDA_UNQ”. Vamos ver como ficaria o fonte de abertura da tabela de agenda, logo após o teste da existência e criação do arquivo de dados.

If !tccanopen(cFile,cFile+'_UNQ')
  // Se o Indice único da tabela nao existe, cria 
  USE (cFile) ALIAS (cFile) EXCLUSIVE NEW VIA "TOPCONN"
  nRet := TCUnique(cFile,"ID")
  If nRet < 0 
    MsgStop(tcsqlerror(),"Falha ao criar índice único")
    QUIT
  Endif
  USE
EndIf

Com o índice único criado, experimente alterar a geração do ID, para não somar 1 , e usar o último ID existente. Você deve reproduzir um erro similar ao da mensagem abaixo:

AGENDA: DB error (Insert): -37 File: AGENDA – Error : 2601 (23000) (RC=-1) – [Microsoft][SQL Server Native Client 11.0][SQL Server]Cannot insert duplicate key row in object ‘dbo.AGENDA’ with unique index ‘AGENDA_UNQ’. The duplicate key value is (000006, 0).

No caso, cada Banco de Dados mostra uma mensagem similar com o mesmo significado — não foi possível realizar a inclusão pois os campos informados formam uma chave que já existe na tabela.

Observações sobre o índice único

Quando criamos um índice único para uma tabela no AdvPL, não é possível abrir este índice no AdvPL, usando por exemplo a função DbSetIndex(). O Objetivo do índice único é existir no Banco de Dados, para implementar uma camada de consistência das informações armazenadas diretamente no Banco de Dados. Porém, mesmo que não seja possível a sua abertura, é possível testar a sua existência usando a função TCCanOpen(), informando  o nome da tabela, e o nome do índice, como vemos no exemplo de código anterior.

Como fazer uma busca indexada – Função DBSeek()

Quando usamos um Banco de Dados relacional, normalmente criamos um índice para uma determinada tabela, e colocamos neste índice um ou mais campos — que vão fazer parte da chave do índice — com o objetivo do motor SQL ter um plano de ação otimizado, fazendo a seleção dos dados (SELECT) sobre o índice, baseado nas condições estabelecidas para trazer uma parte dos dados da tabela — realizada através de expressões condicionais na cláusula WHERE da QUERY.

Quando usamos a engine ISAM, e criamos um índice, a forma de fazermos uma busca optimizada é usar uma função de busca que utilize explicitamente o índice — função DBSeek() — que foi criado na aplicação, fornecendo no primeiro parâmetro um valor que seja composto da concatenação dos campos chave do índice — expressão de indexação — e o resultado será o posicionamento no primeiro registro que a chave de busca informada corresponde aos valores dos campos no registro.

No programa de exemplo, criamos 2 índices. O Índice chamado “AGENDA1”, cuja expressão de índice é apenas o campo “ID”, e o índice “AGENDA2”, com o campo “NOME”.  Estes índices são abertos logo após a abertura da tabela, e de acordo com a ordem de abertura, são selecionados como ordens de navegação ativa do ALIAS da tabela, usando a instrução DBSetorder(). No programa, alimentamos o campo ID na inclusão do registro, com um valor do tipo “CARACTERE”, contendo um valor numérico sequencial, de 6 posições, preenchido com zeros à esquerda: “000001”, “000002”,…

Pra realizar uma busca direta sobre o índice, pelo ID 000134, selecionamos o ALIAS da tabela — DbSelectArea() — depois nos certificamos que a ordem de navegação atual corresponde ao índice do campo chave “ID” — DBSetOrder() — e finalmente montamos uma chave de busca com o valor “000134” como “C” Caractere, e usamos a função DBSeek().

cChave := '000134'
DBSelectArea("AGENDA")
DbSetOrder(1)
IF DBSeek(cChave)
  MsgInfo('Chave ['+cChave+'] encontrada no registro '+cValToChar(Recno()))
Else
  MsgStop('Chave ['+cChave+'] NÃO ENCONTRADA')
Endif

Quando usamos esta instrução em uma tabela AdvPL aberta em modo de compatibilidade ISAM pelo DBAccess, devemos lembrar que no Banco de Dados a tabela é SQL, logo o DBAccess vai desmontar a sequencia de caracteres informada na função DBSeek() , e montar uma ou mais queries necessárias para emular o comportamento do ISAM e retornar se o registro realmente existe.

Baseado no trecho de código acima, podemos implementar algumas funcionalidades no código que perguntem ao operador um código ou um nome a ser pesquisado, e a função de pesquisa fará a busca pelo índice.

Diferença entre índices ISAM x SQL

Os índices criados para navegação e busca de dados usando um driver ISAM puro, como DBF / ADS, são criados em um ou mais arquivos físicos de indexação — para quem já programou em Clipper, quando usávamos a RDD DBFNTX, os arquivos de índices tinham a extensão “.NTX”, e cada arquivo suportava apenas um índice. Ao utilizarmos a RDD DBFCDX, os arquivos de índice tinham a extensão “.CDX”, e cada arquivo poderia suportar mais de uma chave de indexação. Uma particularidade interessante e ao mesmo tempo “perigosa” era a seguinte: Caso você criasse um índice para uma tabela, e ao fazer uma atualização na tabela — inclusão ou alteração — e esquecesse de abrir o arquivo de índices, o índice não era atualizado, o que causava comportamentos indesejáveis, como por exemplo uma busca pelo índice de uma informação que estava gravada na tabela não era encontrada.

Quando o DBAccess foi implementado para acesso a dados nos bancos relacionais — na época com o nome de TOPConnect — foi criado um mecanismo de emulação ISAM, de modo que cada índice que existia na aplicação original em DBF passou a existir também no Banco de Dados Relacional, justamente para que as operações de DBSeek() — busca sobre o índice — fosse tão performática quanto o ISAM, afinal o Banco de Dados já tinha um índice adequado para resolver as expressões de busca montadas em SQL quando usamos a função DBSeek(). A abertura de indices foi mantida igual, de modo que cada índice aberto no alias da tabela pode ser endereçado para mudar a ordem de navegação de acordo com a sequência de abertura. Para trocar a ordem para o primeiro índice aberto, usamos DBSetOrder(1), para o seguindo, DBSetOrder(2), e assim por diante. Caso seja usado um DBSetOrder(0), você passa a acessar a tabela pela ordem física de inclusão dos registros.

Por essas e outras razões de compatibilidade, as funções do Framework do ERP Microsiga abrem sempre a tabela e todos os índices existentes no dicionário ativo de dados (SXS) do ERP.

Conclusão

O assunto da busca indexada ISAM ainda têm mais pontos interessantes a serem vistos, vamos abordá-los no próximo post. Fique a vontade para baixar o código do programa Agenda.PRW — disponível do GitHub (https://github.com/siga0984/Blog) , e alterá-lo a seu critério. Confirme eu for implementando novas funcionalidades no programa, a versão do GIT será atualizada.

Novamente desejo a todos TERABYTES DE SUCESSO !!!

Referências

 

 

 

Anúncios

Escalabilidade e Performance – Stored Procedures

Introdução

Em um tópico anterior sobre “Escalabilidade e performance – Técnicas”, um dos tópicos falava sobre Stored Procedures, inclusive sugerindo que seu uso deveria ser minimizado. Vamos entrar neste tema com um pouco mais de profundidade neste tópico. Vamos começar com o clone do tópico abordado, e esmiuçar ele dentro do contexto do AdvPL e Protheus.

Minimize o uso de Stored Procedures

Este é um ponto aberto a discussão, depende muito de cada caso. Não é uma regra geral, existem pontos em um sistema onde uma stored procedure realmente faz diferença, mas seu uso excessivo ou como regra geral para tudo impõe outros custos e consequências. O Princípio 1 diria: “use apenas stored procedures”. No entanto, esta decisão pode causar grandes problemas para o Princípio 2 devido à escalabilidade. As Stored procedures têm a vantagem de ser pré-compiladas, e não há nada mais perto de um dado no Banco de Dados.

Porém Bancos de Dados transacionais são especializados em quatro funções: Sort, Merge, gerência de Locks e Log. A gerência de lock é uma das tarefas mais críticas para a implementação de algoritmos distribuídos, e este é o real motivo de existirem poucos Bancos de Dados que possam implementar a escalabilidade horizontal. Se as máquinas de Banco de Dados têm dificuldade de escalar horizontalmente, ela é um recurso escasso e precioso. Temos então que otimizar seu uso para não consumir excessivamente seus recursos a ponto de onerar os demais processos do ambiente. Isto acaba adiando a necessidade de escalar horizontalmente o SGBD.

Abordando a questão de desempenho

Se o algoritmo para processamento de um grande grupo de informações pode ser escrito dentro de uma Stored Procedure no próprio Banco de Dados, esta alternativa tende fortemente a ser a mais performática. Num cenário onde o algoritmo é escrito usando um programa sendo executado dentro do servidor de aplicação da linguagem, cada processamento que dependa da leitura de grupos de dados e tenha como resultado a geração de novos dados vai ser onerado pelo tempo de rede de tráfego destes dados, na ida e na volta. Logo, com uma base de dados modelada adequadamente, e uma stored procedure bem construída, ela naturalmente será mais rápida do que um processamento que precisa trafegar os dados pra fora do SGDB e depois receba novos dados de fora.

Porém, este recurso não deve ser usado como solução mágica para tudo. Afinal, o SGDB vai processar uma Stored Procedure mais rápido, pois ele não vai esperar um processamento ser realizado “fora dele”, porém o SGDB vai arcar com o custo de ler, processar e gravar a nova informação gerada. Se isto for feito sem critério, você pode mais facilmente esgotar os recursos computacionais do Banco de Dados, ao ponto da execução concorrente de Stored Procedures afetar o desempenho das demais requisições da aplicação.

Outras técnicas pra não esgotar o SGDB

Existem alternativas de adiar um upgrade no SGDB, inclusive em alguns casos as alternativas são a solução para você não precisar comprar um computador da “Nasa” …risos… Normalmente estas alternativas envolvem algum tipo de alteração na aplicação que consome o SGDB.

Réplicas de leitura

Alguns SGDBs permitem criar nativamente réplicas da base de dados acessadas apenas para consulta, onde as cópias de leitura são sincronizadas em requisições assíncronas. Existem muitas partes da aplicação que podem fazer uma leitura “suja”. Neste caso, a aplicação pode ler os dados de uma base sincronizada para leitura, e os processos que precisam de leitura limpa são executados apenas na instância principal. Para isso a aplicação precisaria saber qual e o banco “quente” e qual é o espelho, para fazer as coisas nos lugares certos.

Caches

Outra alternativa é a utilização de caches especialistas, implementados na própria aplicação. Utilizando por exemplo uma instância de um “MemCacheDB” em cada servidor, cada aplicação que pode reaproveitar a leitura de um dado com baixo índice de volatilidade (dados pouco atualizados ou atualizados em momentos específicos), poderiam primeiro consultar o cache, e somente se o cache não têm a informação desejada, a aplicação acessa o banco e popula o cache, definindo uma data de validade. Neste caso, o mais legal a fazer é definir um tempo de validade do cache (Expiration Time). E, paralelo a isso, para informações de baixa volatilidade, a rotina que fizer update desta informação pode eliminar ela do cache no momento que um update for realizado, ou melhor ainda, pode ver se ela se encontra no cache, e em caso afirmativo, ela mesma poderia atualizar o cache 😉

Sequenciamento de operações

Operações de inserção ou atualização de dados que não precisam ser refletidas em real-time no SGDB podem ser enfileiradas em pilhas de requisições, e processadas por um processo dedicado. O enfileiramento de requisições não essenciais em tempo real limita o consumo de recursos para uma determinada atividade. Caso a pilha se torne muito grande, ou um determinado processo dependa do esvaziamento total da pilha, podem ser colocados mais processos para ajudar a desempilhar, consumindo mais recursos apenas quando estritamente necessário.

Escalabilidade Vertical

Devido a esta questão de dificuldade de escalabilidade de bancos relacionais horizontalmente, normalmente recorremos a escalabilidade vertical. Escalamos horizontalmente as máquinas de processamento, colocando mais máquinas menores no cluster e balanceando carga e conexões, e quando a máquina de banco começa a “sentar”, coloca-se uma máquina maior só para o SGDB, com vários processadores, discos, memória e placas de rede. Mas tudo tem um limite, e quando ele for atingido, a sua máquina de Banco de Dados pode ficar mais cara que o seu parque de servidores de processamento.

Dificuldade de Implementação

Usar caches e réplicas e pilhas não é uma tarefa simples, fatores como a própria modelagem da base de dados podem interferir negativamente em algumas destas abordagens. Não se pode colocar tudo em cache, senão não vai ter memória que aguente. O cache é aconselhável para blocos de informações repetidas constantemente requisitadas, e de baixa volatilidade. Também não é necessário criar pilhas para tudo que é requisição, apenas aquelas que não são essenciais em tempo real, e que podem ter um delay em sua efetivação.

Stored Procedures no AdvPL

O ERP Microsiga disponibiliza um pacote de Stored Proecures, aplicadas no SGDB em uso por um programa do módulo “Configurador” (SIGACFG). As procedures foram desenvolvidas para funcionalidades específicas dentro de cada módulo, normalmente aquelas que lidam com grandes volumes de dados, e foi possível criar um algoritmo que realize o processamento dentro do SGDB, trafegando menos dados “pra fora” do Banco de Dados. Normalmente um pacote de procedures é “casado” com a versão dos fontes do Repositório, pois uma alteração na aplicação pode envolver uma alteração na procedure. Os ganhos de performance são nítidos em determinados processamentos, justamente por eliminar uma boa parte do tráfego de informações para fora do SGDB durante os processos.

Conclusão

Dado o SGDB como um recurso “caro e precioso”, como mencionado anteriormente, a utilização de recursos adicionais como réplicas e caches, ajuda a dar mais “fôlego” pro SGDB, você consegue aumentar o seu parque de máquinas e volume de dados processados sem ter que investir proporcionalmente na escalabilidade do SGDB. E em tempos de “cloudificação” , SaaS e IaaS, quando mais conseguimos aproveitar o poder computacional que temos em mãos, melhor !

Desejo novamente a todos TERABYTES de Sucesso 😀

Até o próximo post, pessoal 😉

Referências

“Escalabilidade e performance – Técnicas”

Acesso a Dados – IndRegua()

Introdução

Desde os primórdios do AdvPL, quando o ERP Microsiga ainda era um executável stand-alone, que usava arquivos DBF para armazenar os meta-dados (dicionários do ERP) e tabelas de dados da aplicação, foi criada uma função no FrameWork AdvPL, para encapsular e tratar a criação de um índice temporário para uma tabela DBF qualquer.

Embora esta função seja do FrameWork do AdvPL, ainda restam hoje algumas dúvidas sobre qual é a melhor forma de utilizá-la, inclusive quais são as diferenças de comportamento quando usamos a função em um alias de uma tabela usando ADS ou c-Tree, e o que ela faz de “diferente” quando é utilizada em uma tabela acessada pelo DBAccess em um SGBD relacional.

Comportamento original da IndRegua

A função foi desenhada para, a partir de um ALIAS ou WorkArea de uma tabela DBF aberta, permitir criar um índice temporário para a tabela, onde você deve informar uma condição de ordenação, usando a mesma sintaxe de composição de chaves de índice do AdvPL, pode ser especificada uma condição de filtro, para que o índice seja criado considerando apenas os registros do alias atual que atendam a condição de filtro especificada.

Com um alias de uma tabela DBF ou c-Tree, a função cria fisicamente no disco um índice de uso temporário (extensão .idx) atendendo as condições parametrizadas. Mesmo quando especificada uma condição de filtro, toda a tabela é percorrida internamente para a criação deste índice. Logo, o tempo de criação do índice filtrado é proporcional ao tamanho da tabela. Porém, uma vez criado e selecionado para uso, este índice permitia uma navegação praticamente instantânea sobre os dados filtrados e ordenados. Após o uso, este índice deve ser fechado, e removido do disco.

Este processo somente é indicado quando você precisa fazer a leitura completa dos dados filtrados mais de uma vez, como por exemplo para a exibição em tela de um Browse para marcar itens de processamento (MarkBrowse). Caso contrário, você poderia simplesmente aplicar um filtro na tabela, e percorrer os registros válidos apenas uma vez. Embora pudesse ser criada uma tabela temporária baseada em uma condição de filtro, e depois fazer o browse diretamente nesta tabela, caso o filtro fosse pouco restritivo, demoraria mais para criar a tabela temporária do que para fazer um índice filtrado, além de consumir muito espaço em disco.

Usando com uma tabela do DBAccess

A utilização da IndRegua no DBAccess, em uma tabela acessada pela RDD TOPCONN, não haverá a criação física explícita pela aplicação de um índice temporário. Na verdade, as condições de filtro e ordenação serão traduzidas pelo motor de navegação do DBAccess, e repassados ao SGBD durante a leitura dos dados. Logo, elimina-se logo de cara o tempo de espera pela criação explícita de um índice físico.

Porém, como estamos na prática submetendo por baixo queries no SGBD, o motor de execução SQL do Banco de Dados em questão vai “se virar” para selecionar os dados nas condições solicitadas, levando em conta as condições de filtro e a ordenação informada. Esta é uma prerrogativa do SDBD, onde ele avalia se é possível aproveitar um índice já existente no SGBD para selecionar os registros que atendem a condição de filtro informada. Caso não seja possível fazer isso, o SGBD vai dar um jeito de resolver a query, mas seu desempenho pode ser muito prejudicado de acordo com o tamanho da tabela. Naturalmente esta operação será muito bem executada pelo SGBD, caso exista algum índice no banco de dados que contemple todos os campos utilizados nas condições de filtro ou seleção de registros desejados.

Este comportamento do banco justifica por que determinadas condições de filtro, quando acrescentadas pelo usuário em um Browse do ERP padrão ficam muito lentas. Existem contornos operacionais para estes tipos de casos, onde normalmente um DBA utiliza um mecanismo de monitoramento de desempenho do próprio SGBD, para elencar as queries menos performáticas, onde normalmente a inclusão de um índice específico no SGDB para favorecer o processamento da query resolve a questão de desempenho.

Filtro ou Query

Nada impede que você faça uma Query para recuperar os dados que você deseja do banco, já colocando nela apenas as colunas desejadas, JOINS quando necessários, colocando as condições e ordenação desejadas. Vale a mesma regra do filtro: Caso exista um índice no SGBD que contemple os campos usados nas condições de seleção de registros (Where da query), mais fácil será para o SGBD processar e retornar os dados.

Uma Query vai ser naturalmente mais rápida que o filtro, pois ela será submetida apenas uma vez ao SGBD, enquanto um filtro vai inferir condições no motor de navegação de registros do DBAccess, que sob demanda vai submeter várias queries para identificar os blocos de dados a serem recuperados. Porém, esta diferença não é tão gritante. A engine de ISAM emulada é bem eficiente, e em muitos casos um filtro atende melhor às condições do Programa. Não é viável colocar um alias de uma Query, que somente permite movimentação para os próxios registros dentro de um Browse de visualização de interface. Dependendo do caso, alguns programas lêem o resultado da query e criam um arquivo temporário no disco, e isto têm o custo da leitura dos dados pela rede, e posterior envio de cada registro para gravação em disco. Mas isto é assunto para outro capítulo … eheheh ….

Boas práticas

Nas condições de filtro, você consegue “ajudar” o motor de queries do SGBD, quando você previamente identifica um índice no banco que contenha todos ou a maioria dos campos que você vai selecionar os dados, e especifica todas as condições de filtro dos dados na ordem de campos do índice. Por exemplo, se você vai recuperar apenas os dados sob condições que usam os campos CPO1, CPO2 e CPO3, e você têm um índice no seu SGBD cuja chave seja “CPO1,CPO3,CPO2”, você ajuda o banco na hora de escrever a sua query informando as condições nesta ordem: “CPO1 >= ’01’ AND CPO3 = ‘001’ AND CPO2 between ’01’ and ’05′”

Outra coisa importante, tanto em filtros como em Queries, é evitar ao máximo usar condições que concatenam valores de campos. Por exemplo, você tem uma variável na memória do Advpl, que contem a concatenação de dois campos, CPO2 e CPO3. Logo, ao escrever a query, você faz algo assim:

cQuery := "SELECT CPO1,CPO2,CPO3,CPO4,CPO5 FROM TABELA WHERE "
cQuery += "CPO1 = '01'"
cQuery += " AND (CPO2 || CPO3) = '"+cChave+"'"

Isto obriga o SGBD a criar internamente uma coluna calculada para então validar o critério de comparação. Para o SGBD, é MUITO mais leve você quebrar as condições:

cQuery := "SELECT CPO1,CPO2,CPO3,CPO4,CPO5 FROM TABELA WHERE "
cQuery += "CPO1 = '01'"
cQuery += " AND CPO2 = '"+left(cChave,2)+"'"
 cQuery += " AND CPO3 = '"+substr(cChave,3)+"'"

Na prática, uma IndRegua() em uma tabela do DBAccess possui basicamente um comportamento de filtro, permitindo especificar uma ordenação diferenciada. Logo, todas as regras de performance aplicáveis a um filtro de IndRegua() também se aplicam a um filtro definido pelo comando SET FILTER TO — ou pela função DbSetFilter().

Lembrando de um ponto importante do filtro: Por questões de compatibilidade, um filtro AdvPL definido via SET FILTER ou DbSetFilter() pode conter expressões AdvPL, incluindo operadores e funções não suportadas pelo SGDB. Porém não existe mágica, o Application Server manda o filtro pro DBAccess, e ele remonta o filtro considerando apenas as condições que o SGDB é capaz de resolver, e cada registro retornado pelo DBAccess ao Appllication Server é revalidado, e caso não seja válido pela expressão de filtro re-ececutada no AdvPL, o registro é ignorado e o próximo registro é solicitado, até que seja retornado um registro válido.

O pior cenário de desempenho e consumo de recursos é um filtro muito restritivo em uma tabela muito grande, pois muitos registros podem ser trafegados desnecessariamente ao Application Server até que os registros que atendem realmente a condição de filtro sejam percorridos pelo programa. Logo, é extremamente recomendável que o seu filtro tenha o menor número possivel de instruções que não possam ser processadas no SGDB. Quanto mais simples, melhor. Na documentação de referências no final deste artigo, a documentação da função DbSetFilter() aborda em detalhes vários destes aspectos.

Conclusão

Este artigo terá continuações, para abordar outros aspectos de acesso a dados, mas já é um bom começo. Ainda existem muitos espaços em branco para serem preenchidos com mais informações, espero ter ajudado de alguma forma. Acompanhem os próximos posts, têm muita coisa boa pra ser publicada 😀

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

Referências

http://tdn.totvs.com/pages/releaseview.action?pageId=6814909
http://tdn.totvs.com/display/tec/SET+FILTER+TO
http://tdn.totvs.com/display/tec/DBSetFilter

Persistência de dados – ISAM

Introdução

Uma parte fundamental de qualquer sistema informatizado é a persistência dos dados. Hoje temos bancos de dados ISAM, relacionais ( SQL ), Bancos No-SQL — para dados não-tabulados, XML, entre outros. Cada um deles é fundamentado em um paradigma para o tipo, quantidade, formato e necessidade de armazenamento e recuperação de dados. Como a maioria dos recursos da tecnologia da informação, cada um destes bancos possui um diferencial ligado a casos de uso. Muitas aplicações comerciais podem utilizar mais de um tipo de banco de dados, de acordo com a necessidade de cada ponto da aplicação.

Neste tópico será abordado um pouco do conceito, características e utilização de mecanismos que usam a implementação ISAM para persistência e consulta de dados.

Conceito (um pouco de teoria é inevitável)

ISAM é um acrónimo de Indexed Sequential Access Method, ou método de acesso sequencial indexado. Trata-se de um método de indexação de dados para acesso rápido. Este mecanismo foi desenvolvido pela IBM para mainframes, antes da década de 70. O termo ISAM aplica-se atualmente a mecanismos e APIs de acesso a dados onde o desenvolvedor explicitamente faz a busca em um índice, para então recuperar a informação que o índice aponta (em contraste com um banco relacional, que possui um optimizador de queries que escolhe internamente o melhor índice a ser usado), ou ainda a um mecanismo de indexação que permita simultaneamente buscas sequenciais e por chave. Normalmente a API ISAM é composta por um grupo de funções, usadas para realizar operações individuais com tabelas, índices e registros.

Características e utilização

Os dados são organizados em registo de tamanho fixo, e salvos sequencialmente em um arquivo, onde a posição ordinal do dado no arquivo representa o seu número de registro. Um ou mais índices podem ser criados para este arquivo, com um ou mais campos nas chaves, e as operações básicas de recuperação de dados consiste basicamente em buscar uma informação diretamente em um índice a partir de uma chave de busca montada com os campos usados no índice, onde após o registro ser encontrado no índice, a informação na tabela de dados é posicionada (ponteiro de registro), e a partir deste instante você pode navegar para os próximos registros de dados ou aos registros anteriores, usando a ordem do índice atualmente aberto, ou usando a ordem natural de inserção (física) da tabela. Algumas APIs permitem campos de conteúdo variável, que normalmente são gravados em um arquivo auxiliar, onde um registro do arquivo ISAM de dados aponta para um offset do arquivo auxiliar para recuperar o(s) campo(s) de tamanho variável.

Na página dedicada ao ISAM pela Wikipedia (em inglês, vide referências no final do post), têm uma lista de aplicações que implementam a arquitetura ISAM. Entre elas, o Clipper, FoxPro, Microsoft Access, Faircom c-TreeACE, Dataflex, Paradox, entre outras. Existe uma relação mais próxima entre o dados fisicamente gravados e o desenho do algoritmo. Normalmente um sistema que utiliza uma engine ISAM para dados obtém grande desempenho desenhando bem a distribuição dos dados dentro de tabelas e os índices necessários para acessar rapidamente as informações.

Destas ferramentas, algumas também oferecem uma camada superior de abstração para implementar acesso relacional, com queries e tudo. A raiz dos dados no disco continua sendo ISAM, mas existem as tabelas de catálogo do banco (systables, sysindexes, syscolumns, etc.) e os algoritmos para realizar o parser de queries, recuperação de dados, transacionamento, etc.

Na prática, é muito mais fácil e prático desenvolver usando um banco relacional. Você cria as tabelas, índices para acelerar as buscas mais utilizadas ou que precisam efetivamente de performance, e utiliza recursos do SGDB para monitorar, configurar e administrar o Banco de Dados, como as estatísticas de índices, segmentação de dados, recursos avançados de pesquisa de textos, além de existir uma ampla gama de aplicações de mercado construídas para conectar-se com estes bancos e permitir de forma assistida a recuperação de dados para análises, estatísticas e relatórios.

Como eu mencionei anteriormente, cada um dos tipos de banco de dados possui um diferencial interessante, muito eficaz quando aplicado no seu cenário ideal. Por exemplo, em uma etapa intermediária de um processamento, ou para armazenamento de dados de baixa volatilidade ( pouca manutenção de estrutura e conteúdo), uma API ISAM pode oferecer um diferencial de performance, devido justamente ao fato de não termos presentes alguns overheads que são implícitos em um Banco de Dados relacional. Porém, a decisão de como distribuir os dados e como criar e usar os índices paa acessar as informações da forma mais eficiente possível ficam na mão do desenvolvedor. E se isto não for feito como manda o figurino, o ganho que poderia ser obtido em determinada parte do processo usando o ISAM é engolido pela forma de implementação do algoritmo.

Uso no ERP Microsiga

O ERP Microsiga utiliza um gateway de conexão com bancos de dados relacionais (DBAccess), homologado para uma lista de Bancos de Dados relacionais, como Microsoft SQL, Oracle, DB2, Informix, e inclusive versões Open Source, como o PostgreSQL e o MySQL — com algumas restrições e usos em produtos específicos. E, ao mesmo tempo, utiliza também o c-TreeACE, da Faircom, para acesso a tabelas de meta-dados do ERP (também conhecidos como “SXS”), bem como armazenamento de dados em módulos do ERP concebidos para serem executados em modo Stand-Alone, e para criar tabelas temporárias compartilhadas entre servidores (a partir do RootPath do ambiente, compartilhado pela rede com as demais máquinas servidoras em ambiente balanceado) e exclusivas (tabelas temporárias no HD local da máquina onde o serviço do TOTVS | Application Server está sendo executado).

Conclusão

Na programação contemporânea, quem não conhece mais profundamente a camada ISAM normalmente referem-se a ela como algo ultrapassado. Realmente, usar uma base principal ISAM em um sistema com larga previsão de crescimento de base de dados, dada a amarração intrínseca com o meio físico, fatalmente você iria esbarrar com questões de performance e dependência direta de devices/hardware. Inclusive, por causa disso, a IBM criou um superset do ISAM, chamado VSAM (Virtual Storage Access Method) na década de 70. Porém, a engine ISAM é muito eficiente para determinados casos de uso, muito comuns em etapas de processos intermediárias, como a criação de arquivos de uso temporário.

A criação de arquivos temporários é um tema que merece atenção especial, e será abordado em profundidade em um outro post, diretamente relacionado a performance e escalabilidade.

Até o próximo post, pessoal 😉

Referências

Wikipedia contributors. ISAM. Wikipedia, The Free Encyclopedia. October 20, 2014, 21:33 UTC. Available at: http://en.wikipedia.org/w/index.php?title=ISAM&oldid=630429759. Accessed December 19, 2014.