S neustále se
zrychlujícím rozvojem vědy a techniky roste i
množství dat, které je nutné zpracovávat pokud možno
v reálném čase a často se stává, že určité projekty
vyžadují výkon, který přesahuje hranice běžných
databází. Takovým projektem je například
projekt Evropské kosmické
agentury (ESA)
Gaia, jenž má za úkol
pořizovat soupis vesmírných objektů vyskytujících se
v naší galaxii a zaznamenávat jejich polohu, pohyb a
změny jasu. Technologie, která byla vybrána pro tento
projekt, se nazývá Caché eXTreme a účelem článku je
seznámit s ní čtenáře.

Caché eXtreme je
sada technologií firmy InterSystems, které
zpřístupňují vlastnosti vysoce výkonné objektové
databáze prostředkům programovacího jazyka Java
k vytváření XTP (eXtreme Transaction Processing)
aplikací. Pro dosažení maximálního výkonu používá
Caché eXTreme in-proces verzi databáze Caché,
implementovanou pomocí Java JNI (Java Nativ
Interface) a Caché call-in API.

Samotný framework
se skládá z následujících komponent:

  • API
    vrstvy eXTreme Globals
    nabízí přímý přístup
    k uloženým datům s maximální flexibilitou a
    rychlostí.

  • eXTreme Event Persistence (XEP) umožňuje
    projekci jednoduchých objektů Javy do XTP
    persistentních událostí, které lze extrémně
    rychle uložit a zpracovávat. Jedná se o odlehčené
    API pro tzv. „low-latency objects“ (objekty
    s krátkou dobou přístupu).

  • eXTreme Dynamic Object (XDO) API poskytuje
    objektově orientovaný přístup k datům, která jsou
    uložena v databázi Caché. Přístup k datům je
    dynamický v tom smyslu, že třídy popisující
    uložené objekty nemusejí být známy v době
    kompilace zdrojového kódu Javy a zároveň
    neprobíhá ani žádné generování kódu.

Výše vyjmenované
komponenty jsou navrženy tak, aby byly použitelné
s platformami, jako jsou např. JCACHE, OSGi, CEP nebo
JMS:

Cache

 

Instalace
a nastavení prostředí

Aby bylo možné
otestovat a plně využít prostředky Caché eXTreme, je
potřeba si nainstalovat Caché verze 2010.2 a vyšší,
s volbou zabezpečení „
minimal“ nebo „normal“, a Java
JDK 1.6 a vyšší. Pro jednotlivá prostředí je potřeba
nastavit příslušné proměnné prostředí. V následujícím
seznamu je uveden seznam proměnných a jejich
nastavení pro prostředí Windows:

  • GLOBALS_HOME cesta do složky, kde
    je nainstalováno Caché

  • PATH musí
    obsahovat cestu do
    GLOBALS_HOME/bin

  • CLASSPATH musí obsahovat plnou
    cestu k níže uvedeným jar archivům, které jsou
    standardně uloženy ve složce
    GLOBALS_HOME\dev\java\lib\JDK16\:

    • CacheDB.jar, CecheeXTreme.jar,
      CacheJDBC.jar

Seznam proměnných
prostředí a jejich nastavení pro platformu UNIX
(Linux) je možné nalézt v dokumentaci Caché.

Globály a
Caché

Jádro databáze
Caché představuje mimořádně efektivní stroj
vícerozměrných dat. Tyto vícerozměrné struktury
(vícerozměrná pole), známé pod názvem „
globály“, se používají
zvláště tehdy, jedná-li se o neobvyklé nebo velmi
specializované struktury dat nebo je-li požadovaný
nejvyšší možný výkon.

API rozhraní vrstvy
Globals, které je plně dokumentováno, umožňuje
aplikacím psaných v Javě manipulovat s globály, a je
plně optimalizované pro extrémně rychlé ukládání a
čtení několika základních datových typů –
(int,long,double,byte[],String a ValueList). Toto
rozhraní je využíváno i vrstvou XDO pro rychlý
přístup k datům uloženým jako objekty Caché.

Globál v Caché,
obdobně jako jiná řídká pole, je stromová datová
struktura. Základní koncept globálů může být
ilustrován na analogii ke jmenné konvenci balíčků
v Javě. Podobně jako třídy v Javě, jsou prvky
vícerozměrných polí jednoznačně identifikovány
jmenným prostorem obsahujícím libovolný počet
identifikátorů. Na všechny jmenné prostory ve
struktuře globálů se odkazujeme jako na „
uzly“. Uzel musí
mít buď potomka, nebo obsahovat data, nebo oboje. Za
název globálu bývá považován identifikátor kořenového
uzlu a je považován i za identifikátor celého
globálu, všechny ostatní identifikátory jmenného
prostoru jsou považovány za indexy. Úplnému jmennému
prostoru uzlu (název globálu plus index) se říká
adresa
uzlu
“.
V notaci Caché je adresa uzlu reprezentována znakem
„stříška“ (^), následovaným názvem globálu a čárkou
odděleným seznamem indexů, který je uzavřen
v kulatých závorkách.

Data do globálu mohou byt
ukládána s libovolným počtem indexů. Indexy navíc
nemají definován typ, a mohou tedy obsahovat data
libovolného typu. Jeden index může být celé číslo,
např. 34, zatímco dalším indexem je smysluplný název,
např. PoložkaŘádku. To platí dokonce i na téže úrovni
indexu.

Uvažujme aplikaci pro
inventuru stavu zásob, která poskytuje např.
informace o druhu, velikosti, barvě a vzoru. Globál
obsahující tyto informace může mít následující
strukturu:




^Zasoba(<polozka>,<velikost>,<barva>,<vzor>)
= <množství>

 

Globál pak může být
naplněn konkrétními daty takto:

 

^Zasoba(“cvičební
úbor”,4,”modrá”,”květinový”)=3

 

Díky přístupu přes datový
uzel v této struktuře je velmi jednoduché určit, zda
je na skladě cvičební úbor velikosti 4 s květinovým
vzorem. Požaduje-li zákazník cvičební úbor velikosti
4 a není si jist barvou či vzorem, je velmi
jednoduché zobrazit jejich úplný seznam tak, že
cyklicky projdeme všechna data, která jsou uložena v
uzlech:

 

^Zasoba(“cvičební úbor”,4)

 

Všechny datové uzly v
uvedeném příkladu byly stejné povahy (uchovávaly
množství) a všechny byly uloženy na stejné úrovni
indexování (čtyři indexy) s podobnými indexy (třetí
index byl vždy text vyjadřující barvu). Ale nemusí
tomu tak být. Datové uzly mohou mít odlišný jak
počet, tak typ
indexů a mohou obsahovat
rozdílné typy dat. Níže je uveden příklad
složitějšího globálu s daty
faktury, obsahující různé
typy dat uložené na různých úrovních
indexování:




^Faktura(<c faktury>,”Zákazník”) =
<Informace o zákazníkovi>



^Faktura(<c faktury>,”Datum”) = <Datum
faktury>



^Faktura(<c faktury>,”Položky”) = <Počet
položek faktury>



^Faktura(<c faktury>,”Položky”,1,”Číslo”) =
<objednací číslo první položky>



^Faktura(<c faktury>,”Položky”,1,”Množství”) =
<množství první položky>



^Faktura(<c faktury>,”Položky”,1,”Cena”) =
<cena první položky>



^Faktura(<c faktury>,”Položky”,2,”Číslo”) =
<objednací číslo druhé položky>

 

Do datového uzlu se často
ukládá pouze jeden datový prvek, např. datum nebo
množství. Někdy je výhodné ukládat více datových
prvků společně jako jeden samostatný datový uzel. To
je vhodné u sady souvisejících údajů, které se často
zpracovávají společně. Snížením počtu přístupů do
databáze může dojít ke zlepšení výkonu, zvláště při
zpracování v síti. Ve výše uvedené faktuře např.
každá položka obsahuje objednací číslo zboží,
množství a cenu uložené zvlášť v samostatných uzlech.
Tyto údaje ale mohou být také uloženy jako seznam
prvků v jednom uzlu:

 

^Faktura(<c faktury>,”PoložkyŘádku”,<cislo
polozky>).

 

Pro vysokou propustnost
systémů s tisíci uživateli je rozhodující omezení
počtu konfliktů soupeřících procesů. K největším
konfliktům dochází mezi transakcemi, které požadují
přístup ke stejným datům. Procesy Caché nezamykají
během aktualizace dat celé stránky. Naproti tomu
transakce vyžadují časté čtení nebo aktualizaci
malých množství dat, a proto stačí zamykání dat na
logické úrovni. Možné konflikty v databázi jsou dále
snižovány pomocí atomizovaných operací sčítání a
odčítání, které nevyžadují zamykání. (Tyto operace se
používají především u čítačů, které přiřazují
identifikační čísla, a pro modifikaci statistických
čítačů.)

Než se podrobněji podíváme
na některé části API rozhraní vrstvy
eXTreme Globals, ukažme si, jak
může vypadat velice jednoduchý program, který vytvoří
v databázi globál s dvěma uzly a poté oba uzly načte
do paměti. Během vytváření globálu a jeho načtením do
paměti, se vždy vytvoří a poté ukončí spojení
s databází, aby se prověřila persistence globálu
v databázi. Zde je zdrojový kód aplikace:

 

package gds.sga;

import com.intersys.globals.*;

 

class
SimpleGlobals {

public static void main(String[] args) {

Connection myConn =
ConnectionContext.getConnection();

 

try
{

//
Connection 1 is used to create a new global array
with two nodes

myConn.connect(“User”,”_SYSTEM”, “SYS”);

NodeReference nodeRef1 =
myConn.createNodeReference(“myGlobal”);

nodeRef1.set(“Hello world”); // create root node
^myGlobal

nodeRef1.appendSubscript(“sub1”); //point to node
address ^myGlobal(“sub1”)

nodeRef1.set(“This is a subscripted node”); //
create subnode ^myGlobal(“sub1”)

nodeRef1.close();

myConn.close();

 

//
Connection 2 is used to read both existing
nodes

myConn.connect(“User”,”_SYSTEM”,”SYS”);

NodeReference nodeRef2 =
myConn.createNodeReference(“myGlobal”);

System.out.println(“Value of ^myGlobal is ” +
nodeRef2.getString());

nodeRef2.appendSubscript(“sub1”); // point to
subnode

System.out.println(“Value of ^myGlobal(\”sub1\”) is
” + nodeRef2.getString());

nodeRef2.setSubscriptCount(0); // point to root
node

//
nodeRef2.kill(); // delete entire array

nodeRef2.close();

myConn.close();

}

catch (GlobalsException e) {
System.out.println(e.getMessage()); }

} //
end Main()

} //
end class SimpleGlobals

 

Výsledek kompilace
a spuštění výše uvedeného příkladu z příkazové řádky
můžeme vidět na obrázku níže.

 

cache1_2

 

V portálu Caché se
pak můžeme přesvědčit, že doopravdy došlo k vytvoření
globálu „^myGlobal“:

 

cache1_3

 

Základním nástrojem
pro vytváření, mazání a odkazování se na globály
v databázi je instance třídy
„NodeRefence“. Metody
této třídy poskytují veškeré prostředky pro práci
s globály. Podívejme se na některé z těchto
metod.

Metoda „set()“ přiřadí
nebo změní hodnotu referencovaného globálu. Pokud
uzel nemá hodnotu nebo neexistuje, metoda vytvoří
nový persistentní uzel a přiřadí mu požadovanou
hodnotu. Tato hodnota může být typu int, long,
double, byte[], String nebo ValueList.

Data a kód jsou v systému
Caché ukládány v souborech s názvem CACHE.DAT (v
jednom adresáři může být pouze jeden takový soubor).
Každý takový soubor obsahuje množství globálů
(vícerozměrných polí). Název globálu musí být v daném
souboru jedinečný, ale může se opakovat v jiných
souborech. Tyto soubory lze volně považovat za
databáze. Místo udávání určitého databázového souboru
používají procesy Caché při přístupu k datům tzv.
názvový prostor. Je to logická mapa názvů, ve které
jsou mapovány názvy vícerozměrných polí globálů a
kódu do databáze. Pokud je databáze přesunuta z jedné
diskové jednotky na jinou nebo na jiný počítač, je
třeba upravit pouze mapu názvového prostoru. Vlastní
aplikace se nezmění.

Objekt obsahující odkaz na
globál operuje standardně v názvovém prostoru
uvedeném při navázání spojení se systémem, je však
možné současně operovat i ve více názvových
prostorech. Jako příklad si uveďme úryvek kódu, který
vytvoří současně dva globály ve dvou různých
názvových prostorech, „User“ a „Samples“. Oba globály
se mohou a budou jmenovat stejně, neboť názvové
prostory jsou mapovány do dvou různých
databází:

 

try
{

//
Create a node in each namespace

NodeReference nodeRef =
myConn.createNodeReference(“myGlobal”);

 

myConn.setNamespace(“User”);

nodeRef.set(“This node is in namespace ” +
myConn.getNamespace());

 

myConn.setNamespace(“Samples”);

nodeRef.set(“This node is in namespace ” +
myConn.getNamespace());

 

//
Access and print the value of each node

myConn.setNamespace(“User”);

System.out.println(nodeRef.getString());// Prints:
“This node is in namespace User”

 

myConn.setNamespace(“Samples”);

System.out.println(nodeRef.getString());// Prints:
“This node is in namespace Samples”

 

myConn.close();

nodeRef.close();

}

catch
(GlobalsException e) { /* message */ }

 

 

Níže uvedený útržek
kódu ilustruje, jakým způsobem se
API rozhraní vrstvy eXTreme Globals vypořádává
s vytvářením jednotlivých uzlů ve stromové struktuře
globálu:

 

//
nodeRef initially points to root node ^myGlobal, but
changes three times

nodeRef.appendSubscript(“x”); // nodeRef points to
^myGlobal(“x”)

nodeRef.appendSubscript(“y”); // nodeRef points to
^myGlobal(“x”,”y”)

nodeRef.set(3.14); // create persistent
^myGlobal(“x”,”y”) = 3.14

nodeRef.setSubscriptCount(0); // nodeRef points to
root node ^myGlobal again

 

V případě odstraňování
uzlů globálu předpokládejme, že je v databázi uložen
globál s následující strukturou:

 

^myGlobal(1)

^myGlobal(“two”,”a”)

^myGlobal(“two”,”c”,”third”)

^myGlobal(3.1)

^myGlobal(3.1,”x”,25)

 

Předpokládejme
dále, že objekt
„nodeRef“ obsahuje referenci
na kořenový uzel „
^myGlobal“, pak metody
kill()“ a „killNode()“ jsou implementovány níže
uvedeným způsobem:

 

nodeRef.killNode(3.1); // kill only
^myGlobal(3.1)

nodeRef.kill(“two”); // kill ^myGlobal(“two”) and
all of its subnodes

nodeRef.kill(); // kill all nodes in global array
^myGlobal

 

Abychom získali
představu, jak lze procházet jednotlivé uzly globálu,
je nutné vědět, jak jsou uzly globálu v databázi
uspořádány. Neexistuje žádné pravidlo, které by
vyžadovalo, v jakém pořadí je nutné jednotlivé uzly
do globálu ukládat, neboť globál je reprezentován
stromovou strukturou, hierarchie uzlů je automaticky
generována, kdykoli dojde k přidání persistentního
uzlu do globálu. Například globál se může skládat ze
tří persistentních uzlů:

^myGlobal(„a“,“23“), ^myGlobal(„a“,“
x“)
,
^myGlobal(„b“). Tyto uzly je možné
přidávat do globálu v libovolném pořadí a výsledná
struktura globálu v databázi bude vždy
takováto:

 

^myGlobal (korenovy uzel bez prirazene
hodnoty)

^myGlobal(“a”) (uroven 1 uzel bez prirazene
hodnoty)

^myGlobal(“a”,”23″) = <nejaka hodnota>

^myGlobal(“a”,” x”) = <nejaka hodnota> (druhy
index obsahuje a „ „ znak mezera!)

^myGlobal(“b”) = <nejaka hodnota>

 

Pořadí uzlů je dáno
úrovní vnoření uzlu a použitým způsobem třídění znaků
(collation order) v databázi.

API rozhraní nabízí
tři základní metody třídy „
NodeReference“, které je
možné použít k procházení jednotlivými uzly globálu:
nextSubscript()“, „previousSubscript()“, „exists()“, „hasSubnodes()“.

Níže uvedený úryvek
kódu demonstruje jeden z možných způsobů procházení
uzly globálu „
^myNames“, který obsahuje tato
data:

 

^myNames (valueless root node)

^myNames(“dogs”) (valueless level 1 node)

^myNames(“dogs”,”Balto”) = 6

^myNames(“dogs”,”Hachiko”) = 8

^myNames(“dogs”,”Lassie”) = 9

^myNames(“dogs”,”Lassie”,”Timmy”) = 10

^myNames(“dogs”,”Whitefang”) = 7

^myNames(“people”) (valueless level 1
node)^myNames(“people”,”Anna”) = 2

^myNames(“people”,”Julia”) = 4

^myNames(“people”,”Misha”) = 5

^myNames(“people”,”Ruri”) = 3

^myNames(“people”,”Vlad”) = 1

 

//
Search for first child node of ^myNames(“people”) in
ascending collation order

nodeRef.appendSubscript(“”);

String subscr = nodeRef.nextSubscript();

if
(subscr.equals(“”))
nodeRef.setSubscriptCount(nodeRef.setSubscriptCount-1);

 

//
Access all level 2 nodes under ^myNames(“people”) in
ascending order

System.out.print(“Ascend from below first subscript:
“);

while (!subscr.equals(“”)) {

nodeRef.setSubscript(nodeRef.getSubscriptCount(),
subscr);

if
(nodeRef.exists()) { // if this node has data

System.out.print(“\”” + subscr + “\”=” +
(nodeRef.getInt()) + ” “);

}

subscr = nodeRef.nextSubscript();

};

 

Výše uvedené
příklady se dotkly celého API modulu eXTreme
Globals velice povrchně,
jen abychom získali představu o tom, o co se opírají
vrstvy
eXTreme Event Persistence
(XEP) a eXTreme Dynamic Object (XDO). Podrobnější
informace je možné nalézt v dokumentaci Caché.

V další části
článku se budeme věnovat modulu
Caché eXTreme Event
Persitence, který umožňuje pracovat s projekcí
jednoduchých objektů Javy jako s persistentními
událostmi.