7,540
edits
Changes
→Partíció mérete
* '''Sorting is a design decision''': A sorrendezést a tábla létrehozásakor definiálni kell, a clustering oszlop fogja meghatározni, ezen később módosítani nem lehet. Tehát ez is a query megtervezésekor meghatározandó.
<br>
=Cassandra bemutatása=
* '''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>
:[[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 (
</pre>
</pre>
=LekérdezésekCQL (Cassandra Query Language) alapok=
https://www.datastax.com/2012/01/getting-started-with-cassandra
==Keyspace és clusterinfo==
<pre>
cqlsh:adam> DESCRIBE KEYSPACE adam;
==Időbélyegek==A timestamp és a TTL is egy mezőre vonatkozik, nem az egész sorra. Tehát minden egyes column pair-nek van egy TTL és egy timestamp metadata értéke is. <pre>cqlsh:adam> SELECT first_name, last_name, ... writetime(last_name) FROM user; first_name | last_name | writetime(last_name)------------+-----------+---------------------- Mary | Rodriguez | 1538771050876617 Bill | Nguyen | 1538771031333072</pre> Ha a TTL lejár, NULL-ra fogja állítani a mező értékét. <pre>UPDATE user USING TTL 3600 SET last_name ='McDonald' WHERE first_name = 'Mary' ;</pre><pre>cqlsh:adam> SELECT first_name, last_name, TTL(last_name) ... FROM user WHERE first_name = 'Mary'; first_name | last_name | ttl(last_name)------------+-----------+---------------- Mary | Rodriguez | 3588</pre> ==AdatokAlap query==
Adatbázis létrehozása:
2 | eng | fred | smith
</pre>
==Listák és Map-ek==
===Sets===
<br>
=Kulcsok és indexek lekérdezésekben=
==Kulcs használati alapelvek==
* Ugyan 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.
* A kulcsok nem kell hogy egyediek legyenek. Simán beszúrhatunk olyan sorokat, amikben a particionáló kulcsok azonosak, csak a clustering kulcsban különböznek. (Ha a clustering kulcs is egyezik, akkor nem új sort fog beszúrni, hanem a régit felülírja, ugyanis a particionáló kulcsok kijelölik a node-ot, és a node-on a particionáló + a clustering kulcsok jelölik ki a sort). Ha sok az olyan sor, ahol megegyezik a particionáló kulcsok értéke azzal az a baj, hogy úgynevezett 'hotspot'-okat hozunk létre, ami azt jelenti, hogy egy node-on őszpontosul az összes partíció (sor) mivel a partition key határozza meg a node-ot. Tehát olyan particionáló kulcsokat érdemes választani, amikben kevés az egyezés. <br>
Példa: Adott a következő tábla:
<pre>
CREATE TABLE users (
col1 int, col2 int, col3 int, col4 int,
PRIMARY KEY ((col1, col2), col3) );
</pre>
Itt particionáló kulcsok a col1 és col2, és clustering kulcs a col3.
Ekkor az alábbi két beszúrás két külön sort fog eredményezni, de ugyanarra a node-ra fognak kerülni:
<pre>
</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.
Ehhez ha akarjuk hozzáadhatjuk még a Clustering kulcsokat szűrési feltételnek, de nem kulcs mezőt nem adhatunk hozzá. <br>Tehát ez helyes: CREATE INDEX ON SELECT * FROM user ( emails );WHERE col1='aaa' AND col2='ddd' AND col3='ccc'De ez helytelen, mivel col4 nem kulcs mező. SELECT * FROM user WHERE col1='aaa' AND col2='ddd' AND '''col4='ccc''''
* A Materialized view-ban az összes eredetileg particionáló kulcsot átmozgathatjuk Clustering kulccsá, és egy addig nem kucsmezöből készíthetünk particionáló kulcsot. Viszont fontos, hogy az összes korábbi kulcs mező kulcs maradjon. Az új particionáló kulcs, ami korábban nem volt kulcs mező nem kell hogy egyedi értékeket tartalmazzon. Viszont ha nem elég nagy a kardinalitása, akkor a materializált view-ból is 'hotspot' fog kialakulni mivel a materializált view is egy Cassandra tábla, így fontos, hogy az új particionáló kulcs is jól szétossza a node-ok között a tárolást. Mivel ha akarjuk, az összes base tábla particionáló kulcsából Clustering kulcsot csinálhatunk, a materializált view-ban tetszőleges korábban nem kulcs szerint tudunk keresni. <br>
Fontos, hogy az összes kulcs elemre az MW select-ben NOT NULL kikötést kell tenni, hogy biztosítsuk a sorok egyediségét. <br>
Tehát ez helyes:
<pre>
CREATE MATERIALIZED VIEW huser_mw AS
SELECT *
FROM user
WHERE col1 IS NOT NULL AND col2 IS NOT NULL AND col3 IS NOT NULL
PRIMARY KEY ((col4), col1, co2, col3);
</pre>
A korábbi col1 és col2 particionáló kulcsból Clustering kulcsot csináltunk, és az új view-ban tudunk col4 szerint keresni.
==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.
<pre>
CREATE CUSTOM TABLE adam.test1 (col1 text,col2 text,col3 text,col4 text,PRIMARY KEY ((col1), col2, col3)); CREATE INDEX user_last_name_sasi_idx indexOnCol4 ON user adam.test1 (last_namecol4)USING 'org.apache.cassandra.index.sasi.SASIIndex';
</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>
=Adatbázis GUI=
===DevCenter===
A datastax (a Cassandra gyártója) biztosít egy ingyenes GUI-t ami remek segítség az adatbázis karbantartásában:
Install: https://docs.datastax.com/en/developer/devcenter/doc/devcenter/dcInstallation.html
:[[File:ClipCapIt-181013-142901.PNG]]
<br>
===RazorSQL===
Sokféle grafikus eszközzel csatlakozhatunk a Cassandra adatbázishoz. A legtöbben a RazorSQL-t ajánlották, ami fizetős: https://razorsql.com/
:[[File:ClipCapIt-180930-154548.PNG]]
=Adat model=
==Conceptual Data Modeling==
Az adatmodell megtervezését az alábbi példán keresztül fogjuk bemutatni. Ez egy leegyszerűsített modellje egy utazásközvetítő weboldalnak ami összegyűjti a különböző utazási irodák ajánlatait, amik több utazást is kínálnak, és az utasokat egy utazáson belül is több szállodában szállásolják el.
:[[File:ClipCapIt-181007181010-164913232642.PNG]]
Az RDBMS modellt úgy gyártanánk el ebből, hogy minden téglalapból csinálnánk egy táblát, majd létrehoznánk idegen kulcsokat a vonalak mentén a számosságot figyelembe véve.
Itt azonban query-first megközelítést kell alkalmazni. Első lépésben a képernyő tervek és elvárt funkciók alapján be kell azonosítani a lekérdezéseket, amiket QX-el szokás jelölni, ahol az X egy egész szám.
==Defining Application Queries==* Q1. Utazások megkeresése egy adott városhoz közelében
* Q2. Utazás részleteinek listázása
* Q3. Programok megkeresése egy város közelében
* Q4. Utazások megkeresése programok alapján
* Q5. Elérhető időpontok keresése egy utazáshoz
* Q6. Foglalás kikeresése foglalási szám Utasok listája utazás alapján
* Q7. Foglalás kikeresése hotel, dátum és utas neve alapján
* Q8. Foglalás kikeresése utas neve alapján
* Q9. Utas Foglalás részletek
Ha megvannak a lekérdezések, akkor a lekérdezésekből egy folyamat ábrát kell rajzolni, hogy megtudjuk hogy melyik lekérdezés eredménye szolgálhat input-ként egy másik lekérdezésnek:
:[[File:ClipCapIt-181007181010-172941232613.PNG]] ==Logikai adatmodell==Most hogy beazonosítottuk a szükséges lekérdezéseket, megtervezhetjük a táblákat. A táblák nem mások, mint a fenti flow diagramban a dobozok, vagyis a keresés eredmények, amik a denormalizált adatstruktúra miatt megfelel a táblának. A tábla neve mindig a fő entitással kezdődik, amit a lekérdezés visszaad, és _by_ szó használatával hozzá kell kapcsolni azokat másodlagos entitásokat, amik mentén lekérdezzük a fő entitást. Pl a Q1-re egy lehetséges elnevezés: '''tours_by_city''' Ha a lekérdezés neve több szóból áll, akkor azokat is "_" al kell elválasztani, pl: '''available_dates_by_tour''' ===Chebotko logical diagram===A legelterjedtebb diagram a táblák modellezésére az úgynevezett Choebotka diagram, ami ugyan dobozokból fog állni, mint a query flow diagramunk, de a dobozok belsejében ki lesznek fejtve az adott tábla oszlopai is. Jelmagyarázat: * '''K''': partitioning key az adott oszlop* '''C↑''': clustering key ASC rendezéssel* '''C↓''': clustering key DESC rendezéssel Ha egy táblába bele mutat egy nyíl akkor az egy olyan lekérdezés, amit az adott tábla támogat. Ha két tábla között van nyíl, akkor az egyik a másiknak az úgynevezett downstream query- je. Íme az utazási iroda teljes adatbázis modellje, azaz Chebotka diagramja: :[[File:ClipCapIt-181011-220118.PNG]] Láthatjuk, hogy sem a hotelnek, sem a customer-eknek nincs saját táblája, ami elkerülhetetlen lett volna RDBMS-ben, itt viszont mivel egyik query sem azonosított ilyen igényt, ezért nem is készült ilyen tábla (query first megközelítés) Ami még fontos, hogy a Q1-hez és a Q2 lekérdezésekhez tartozó táblákban a particionáló kulcs szöveges, ez az amit a felhasználó megad a felületen. A többi lekérdezésnek már van upstream lekérdezése, ahonnan kipottyannak számára a megfelelő ID-k.{{note|A Chebotka logikai diagramon még nincsenek adattípusok meghatározva, az ábrán egy adott mező (pl customerName) lehet hogy később egy user defined adat típussal lesz megvalósítva (pl. First Name, Last Name) }} ==Fizikai adatmodell==A Fizikai Chebotka diagramon már pontosan meghatározzuk az egyes mező típusokat, akár összetett, user defined típusokat is létrehozhatunk. Jelölése az alábbi: :[[File:ClipCapIt-181011-215422.PNG|500px]] A fenti felsorolásban láthatjuk hogy '''*név*''' formátumban kell a User Defined Type -ra hivatkozni. A UDT fizikai modelljében is két csillag közé kell rakni a nevet: :[[File:ClipCapIt-181011-221836.PNG]]A UDT-nak nincsenek kulcsai, mert nem önálló táblák. Ezen a diagramon már nem szerepelnek a lekérdezések, pusztán a tábla szerkezetek. A tábla model tetején be kell jelölni, hogy az adott tábla melyik keyspace-be fog kerülni. Mi két keyspace-t hoztunk létre, egyet az utazásoknak, egyet pedig a foglalásoknak: :[[File:ClipCapIt-181011-221228.PNG]] ===Materialized views===A fizikai modellben az MW neveit dőlt betűkkel jelöljük, és egy szaggatott vonalú nyíllal kötjük össze a base táblával a Chebotka modellben::[[File:ClipCapIt-181011-230249.PNG]] 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>