armazenamento - Técnico Lisboa

Propaganda
Armazenamento de Informação em HSQL
Bruno Azenha
Instituto Superior Técnico, Universidade Técnica Lisboa
Taguspark, Portugal
[email protected]
Resumo: Neste artigo é analisado o armazenamento de informação no SGBD HSQL.
Este SGBD foi lançado em 1998 por um grupo de entusiastas que quiseram explorar a
linguagem Java para desenvolver um SGBD que fosse leve e simples. A sua versão
actual (1.8.0) suporta um grande conjunto dos standards SQL 92 e 200n, acrescentado
algumas extensões proprietárias. Apresenta três modos principais de funcionamento “in-memory”, “standalone” e “client/server” – sendo as ligações feitas através de um
driver JDBC. Apesar das suas limitações, o facto de ser OpenSource, freeware e
apresentar bons desempenhos para um sistema implementado em Java, levaram a que
tenha sido usado em alguma aplicações bastante conhecidas, tais como o Mathematica,
OpenOffice, JBoss e outras.
Palavras-chave: armazenamento de informação, bases de dados relacional, bases de
dados Java, HSQL
1. Introdução
O HSQL é um SGBD relacional escrito em Java, que disponibiliza um driver
JDBC, suportando um grande conjunto das especificações ANSI-92 SQL, bem como
algumas das melhorias introduzidas pelo SQL 99 e 2003, acrescentando também
algumas extensões proprietárias. Caracteriza-se por ser um SGBD pequeno e rápido,
que suporta tabelas em disco e em memória [1].
O desenvolvimento iniciou-se em 1998 por um grupo de entusiastas que
lançaram o projecto Hypersonic SQL, no seguimento da euforia que se vinha a gerar em
torno da linguagem Java. Em 2000, o projecto foi encerrado, no entanto um grupo de
antigos programadores decidiram-se juntar via Internet e formar o “HSQLDB
Development Group”, lançando a sua primeira versão do HSQL (1.6) em Abril de 2001.
Desde essa altura que novas versões e funcionalidades vêm sido adicionadas
periodicamente.
As aplicações do utilizador só podem aceder ao servidor através da interface
JDBC, sendo que este apresenta três casos de uso: aplicação e BD na mesma máquina
virtual Java (JVM), ligação HTTP através da Internet com recurso a um WebServer ou a
uma Servlet ou ligação TCP/IP (Intranet) usando um servidor dedicado. Conforme os
modos de funcionamento, o SGBD disponibiliza ou não diversas funcionalidades e
graus de desempenho.
Apesar de ser um SGBD algo limitado qando em comparação com outros
sistemas comerciais, o facto de ser OpenSource, freeware e apresentar bons
desempenhos para um sistema implementado em Java, levaram a que venha a ser usado
com sucesso em alguma aplicações bastante conhecidas, tais como o Mathematica,
OpenOffice, JBoss, JFox, TrackStudio e outras.
1
O objectivo deste artigo é descrever o modo como se efectua o armazenamento
de informação no SGBD HSQL. Na Secção 2, procura-se dar uma visão geral do
SGBD. Na Secção 3, é analisada a forma de implementação física das BDs no sistema
de ficheiros do sistema operativo (SO). A Secção 4 apresenta uma análise aos tipos de
tabelas disponibilizados e as suas vantagens e desvantagens, bem como a forma como
estas são implementadas. A Secção 5 expõe o modo como os dados são armazenados,
quer em disco quer em memória. Na Secção 6 apresenta-se a capacidade do HSQL em
lidar com tipos de dados binários. Finalmente, na Secção 7, é feita uma breve análise e
discussão sobre o SGBD.
2. Visão global do sistema
O HSQL apresenta uma arquitectura relativamente simples. Além de um
servidor, disponibiliza também dois tipos de interfaces gráficas. A sua execução em
Sistemas Windows ou Linux efectua-se através de “shell scripts” existentes no
directório “demo”.
Existem três modos de funcionamento neste SGBD:
Server Mode
O motor encontra-se a correr numa JVM, à espera de ligações de programas na
mesma máquina e de máquinas ligadas na rede. Os clientes ligam-se através de
um driver HSQL JDBC. Dependendo do modo de servidor, é possível
disponibilizar acessos até 10 bases de dados, sendo estas especificadas na altura
do arranque.
In-Process ou Standalone Mode
Este modo lança o motor da BD como parte da aplicação na mesma JVM. Para a
maioria das aplicações, este modo poderá ser o mais rápido pois os dados não
precisam de ser convertidos e enviados através da rede. A desvantagem consiste
em tornar-se impossível neste modo ligar à BD fora da aplicação que lhe está a
aceder.
Memory-Only Databases
É possível executar o HSQL num modo em que a base de dados não é
persistente e existe unicamente em memória. Como nenhuma informação é
escrita para disco, este modo é recomendado apenas para processamento interno
de dados, em “applets” ou em aplicações muito específicas.
O modo de interacção dos diferentes componentes é bastante simples, como se
pode observar na Figura 1. Quer as ferramentas que vêm com o HSQL (interfaces
gráfica e ferramentas de gestão/exploração de dados), quer os programas terceiros,
acedem ao motor da BD através da interface JDBC. Esta por sua vez permite diferentes
tipos de uso: ligações HTTP via Internet usando um Web Server ou uma servlet;
standalone em que a aplicação e a BD estão na mesma JVM; ligação TCP/IP numa
intranet em que se recorre ao uso de um servidor dedicado.
2
Demonstration programs and Tools
JDBC Interface
HTTP
Connection
WebServer
Standalone
HSQL
Connection
Servlet
Server
Database Engine
Figura 1 – Arquitectura HSQL
3. Suporte Físico
Quando o servidor é lançado ou quando é estabelecida uma ligação uma BD, é
sempre criada uma nova BD vazia se ainda não existir nenhuma no caminho
especificado. Assim sendo, caso seja cometido um erro a especificar o caminho, é
estabelecida à mesma uma ligação, podendo o utilizador não se aperceber do facto e
estranhar por ver uma BD vazia. Para evitar isto, é possível definir a propriedade
“ifexists = true” no ficheiro “.properties” para permitir que a ligação seja feita apenas a
uma BD que já exista, evitando assim a criação de uma nova BD vazia. Caso esta não
exista, é lançada uma excepção.
Nos casos em que o HSQL se executa em modo “standalone” ou “server”, cada
base de dados irá consistir num conjunto de 2 a 5 ficheiros todos com o mesmo nome
mas com diferentes extensões, localizados na mesma directoria (./data). Por exemplo,
uma BD com o nome “teste”, consistiria nos seguintes ficheiros:
 teste.properties – contém configurações genéricas da BD (versão,
compatibilidades, tamanho do ficheiro de log, etc.)
 teste.script – contêm a definição em SQL das tabelas, dos dados e de
outros objectos da BD, para as tabelas que não são “cached”1
 teste.log – contém as alterações recentes à BD
 teste.data – contém os dados para as tabelas “cached”
 teste.backup – é uma cópia de segurança comprimida do último estado
consistente conhecido para o ficheiro “.data”
Se a BD não tiver tabelas “cached”, os ficheiros teste.data e teste.backup não
existirão. Adicionalmente, o SGBD pode ligar a outros ficheiros de texto formatados,
por exemplo ficheiros CSV2, existentes em qualquer directório do disco.
Cached porque quando os dados que se encontram em disco no ficheiro “.data” são
necessários, o SGBD carrega-os para memória
2 CVS significa “Comma Separated Values”. É um formato de ficheiro usado para portabilidade
de BDs. Cada linha representa um túplo e cada campo encontra-se separado por um “;”
1
3
Enquanto a BD “teste” estiver a ser acedida, o ficheiro “teste.log” é usado para
guardar as alterações feitas aos dados. Este ficheiro é removido quando se executa o
encerramento da BD através do comando “SHUTDOWN”, ou seja, as operações são
guardadas no ficheiro “.script” ou “.data” conforme o conteúdo e as propriedades da
BD. Caso isso não se verifique (se terminar devido a uma falha), o ficheiro é usado da
próxima vez que a BD fôr iniciada para repôr o último estado consistente. Existe ainda
um ficheiro teste.lck que é usado para assinalar que a BD está aberta, e que é eliminado
após um comando de SHUTDOW.
Para efeitos de recuperação, em algumas circunstâncias são criadas cópias dos
vários ficheiros com extensão “.old” e “.new”. Estes ficheiros são usados num processo
de recuperação que se assemelha ao “shadow paging”. Mais informações poderão ser
obtidas em [1] (Appendix C).
4. Estrutura e Implementação das Tabelas
Nesta Secção pretende-se expôr a forma como o HSQL permite armazenar os
dados nas tabelas, quer ao nível dos tipos de tabelas que disponibiliza, quer a forma
como estas se encontram implementadas.
Tipos de Tabelas
O HSQL define vários tipos de tabelas, as tabelas temporárias (TEMP) e 3 tipos
de tabelas persistentes: em memória (MEMORY), em disco (CACHED) e de texto
(TEXT).
As tabelas temporárias só duram enquanto a ligação está activa. Já para as
tabelas em memória, sempre que a BD é iniciada são lidos todos os dados existentes no
ficheiro “.script”. Quando é executado o comando de SHUTDOW, todos os dados são
escritos novamente de memória para o ficheiro “.script”. As vantagens deste tipo de
tabelas sobre as tabelas em disco são tempos de acesso menores. No entanto, este tipo
de tabelas só é adequado para pequenos conjuntos de dados.
No caso das tabelas em disco, os dados só são lidos do ficheiro “.data” quando a
tabela é acedida. Assim, só são mantidos em memória parte dos dados e dos índices,
donde vem o nome deste tipo de tabelas – CACHED (a quantidade de dados a serem
mantidos em cache é configurável). Quando se executa o comando SHUTDOW, os
dados em memória que sofreram alterações são escritos para disco e são efectuadas
cópias de segurança de todas as tabelas em disco. Este tipo de tabelas são ideais para
grandes conjuntos de dados, visto que se acelera o acesso aos mesmos recorrendo à
cache, ao mesmo tempo que permite a manipulação de grandes conjuntos de dados que
não caberiam todos em memória usando tabelas do tipo MEMORY.
Existe ainda um terceiro tipo de tabelas, as tabelas de texto. Este tipo de tabelas
usa ficheiros CSV como fonte de dados. O uso de memória é mais eficiente pois assenta
no carregamento para memória da totalidade dos índices e apenas uma pequena parte
dos dados. Têm também a vantagem da portabilidade e legibilidade do formato, que
praticamente todas as folhas de cálculo, demais SGBDS e processadores de texto podem
ler e modificar. Ficam a perder em termos de desempenho para os outros dois tipos de
tabelas persistentes, já que cada acesso a dados implica um potencial acesso a disco,
com consequentes overheads3.
3
Overhead é um custo adicional em processamento ou armazenamento que, como
consequência, piora o desempenho de um programa ou de um dispositivo de processamento.
São custos adicionais indesejáveis, que deveriam ou poderiam ser evitados.
4
Estrutura das tabelas
Em termos de implementação das tabelas, estas são implementadas como
objectos Java. Na Figura 2 encontra-se uma representação simplificada da estrutura de
uma tabela. Cada tabela contém um nome (que se encontra protegido), um conjunto de
colunas (cada objecto coluna contém o nome, tipo dados, etc.), um ou mais índices (os
túplos só são acessíveis através de índices pelo que estes são indispensáveis), uma cache
(objecto org.hsqldb.persist.DataFileCache) para gerir o acesso a dados se a tabela fôr do
tipo MEMORY ou CACHED, e restrições de integridade (se existirem chaves
estrangeiras).
Se o utilizador não criar uma chave primária, são criados automaticamente uma
coluna e índices ocultos afim de possibilitar os acessos ao túplo. Para além destes
campos, que são os mais relevantes, existem ainda alguns campos para optimizar
acessos, de propriedades da tabela, e mais alguns dependendo depois do subtipo de
tabela a considerar (MEMORY, TEXT, etc.). Na “package” org.hsqldb encontra-se o
ficheiro Table.java contendo a implementação.
Table
Column (Vector)
Index (Vector)
Cache
Constraint (Vector)
Figura 2 – Estrutura de uma tabela
5. Armazenamento e Acesso a dados
Os dados em HSQL só são acessíveis através de índices (Index). Os índices
(definidos na “package” org.hsqldb) são uma implementação de uma árvore AVL4 em
que cada nó tem um apontador para o nó pai afim de permitir navegação bi-direccional
(raiz para as folhas e vice versa), para além do nó esquerdo e nó direito. Na Figura 3
pretende-se mostrar a forma como os índices se encontram estruturados.
As árvores binárias simples apresentam um pior caso quando são inseridos dados
com determinada ordenação e que levam a que estas fiquem muito desequilibradas.
Nessas situações teríamos uma complexidade de O(n) nas procuras e inserções. As
árvores AVL eliminam o pior caso, garantindo que teremos sempre O(lg n) em procuras
4
Árvores binárias de procura, auto-ajustáveis e balanceadas em altura, que receberam o nome
dos seus inventores: Adelson-Velskii e Landis
5
inserções, devido a forma como é feito o balanceamento em cada nó. Em [3] encontrase uma explicação detalhadas das características das árvores AVL.
Cada nó está ligado a um túplo, existindo algumas diferenças entre as várias
subclasses de “Node”. Conforme temos tabelas do tipo MEMORY, CACHED ou
TEXT, existem variações à forma como os nós mantêm referências para outros nós da
árvore ou para os túplos, já que as tabelas apresentam diferentes formas de aceder aos
seus dados. Os dados contidos no túplo são vectores de objectos Java.
No índice existe um nó raiz que se encontra ligado aos restantes nós através de
referências a objectos Java ou apontadores de ficheiros, dependendo da implementação
do nó. O nó raiz toma o valor “null” se a tabela estiver vazia. Um objecto “Index”
contém ainda informação sobre as colunas da tabela que referencia.
Index
Node [root]
Node [parent]
Node [left]
Node [right]
Row [data]
Table
Object
(array) [data]
Figura 3 – Estrutura de um índice
O objecto tabela (ver org.hsql.Table), representado na Figura 2, contém um
objecto cache do tipo “DataFileCache” (ver org.hsqldb.persist.DataFileCache) que,
recorrendo a mais alguns objectos, gere o acesso aos dados e trata as transferências
disco/memória.
A cache funciona como uma “hashtable” em que os túplos se encontram ligados
através de listas duplamente ligadas (ver org.hsqldb.Row e org.hsqldb.CachedRow).
Quando é necessário arranjar espaço para ler mais dados para memória, os mais antigos
são guardados (se necessário) e removidos da cache. A Figura 4 dá uma percepção da
relação entre os diferentes componentes da cache.
Em cada momento existem alguns túplos (Row) na cache, todos eles interligados
através da lista duplamente ligada. O array “Data” contém em cada “bucket” um
apontador para o primeiro túplo (caso exista) mapeado pela função de hash para esse
“bucket”. Por outro lado, a cache contém também uma ligação para o ficheiro “.data”, o
local onde os dados da BD se encontram guardados de forma persistente.
Com esta estrutura não só se torna fácil aceder de forma aleatória aos túplos
graças à “hastable”, mas também é possível efectuar acessos do tipo “scan” a todos os
dados em cache, já que estes se encontram acessíveis através da lista duplamente ligada.
6
A lista permite ainda que as inserções e remoções de túplos da cache seja relativamente
simples.
Cache
Data
(array)
Row
.data:
[contains the binary records and empty blocks]
Figura 4 – Arquitectura da cache
6. Tipos de dados especiais
O HSQL, para além dos tipos tradicionais de dados, apresenta ainda suporte para
tipos de dados especiais, os tipos OTHER e OBJECT, que são mapeados para
java.lang.Object. Isto torna possível o armazenamento de dados binários nas colunas,
tais como fotografias, ficheiros áudio ou objectos Java serializados. A ferramenta “SQL
Tool”, que vem com o SGBD, mantém um buffer binário para onde é possível ler
ficheiros recorrendo ao comando ‘\bl’ e ao uso de “prepared statments”. Quando os
dados estão no buffer, podem então ser escritos para uma coluna da BD. Seguidamente,
apresenta-se um exemplo em que se guarda na tabela ‘musictbl’ um ficheiro mp3:
$ \bl /tmp/favoritesong.mp3
$ \bp INSERT INTO musictbl (id, stream) VALUES (3112, ?);
Quando se quisesse obter os dados outra vez num formato de ficheiro, bastaria
executar os seguintes comandos:
$ SELECT stream FROM musictbl WHERE id = 3112;
$\bd /tmp/favoritesong.mp3
O tamanho máximo do objecto é ditado pela memória disponível. Na prática,
SGBDS de produção têm suportado com sucesso objectos até um megabyte.
7. Discussões e Conclusões
O HSQL enquanto SGBD não pode ser considerado com uma alternativa a
SGBDs já estabelecidos e direccionados a uso empresarial e/ou em larga escala, como o
7
Oracle, SQLServer ou até o MySQL devido às limitações que ainda apresenta. Ainda
assim, para armazenamento de pequenos conjuntos de dados, que não requeiram
grandes tratamentos ou para dar suporte a aplicações com poucas exigências de
manipulação de dados, este SGBD poderá ser uma hipótese a considerar pela sua
simplicidade e relativa rapidez.
O suporte de armazenamento de dados tem vindo a sofrer sucessivas evoluções,
acrescentando cada vez mais funcionalidades e começa nesta altura a revelar-se
interessante, quer pelo que já disponibiliza quer pelas funcionalidades que estão em
desenvolvimento (suporte para “datasets” entre 10MB e 2GB, acessos concorrentes,
etc). É portanto, um sistema que poderá ser acompanhado com algum interesse, até por
possibilitar um aprendizagem académica do funcionamento de um SGBD, mesmo que
bastante simplificado.
8. Referências
[1] HSQL Development Group, Hsqldb User Guide, www.hsqldb.org, 2005
[2] HSQL Development Group, Hypersonic SQL Arquitecture, www.hsqldb.org, 2005
[3] David Carlson, AVL Trees, http://cis.stvincent.edu/swd/avltrees/avltrees.html, 2004
8
Download