Changes

Cassandra -NoSQL database

12,866 bytes added, 21:51, 16 February 2019
Partíció mérete
* '''Keyspace''': ez felel meg egy adatbázisnak az RDBMS-ben, vagy egy Index-nek az Elasticsearch-ben
* '''Column families''': egy tábla az adatbázisban, amihez sorok tartoznak. Azonban az oszlopok fajtája soronként eltérhet.
* '''Partition''': Egy sor egy táblában Azon sorok (illetve cellák) összessége, amiknek a particionáló kulcsuk értéke egyenlő (tehát ugyan azon a node-on lesznek). Ezt előre ki kell számolni, lásd lentebb.
* '''Clustering''': sorok sorba rendezése egy táblán belül (Semmi köze a Cassandra cluster-hez)
===Táblák felépítése (Column families)===
Cassandra-ban nem úgy kell elképzelni a táblaszerkezetet mint az RDBMS világban, mint egy AxB nagyságú táblázat, ahol egy entitást egy sor reprezentál. Cassandra-ban egy sort úgy kell elképzelni, mint a listája az adott sorban szereplő Oszlopnév->érték (név->érték ) pároknak. Cassandra-ban azok az oszlopok egy sorban, ahol nincs érték, nincsenek definiálva az adott sorra, az az oszlop, aminek nem adtunk értéket egyszerűen nem szerepel az Oszlopnév->érték felsorolásban.
Tételezzük fel, hogy van egy táblánk, aminek 11 oszlopa van, ahol az oszlop nevek Column1-től mennek Column11-ig. Minden sorba csak azok az oszlopok fognak bekerülni, ahol tényleg van érték:
* Egyértelműen azonosítani akarunk egy sort a kulccsal egy táblában (ez eddig triviális, RDBMS-ben is így van ('''primary vagy compound primary key''')
* A Cassandra-nak be kell tudni azonosítani hogy az adott sor melyik node-on van eltárolva. Az adott kulcs alapján a Cassandra muszáj hogy mindig ugyan azt a node-ot találja meg ahova elsőre beszúrta az adatot. ('''Partition key''')
* Sorrendezése a soroknak a táblán egy partíción belül (vagyis azonos particionáló kulccsal rendelkezező sorokon belül. ): Cassandra-ban a sorok sorrendezését már a tábla létrehozásakor definiálni kell ('''Clustering key'''= csoportosító kulcs)
<br>
Ugyan úgy Ugyanúgy mint az RDBMS világban, a Cassandra táblákon belül is egyértelműen, egyedi módon azonosítani kell tudni minden egyes sort egy egyedi azonosító alapján. Ez az elsődleges kulcs, ez eddig triviális. Az azonban, hogy hogyan épül fel az elsődleges kulcs, már kicsit bonyolultabb. A '''Primary key''' (elsődleges kulcs) a két már említett kulcsfajtából épülhet fel. Tartalmaznia kell legalább egy '''partition key'''-t és 0 vagy több '''clustering key'''-t. Amennyiben több mint egy kulcsból épül fel (tehát legalább egy p'''artition partition key'''-ből és vagy még több valamennyi '''clustering key'''-ből vagy több mint egy további partition kulcsokból) akkor '''Compound primary key'''-nek hívjuk.
:[[File:ClipCapIt-181007-185315.PNG]]
===Partition key===
A Cassandra tipikusan egy sok node-on futó elosztott alkalmazás, a benne tárolt adatok egyenletesen szét vannak szórva a cluster-ben. A szétszórás értelem szerűen értelemszerűen nem táblák mentén történik, hanem tábla soronként. Tehát elképzelhető egy egy táblából minden egyes sor más és más node-ra kerül.
Cassandra-ban a node-ok egy gyűrűbe vannak szervezve. Minden egyes node-nak van egy egyedi azonosítója, egy 64 bites token. (from -2<sup>23</sup> to 2<sup>63</sup>-1)
Azt hogy egy partíció (sorazonos particionáló kulccsal rendelkező sorok összessége) melyik node-on éli az életet (hova kell kiírni ill. honnan kell beolvasni) a particionáló dönti el a partion kulcsok alapján. Az alapértelmezett particionáló a '''Murmur3Partitioner''', ami mindig ugyan arra ugyanarra a 64 bites tokenre képzi le ugyan azt ugyanazt a '''partion kekey'''y-t.
-4069959284402364209
</pre>
 
 
===Compound primary key===
);
</pre>
{{note|A kulcsok sorrendje a PRIMARY KEY megadásánál kritikus. Ez határozza majd meg, hogy milyen 'sorrendben' kell majd őket szerepeltetni a lekérdezések WHERE szekciójában (lásd [[Cassandra_-NoSQL_database#Kulcsok_.C3.A9s_indexek_kezel.C3.A9se|Kulcsok és indexek kezelése]] című fejezetet)
===Clustering key===
A Clustering key (csoportosító kulcs) kizárólag egy táblán partíción belül határozza meg a sorok sorrendjét. Ha a compound primary kulcsunk több clustering kulcsot is tartalmaz, akkor először a listában az leső alapján fog sorrendezni, aztán a második alapján, és így tovább. Megadhatjuk a rendezés irányát is. Fontos ezt már a tábla tervezésekor kitalálni, mert később ezt már nem tudjuk megváltoztatni.  
A rendezés irányát a WITH CLUSTERING ORDER BY kulcsszóval adhatjuk meg:
<pre>
CREATE TABLE store_by_location (
country col1 text, state col2 text, city col3 text, store_name text, description col4 text, PRIMARY KEY (countrycol1, statecol2, city, store_namecol3)) WITH CLUSTERING ORDER BY (state col2 DESC, city ASC, store_name col3 ASC);
</pre>
A fenti példában a particionáló kulcs a col1 és a két Clustering (csoportosító) kulcs a col2 és col3. A col2 szerint visszafele, a col3 szerint előre rendezünk. Ha nem adjuk meg, akkor az előre rendezés az alapértelmezett.
 
 
{{note|A rendezés csak akkor értelmezett egy Clustering key alapján, ha a partitioning kulcsok megegyeznek két sorban, ahol a Clustering kulcsok különböznek (ezért mondtuk, hogy csak egy partíción belül értelmezett). Tehát egy olyan adathalmazban, ahol a particionáló kulcsok értékkészlete unique, ott a Clustering kulcsoknak a sorok sorrendjére nincs hatása. Úgy is mondhatjuk, hogy csak egy node-on belül rendezik a sorokat}}
 
A fenti példában, azokban a sorokban, ahol a '''col1''' megegyezik, a '''col2''' szerint lesznek visszafelé rendezve a sorok. És azokban a sorokban, ahol a '''col1''' és a '''col2''' is megegyezik, a '''col3''' szerint előre lesznek rendezve a sorok.
 
Pl. beszúrjuk ezeket az alábbi sorrendben:
col1=k1, col2=B, col3=B
col1=k1, col2=A, col3=Z
col1=k1, col2=C, col3=X
col1=k1, col2=B, col3=A
 
 
Akkor a végeredmény a következő lesz, ha lekérdezzük (így is van tárolva). A particionáló kulcsok minden sorban azonosak (tehát ugyan azon a node-on vannak), és a col2 szerint visszafele, a col3 szerint előre rendez:
col1=k1, col2=C, col3=X
col1=k1, col2=B, col3=A
col1=k1, col2=B, col3=B
col1=k1, col2=A, col3=Z
 
<br>
A sorrendezésen felül a clustering kulcsoknak a lekérdezés WHERE szekciójában van szerepe, mert felírhatunk rájuk '''>, >=, <, <=''' operációkat, amiket a particionáló kulcsokra nem írhatunk fel. (lást részletesen a [[Cassandra_-NoSQL_database#Lek.C3.A9rdez.C3.A9s_megk.C3.B6t.C3.A9sek|Lekérdezés megkötések]] című fejezetben.
 
===Partíció példa===
A fenti tábla definíció mellett adott a következő adathalmaz:
col1=k1, col2=B, col3=B
col1=k1, col2=A, col3=Z
col1=k1, col2=C, col3=X
col1=k2, col2=B, col3=A
 
 
Két partícióra oszlanak, mivel a col1 a particionáló kulcs:<br>
1. partíció:
col1=k1, col2=B, col3=B
col1=k1, col2=A, col3=Z
col1=k1, col2=C, col3=X
 
2. partíció:
col1=k2, col2=B, col3=A
 
 
<br>
==Időbélyegek és Time to Live==
Mary | Rodriguez | 3588
</pre>
 
 
 
<br>
==Másodlagos indexek==
* Gyakran átírt mezőknél: Ha gyorsabban gyűlnek fel a tombstones-ok mint ahogy azokat a Cassandra fel tudná dolgozni, hibára fog futni az update egy idő után
Példa: a user nevű táblában a last_name-re hozunk létre másodlagos indexet.
<pre>
CREATE INDEX ON user ( last_name );
CREATE INDEX ON user ( emails );
 
 
<br>
Cassandara 3.4-től használhatjuk az Apple által kifejlesztet SASI másodlagos index implementációt, ami több funkcionalitást tesz lehetővé mint a beépített változat:
* Kisebb, nagyobb -ra keresés
</pre>
==='''INDEX vs Filter===:'''<br>Hatékonysági okokból alap esetben a Cassandra csak arra az oszlopra enged lekérdezni, amire van index.Csak akkor lehet index nélküli oszlopra hivatkozni a WHERE kifejezésben ha ezt implicit engedélyezzük az '''ALLOW FILTERING''' kulcsszóval a lezáró ; előtt. Ugyanis ha egy oszlopon nincs index, akkor a Cassandra az összes sort be fogja olvasni, és filterrel fogja kiválasztani WHERE-ben definiált mezőket. Milliós sorszám esetén ez már problémás lehet.
https://www.datastax.com/2012/01/getting-started-with-cassandra
 
 
<br>
A Materialized view az eredeti tálba egy részhalmazának, vagy az egész tálba egy olyan másolata, ahol más kulcsok alapján tesszük kereshetővé ugyan azt az adathalmazt. Ez akkor jó, ha van egy táblánk amit A és B oszlop szerint is keresni akarunk, ilyenkor csinálunk egy táblát, ahol A a kulcs, és egy Materialized view-t, ahol a B a kulcs. Ennek az a nagy előnye azzal szemben, mint ha erre két valódi táblát definiálnánk, hogy mikor az igazi táblába szúrunk be, akkor a materialized view-t is frissíteni fogja a Cassandra, nem nekünk kell manuálisan megcsinálni.
===Kulcsok az MW-ben===
Az MW-ban a kulcsokra nagyon komoly megkötés van:
* az alap tábla összes kulcsát tartalmaznia kell
Mire kell figyelni: <br>
* Avoid too large partitions
* Choose your partition key in a way that distributes the data correctly, avoiding cluster hotspots (the partition key chosen above like days of the week is not a good one as it leads to temporal hotspots)
<br>
<br>
=Architektúra=
 
==Alapfogalmak==
 
===Gossip (pletykák)===
 
 
 
===Snitches===
A snitch protokoll segítségével térképezi föl egy node, hogy milyen messze vannak tőle az általa ismert node-ok, hogy ha egy műveletben koordinátor node-ként vesz részt, meg tudja határozni hogy melyik node-okról olvasson (a legközelebbi) és melyik node-okra írjon.
 
 
 
 
===Lightweight Transactions (check-and-set)===
Cassandra-ban nem létezik a hagyományos értelembe vett tranzakció kezelés, csak az úgynevezett pehelysúlyú tranzakció (LWT) ami azt biztosítja, hogy egy olvasás és az azt követő írás egy tranzakcióban lesz ('''linearizable consistency'''). Az olvasással ellenőrizzük, hogy az adott adat szerepel e már az adatbázisban, és ha nem, akkor bírjuk. Ez LWT csak egy partíción belül működik és elég költséges művelet, mivel a végrehajtásához a Cassandra a Paxos nevű konszenzus algoritmust futtatja. A konszenzus kialakításához a partíciót tároló replikák többségének konszenzusra kell jutnia az adott tranzakciót illetően.
 
 
 
 
==Node-ok csoportosítása==
Cassandrában a node-okat két szinten csoportosíthatjuk: Rack és Data Cener.
 
* '''Rack''': A rack-ben olyan nodo-kat csoportosítunk, amik tényleg egy fizikai rack-ben vannak, tehát ezek vannak a "legközelebb" egymáshoz.
* '''Data Center''': Egy datacenter-ben azokat Rack-eket csoportosítjuk, amik fizikailag egy szerverfarmon vannak.
:[[File:ClipCapIt-181106-205504.PNG]]
Alapértelmezetten minden node-unk a '''RACK1'''-be fog tartozni, és a '''DC1''' datacenterbe.
 
===Seed Nodes===
Minden egyes node-nak amit hozzáadnunk a cluster-hez szüksége van egy referencia node-ra, amitől le tudja kérdezni a cluster topológiáját (élő és halott node-ok, távolság..). Ezeket hívják seed-node-nak.
 
Minden egyes data-center-ben legalább két seed-node-ot kell létrehozni. A nem seed-node -knak a seed-nodeokat a cassandra.yaml fájlban kell statikusan beállítani. Alapértelmezetten csak a localhost van hozzáadva a listához:
<pre>
- seeds: "127.0.0.1"
</pre>
=Telepítés=
...
=CQL (Cassandra Query Language)alapok=
https://www.datastax.com/2012/01/getting-started-with-cassandra
2 | eng | fred | smith
</pre>
 
 
==Listák és Map-ek==
 <br> =Kulcsok és indexek kezeléselekérdezésekben=
==Kulcs használati alapelvek==
* Ugyan azt az azzal a primary kulccsal (partition és clustering kulcs együttvéve) egy INSERT-et többször is ki lehet adni, elsőre beszúrja, másodikra frissíti a sort, tehát átírja a nem kulcs mezőket a sorban az új értékre.  
</pre>
A fenti példa azért eredményez két sort, mert a Clustering kulcs értékében elérnek (col2mod). Ha a Clustering kulcs is azonos lenne, és csak col4-ben térnének el, akkor nem eredményezne új sor.
 
==Lekérdezés megkötések(Restriction)==
https://www.datastax.com/dev/blog/a-deep-look-to-the-cql-where-clause<br>
Itt a megkötés (restriction) szó azt jelenit, hogy egy kulcs szerepel e a WHERE ágban vagy sem. Ha igen, akkor az egy 'restricted' kulcs, ha nem szerepel, akkor az egy 'unrestricted' kulcs.
 
Példa tábla:
<pre>
CREATE TABLE adam.test1 (
col1 text,
col2 text,
col3 text,
col4 text,
PRIMARY KEY ((col1), col2, col3)
);
 
CREATE INDEX indexOnCol4 ON adam.test1 (col4);
</pre>
 
 
===Particionáló kulcsok + indexek===
Egy lekérdezésben vagy az összes particionáló kulcsot szerepeltetjük, vagy csak indexelt oszlopokat. Tehát két lehetőségünk van:<br>
1. csak a paritcionáló kulcs(ok):
SELECT * FROM adam.test1 WHERE col1 = 'k1'
(Persze ezt kiegészíthettük volna Clustering kulcsokkal)
 
2. csak az index:
SELECT * FROM adam.test1 WHERE col4 = 'C'
 
 
Alap esetben sem a particionáló kulcsokra, sem az indexekre nem használhatjuk a <, <=, >, >= operációkat, csak az =, IN ('k1', 'k2',..).
{{warning|Az IN használata erősen ellenjavallott performancia okokból, de ha már használjuk, akkor az IN értékkészletét alacsonyan kell tartani.}}
 
(A másodlagos indexre megengedett a <,> operáció, ha a SASI implementációt használjuk)
 
 
 
===Clustering kulcsok===
A Clustering kulcsokra már nagyon sok operáció engedélyezett:
* Ha csak egy oszlopot vonunk be az operációba: >, >=, =, <,<=, IN, CONTAINS
* Ha több clustering kulcsot is bevonunk: >, >=, =, <,<=, IN
 
 
A Clustering kulcsoknak az a sorrendje, ahogy a PRIMARY KEY kifejezésben megadtuk. Ha egy Clustering kulcsot szerepeltetünk a WHERE -ben, akkor az összes azt megelőző kulcsot is be kell rakni (pl. nem lehet benne csak a col3, ha a col2-öt nem szerepeltetjük)
 
 
Fontos, hogy ha a Clustering kulcsra írunk bármilyen megkötést (restriction-t) ami nem a '=', vagyis egyike a >,>=,<, <=, IN, CONTAINS megkötéseknek, akkor az összes sorban korábbi Clustering kulcsot is szerepeltetni kell, és azokban csak a '=' megkötés használható, vagy a <,>.. megkötése csak a sor legvégén használhatóak, a legutolsó clustering kulcsban, amit még beteszünk a WHERE -be (mert hogy a Clustering kulcsok megadása nem kötelező)
 
 
Ha tartományra kérdezünk le, akkor a kisebb mint és nagyobb mint (vagy fordítva) mindig ugyan arra a Clustering kulcsra kell legyen felírva és csak az utolsó kulcsra.
 
 
:[[File:ClipCapIt-181015-221508.PNG]]
Helyes, mert az egyetlen egy particionáló kulcs szerepel, és az első Clustering kulcsra írtunk fel '<' feltétel.
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND col2 < 'Z'
 
:[[File:ClipCapIt-181015-221508.PNG]]
Helyes, pert benne van a particionáló kulcs, és mivel a col3 szerepel a WHERE-ben, a col2 is ott van, ahol csak az '=' restriction-t használtuk. Mivel a col3 az utolsó olyan Clustering kulcs, amit szerepel a WEHRE-ben, ezért ott használhattuk a '>' megkötést.
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND col2 = 'Z' AND col3 > 'A'
 
:[[File:ClipCapIt-181015-221508.PNG]]
Helyes, mert a tartományra lekérdezés a végén van, és a nagyobb mint-kisebb mint operációkban ugyan az az oszlop (Clustering kulcs) szerepel, ráadásul a legvégén, különben nem lenne helyes.
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND col2 = 'Z' AND col3 > 'A' AND col3 < 'Z'
A tartomány több Clustering kulcsra is vonatkozhat, még akkor is ha aszimmetrikus, tehát ez is helyes:
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND (col2 , col3) > ('A','A') AND col2 < 'Z'
 
 
 
:[[File:ClipCapIt-181015-221536.PNG]]
Helytelen, mert a col3 Clustering kulcs szerepel, de a col2 nem, pedig a kulcsok definiálásakor a col2 előbb volt mint a col3.
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND col3 > 'A'
 
:[[File:ClipCapIt-181015-221536.PNG]]
Helytelen, mert a col2-re nem az '=' operátort használjuk, pedig megadtuk a col2 utáni soron következő kulcsot is a col3-at. Mindig csak a legutolsó kulcs-ra használhatunk az '='-től eltérő operációt.
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND col2 > 'Z' AND col3 > 'A'
 
 
 
===IN megkötés===
Az IN megkötést mind Particionáló kulcsra mind Clustering kulcsra lehet alkalmazni a sorrendre való tekintet nélkül (ezt a 2.2-ben vezették be, azelőtt ezt is csak az utolsó kulcs-ra lehetett rárakni).
 
 
IN az utolsó előtti Clustering kulcsra:
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND col2 IN ('A', 'B') AND col3 = 'Z'
 
 
IN a particionáló kulcson:
SELECT * FROM adam.test1 WHERE col1 IN ('k1', 'k2') AND col2='A' AND col3 < 'Z';
 
 
Több oszlopos IN lekérdezés :
SELECT * FROM adam.test1 WHERE col1 = 'k1' AND (col2 , col3) IN (('A','A'), ('B','Z'));
 
===CONTAINS és CONTAINS KEY megkötések===
 
 
<br>
Láthatjuk, hogy az alaptáblában a TourId particionáló kulcs volt, az MW-ben viszont már csak Clustering key, ezzel teljesítettük a megkötést, hogy az alap tábla összes kulcsának szerepelnie kell az MW kulcsai között.
 
 
=Tervezés=
 
==Partíció mérete==
Egy partíció, vagyis azon cellák összessége, aminek ugyan az a particionáló kulcs csoportja, nem lehet nagyobb mint '''2 milliárd cella / partíció'''. Az egy partícióba eső cellák számát így lehet kiszámolni:
 
N<sub>v</sub> = N<sub>r</sub>*( N<sub>c</sub> − N<sub>pk</sub> − N<sub>s</sub>) + N<sub>s</sub>
 
* N<sub>v</sub> = össz cella szám, ezt akarjuk kiszámolni, ez nem lehet több mint 2 milliárd.
* N<sub>r</sub> = az összes sorok száma a partícióban
* N<sub>pk</sub> = primary kulcs oszlopok száma
* N<sub>c</sub> = az oszlopok száma a partícióban
* N<sub>s</sub> = statikus oszlopok
 
 
Mikor a tábla szerkezetet kitaláljuk, fontos, hogy előre megbecsüljük, hogy mi lesz az '''N<sub>v</sub>'''. Fontos hogy a legrosszabb esetet számoljuk ki beleszámolva a jövőbeli elképzelt növekedést is. Ha ez átlépné az 1 milliárdot, akkor be kell vezessünk újabb particionáló kulcsokat is.
 
Minél szélesebb egy tábla annál egyszerűbb ezt a korlátot elérni még relatíve kevés adattal is.
 
 
=Java kliens=
 
 
 
<source lang="java">
public class CassandraConnector {
 
private static CassandraConnector instance;
 
private Cluster cluster = null;
 
private Session session = null;
 
private MappingManager manager = null;
 
private CassandraConnector() {
initConnection();
}
 
public static CassandraConnector getInstance() {
 
if (instance == null) {
 
synchronized (CassandraConnector.class) {
if (instance == null) {
// if instance is null, initialize
instance = new CassandraConnector();
}
}
}
 
return instance;
}
 
private void initConnection() {
 
String host = System.getProperty("cassandra.default.host", "localhost");
String port = System.getProperty("cassandra.default.port", "9042");
String keyspace = System.getProperty("cassandra.default.keyspace");
String username = System.getProperty("cassandra.user", "user");
String password = System.getProperty("cassandra.pwd", "pass");
boolean withSSL = Boolean.parseBoolean(System.getProperty("cassandra.needSSL", "false"));
 
connect(nodes, withSSL, username, password, keyspace);
}
 
private void connect(String host, String port, boolean withSSL, String username, String password, String keyspace) {
 
try {
 
Cluster.Builder b = Cluster.builder().withSocketOptions((new SocketOptions()).setReadTimeoutMillis(1800000))
.withQueryOptions((new QueryOptions()).setFetchSize(100000));
 
 
builder.addContactPoint(host).withPort(port);
 
if (withSSL) {
b.withSSL();
}
 
if (username != null && username.trim().length() > 0 && withSSL) {
b.withCredentials(username, password);
}
 
session = cluster.connect();
 
manager = new MappingManager(session);
 
} catch (Exception e) {
System.out.println(e);
throw e;
}
}
 
 
public Session getSession() {
 
return session;
}
 
public MappingManager getManager() {
 
return manager;
}
 
 
 
@Override
public void finalize() {
try {
session.close();
cluster.close();
} catch (Exception e) {
// ...
}
}
}
</source>