Acesso a dados – ISAM versus Relacional

Introdução

Em um post anterior ( https://siga0984.wordpress.com/2014/12/22/persistencia-de-dados-isam/) foi feita uma introdução à abordagem ISAM para persistência de dados. Acho interessante a leitura desse artigo antes de continuar a ler este post. Atendendo a pedidos, vamos analisar com uma lupa as características de uma engine ISAM versus uma engine relacional disponibilizada através de um SGBD (Sistema Gerenciador de Banco de Dados).

Exemplo de acesso a dados

Vamos usar o exemplo clássico da dupla “cabeçalho de pedido” e “itens de pedido”. Criamos duas tabelas distintas, uma para os dados do pedido (cabeçalho) e outra para os itens dos pedidos. Fazemos isso praticamente da mesma forma em um banco ISAM como em um banco relacional.

Normalmente criamos um ou mais índices na tabela que armazena os ítens de pedido usando o código do pedido — que é gravado em cada item do pedido — e um campo adicional, como o código do produto ou um campo de sequência ou item, para ordenar os itens que pertencem ao mesmo pedido, de modo que eles fiquem agrupados pelo índice e possam ser rapidamente lidos em sequência. E, naturalmente criamos um ou mais índices também na tabela que armazena o cabeçalho do pedido, para que os pedidos possam ser rapidamente localizados. Isto também é feito praticamente da mesma forma em um banco ISAM bem como em um SGBD.

Agora, o que vai fazer a diferença é a forma como os dados destas tabelas são recuperados pela abordagem ISAM e pela abordagem relacional através de uma ou mais queries.

Cenário proposto

Uma rotina simples de recuperação dos dados de um pedido, onde é fornecido um código do pedido, e a aplicação deve recuperar todos os dados relacionados aquele pedido, considerando que devem também ser trazidos para o pedido algumas informações do cadastro do cliente que fez o pedido, e ao trazer os itens de pedido, devem ser trazidas informações mais detalhadas do cadastro do produto envolvido na operação. No cenário proposto, vamos lidar com quatro tabelas: Clientes, Pedidos, Itens de Pedidos e Produtos, e a aplicação para recuperar estes dados acessa os arquivos de dados através de uma conexão de rede.

Pseudocódigo com abordagem ISAM

Selecione a tabela de Pedidos
Selecione a ordem de codigo de pedido
Busque pelo codigo do pedido informado 
recupere os dados do pedido 
selecione a tabela de clientes 
selecione a ordem de codigo de cliente
busque pelo codigo do cliente
recupere os dados do cliente 
selecione a tabela de itensdepedido 
selecione a ordem de codigo do pedido 
busque pelo codigo do pedido 
enquanto o codigo do pedido deste item for do mesmo pedido e nao for final de arquivo faça:
 recupere os dados do item do pedido
 selecione a tabela de produtos
 busque pelo codigo do produto
 recupere as informaçoes desejadas do produto
 selecione a tabela de itens de pedido
 posicione no proximo item de pedido
mostre os dados na interface
fim da rotina

Na engine ISAM, cada tabela é endereçada por um apelido (Alias) dado no momento de sua abertura, então para cada informação a ser buscada, precisamos selecionar a tabela para a operação, definir a ordem de busca, fazer a busca indexada e recuperar a informação. Para o caso dos itens de pedido, cada item recuperado precisa trazer algumas informações do cadastro de produto, fazendo exatamente este processo, e a cada item recuperado, devemos ir para o próximo item naquela ordem ( DbSkip() ), e verificar se o novo item posicionado é do mesmo pedido, para repetir o processo para todos os itens.

Levando-se em conta que cada operação ISAM faz pelo menos um I/O pela rede para acessar os dados, em um pedido com 20 itens, temos 1 IO para buscar o pedido, 1 para o cliente, e 2 para cada item de pedido ( posicionar no item, buscar o produto, posicionar no proximo item, buscar o produto), totalizando em uma abordagem bem enxuta 2 + ( 20 * 2 ) = 42 IOs…

Pseudocódigo com abordagem relacional ( Query )

"selecione os dados do pedido e dados do cliente das tabelas de pedidos e de clientes onde o codigo do pedido é igual ao código a pesquisar e o código do cliente no cadastro de clientes é o mesmo código de cliente da tabela de pedidos"
"selecione itens dos pedidos e dados do produto das tabelas itensdepedido e produtos onde o código do pedido é igual ao código informado e o código do produto no cadastro de produtos é o mesmo código de produto da tabela de itens de pedido, ordenado por codigodopedido e itemdopedido"

Na engine relacional, um IO recupera os dados do pedido e do cliente, e 20 IOs recuperam os dados de cada item de pedido com os detalhes do produto informado naquele item. É praticamente a metade dos IOs que uma engine ISAM. Na prática, escrito sem toda a riqueza de detalhes, considerando que %CODPED% é o código do pedido informado para buscar, o pseudocódigo mais próximo do Advpl seria:

SELECT PED.* , CLI.* FROM CLIENTES CLI, PEDIDOS PED WHERE PED.CODPED = %CODPED% AND CLI.CODCLI = PED.CODCLI
SELECT ITEM.* , PROD.* FROM ITENSDEPEDIDO ITEM, PRODUTOS PROD WHERE ITEM.CODPED = %CODPED% AND 
ITEM.CODPRO = PROD.CODPRO ORDER BY ITEM.CODPED, ITEM.SEQ

Considerações do ISAM

  • Dada a natureza da operação ISAM, cada vez que pulamos para o próximo registro, todas as colunas do registro são lidas para a memória, sejam elas usadas pelo programa ou não.
  • Uma alteração estrutural na tabela fatalmente envolve fazer uma cópia da tabela atual, criar uma nova estrutura com um novo alinhamento para os campos, e copiar todos os dados de volta da cópia para a nova estrutura.
  • Qualquer acesso otimizado aos dados deve envolver um índice que ordene os dados de modo a agrupá-los em sequência.
  • Você pode reaproveitar um índice para varias situações comuns de acesso em um programa, porém uma vez que um índice permanente seja criado e utilizado no sistema, alterar ou excluir esse índice pode causar transtornos e bastante retrabalho para refatorar os códigos dele se utilizam.
  • Caso seja necessária uma extração de dados em uma ordem não indexada, necessária eventualmente, precisamos explicitamente criar um índice temporário, e esta operação normalmente envolve a leitura e avaliação de todos os registros da tabela.
  • Manter muitos índices permanentes criados para a tabela pode prejudicar os tempos de inserção e atualização de registro, pois todos os campos da tabela que fazem parte de um índice precisam ser atualizados quando um novo registro é inserido, ou quando alteramos um campo que faça parte da chave de um ou mais índices.
  • A questão de filtros de acesso a dados é mais delicada, pois quando a aplicação acessa direto a tabela, o filtro é aplicado na camada de leitura, onde ao avançar para o próximo registro, podem ser feitas muitas leituras de registro que serão desconsideradas até que um novo registro dentro da condição de filtro seja retornado (filtro muito restritivo em tabelas muito grandes). Quando usamos a abordagem ISAM através de uma engine client-server, como o c-Tree Server por exemplo, o filtro pode ser processado em server-side, sem trafegar desnecessariamente os registros que não pertencem ao filtro.
  • Quando temos mais de uma tabela, com dados relacionados entre elas, o relacionamento é explicitamente escrito do programa, a busca de cada informação em uma tabela relacionada implica em abrir e selecionar a área de trabalho da tabela relacionada, verificar se o índice em uso é adequado para a busca, e posicionar o registro desta tabela fazendo um DBSeek (busca pela chave indexada) usando os dados dos campos da chave de índice da tabela principal e verificar se o dado relacionado foi encontrado, para então trazê-lo e restaurar o alias de trabalho novamente. Quanto mais amarrados forem os dados a serem manipulados, maior é o custo de criar e dar manutenção no fonte, para que ele seja performático. Quando consideramos um ambiente onde o servidor de aplicação está separado da engine server ISAM por uma camada de rede, cada operação atômica sofre uma penalização do overhead de rede.

Acesso a banco relacional por Queries

Quando utilizamos um SGBD relacional para acesso aos dados, temos algumas vantagens em relação ao ISAM:

  • – Suporte a alteração estrutural, para adicionar, alterar ou remover colunas. O SGBD relacional normalmente tem mecanismos de armazenamento segmentados, que permitem fazer pequenas alterações sem precisar reescrever a tabela inteira.
    – Em uma query, podemos pedir ao banco de dados apenas as colunas que nos interessam.
    – Quando precisamos trazer na mesma query dados de tabelas relacionadas, podemos usar os recursos de OUTER JOIN para localizar os dados nas tabelas relacionadas, e trazer delas as colunas desejadas, onde trazemos colunas de diferentes tabelas em um mesmo evento de rede. ( menos IO para buscar os mesmos dados em relação ao ISAM).
    – O SGBD possui um mecanismo de análise e geração de plano de execução, que leva em conta a verificação dos índices já existentes para optimizar a busca, e a geração de sub-sets de dados internamente para optimizar as operações.
  • Uma query é escrita praticamente de forma literal, respeitando algumas definições sintáticas. Praticamente, a estrutura de uma query simples é “select coluna(s) from tabela(s) where condição(ões) order by campo(s)”, algo intuitivamente compreensível.
  • Existem palavras-chave que podem ser usadas nas queries para realizar somas e agrupamentos de dados (SUM, GROUP BY).

Isto traz ao SGBD a parte complexa de separar os dados exatamente da forma que foram solicitados. Para auxiliar neste processo, existem boas práticas que devem ser adotadas para a codificação de queries, e técnicas como a normalização de dados, para evitar redundâncias desnecessárias e agrupar os dados de forma eficiente, além de configurações do banco de dados específicas para melhor atender o tipo e volume de requisições realizados (tuning), inclusive algumas destas ferramentas podem sugerir a criação de índices para melhorar o desempenho da execução de queries.

Comparando desempenho

Avaliando ambas as abordagens, naturalmente o acesso por queries tende a ser mais rápido, principalmente nos cenários onde buscamos informações relacionadas e aplicamos filtros. Tudo é feito no SGBD, e apenas os dados selecionados são trafegados. Porém, o desempenho de uma query mais complexa requer alguns cuidados no momento de escrevê-la, para que o SGBD seja capaz de elaborar um plano de execução para a recuperação das informações da melhor forma possível, onde a principal consideração é haver a existência de um índice que contemple todos os campos utilizados para filtro de informações.

Outros cenários

Não necessariamente uma abordagem com Queries será sempre mais rápida que uma abordagem usando ISAM, existem casos onde não compensa trocar tudo por Query. O exemplo clássico é a busca de dados exatos na base usando DbSeek(), informando uma chave completa do índice. A busca por um dado usando DbSeek() poir default procura a existência no SGDB do primeiro registro na ordem em uso cujo conteúdo dos campos atenda a chave informada na busca. Caso a chave não seja encontrada, a função retorna .F. (falso) e o alias é posicionado no final de arquivo (EOF).

Embora alguns de vocês já possam tenham ouvido falar que “Seek” é mais lento do que “Query”, o cenário proposto para isso é: Uma parte de um código AdvPL que busque informações relacionadas em uma ou mais tabelas fazendo o posicionamento das tabelas relacionadas com DbSeek() é mais lento do que criar uma Query com JOIN que traga também os campos das tabelas relacionadas.

Existe um cenário de uso do DBSeek(), onde podemos parametrizá-lo para, caso a chave não seja encontrada, a tabela seja posicionada no próximo registro cuja chave seja imediatamente superior à chave informada para busca. Isto é chamado de “SoftSeek”. Neste cenário, quando a chave não é encontrada, o DBAccess vai reduzindo a query, chave por chave, para localizar e posicionar no primeiro registro imediatamente maior do que a chave informada. Esta operação pode fazer o DBAccess submeter uma query para cada campo da chave de índice, até que algum dado seja encontrado ou não sobrem mais chaves no índice para fazer a comparação.

Por exemplo, para saber qual é o último item sequencial de um pedido, onde a chave é o codigodopedido+sequencia, é muito fácil fazer isso no ISAM, bastando incrementar em uma unidade o código do pedido, fazendo um SoftSeek(), e apos a busca, fazemos um DbSkip(-1) para posicionar no registro imediatamente anterior. Isto no ISAM é muito rápido, mas quando realizado através do DBAccess, o DbSeek() com softseek pode ser mais lento, e ao posicionar no registro adequado, o posicionamento do registro anterior vai fazer o DBAcces montar um cache “backwards” dos registros anteriores. Neste cenário, um select max(itemdepedido) from itensdepedido where pedido=%CODPED% definitivamente será mais rápido — desde que exista um índice que contemple a chave “pedido+itemdepedido”.

Mesmo que em um teste “de mesa” os tempos possam ser muito próximos, o peso da operação é muito mais leve. Isto significa que, mesmo que o tempo da operação apresente um ganho de tempo pouco significante, quanto mais operações simultâneas você fizer usando esta abordagem, você não vai consumir tanto recurso de máquina. Logo, o seu ambiente vai suportar muito mais requisições do que usando a outra abordagem, pois com uma abordagem mais pesada, você consegue atingir o limite de desempenho do equipamento com bem menos requisições simultâneas. Este é um ponto chave de escalabilidade e desempenho. Mesmo que o seu projeto sistemico possa escalar horizontalmente, quanto mais leve for o processo, menos máquinas você precisa acrescentar no seu grid computacional para obter os mesmos resultados.

Conclusão

Dos pontos de vista de usabilidade e desempenho, no geral é mais fácil e performático realizar consultas com queries, desde que as mesmas sejam bem escritas, utilizando-se das boas práticas recomendadas para a construção das requisições, que normalmente são as mesmas para praticamente todos os bancos relacionais. Uma query mal escrita, junto de um algoritmo mal escrito, podem até gerar p resultado esperado, mas a um custo de máquina e desempenho não satisfatórios ou não escaláveis.

Vários SGDBs possuem ferramentas interativas de averiguação de desempenho de Queries. Por exemplo, o Microsoft SQL Management Studio possui um recurso de “mostrar plano de execução estimado”, onde a ferramenta analisa as etapas que o SGDB terá que fazer para acessar as informações desejadas na Query, informando quais das etapas serão as com maior custo.

Procure sempre conhecer e aprender a usar as ferramentas você têm a sua disposição, este conhecimento definitivamente faz a diferença no desenvolvimento de uma aplicação.

Desejo a todos TERABYTES de sucesso 😀 Até o próximo post, pessoal 😀

Anúncios

Um comentário sobre “Acesso a dados – ISAM versus Relacional

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s