U mom prethodnom postu možete pročitati što su KDB+ baza podataka i programski jezik Q, te koje su im snage i slabosti. i ukratko u uvodu. U ovom članku implementirat ćemo uslugu u Q-u koja će obrađivati dolazni tok podataka i izračunavati različite funkcije agregacije iz minute u minutu u "stvarnom vremenu" (tj. imat će vremena izračunati sve prije sljedeće serije podataka). Glavna značajka Q-a je da je vektorski jezik, što nam omogućuje rad ne na pojedinačnim objektima, već na nizovima njih, nizovima nizova i drugim složenim objektima. Jezici poput Q-a i srodnih jezika, K, J i APL-a, poznati su po svojoj kratkoći. Često se program koji bi zauzeo nekoliko ekrana koda u poznatom jeziku poput Jave može napisati u nekoliko redaka. Upravo to želim pokazati u ovom članku.

Uvod
KDB+ je stupčasta baza podataka dizajnirana za vrlo velike količine podataka organiziranih na specifičan način (prvenstveno po vremenu). Koristi se prvenstveno u financijskim institucijama poput banaka, investicijskih fondova i osiguravajućih društava. Q je interni jezik KDB+-a, koji omogućuje učinkovit rad s tim podacima. Filozofija Q-a je kratkoća i učinkovitost, žrtvujući jasnoću. To je zato što bi vektorski jezik bio teško razumjeti, dok kratkoća i bogatstvo omogućuju prikaz puno većeg dijela programa na jednom zaslonu, što ga u konačnici čini lakšim za razumijevanje.
U ovom članku implementirat ćemo potpuni program u Q-u, a možda biste ga i vi htjeli isprobati. Za to će vam trebati sam Q. Besplatnu 32-bitnu verziju možete preuzeti s kx web stranice – Tamo ćete, ako ste zainteresirani, pronaći i referentne informacije o Q-u, knjizi i razne članke na ovu temu.
Formuliranje problema
Postoji izvor koji šalje tablicu podataka svakih 25 milisekundi. Budući da se KDB+ prvenstveno koristi u financijama, pretpostavit ćemo da se radi o tablici trgovanja sa sljedećim stupcima: vrijeme (vrijeme u milisekundama), sym (simbol tvrtke na burzi – IBM, AAPL,…), cijena (cijena po kojoj su dionice kupljene) i veličina (veličina transakcije). Interval od 25 milisekundi odabran je proizvoljno; nije ni premalen ni prevelik. Njegova prisutnost znači da su podaci koji dolaze u uslugu već međuspremljeni. Bilo bi lako implementirati međuspremljenje na strani usluge, uključujući dinamičko međuspremljenje na temelju trenutnog opterećenja, ali radi jednostavnosti, držat ćemo se fiksnog intervala.
Usluga mora izračunati skup agregacijskih funkcija - maksimalnu cijenu, prosječnu cijenu, veličinu zbroja i druge korisne informacije - po minuti za svaki dolazni simbol iz stupca sym. Radi jednostavnosti, pretpostavljamo da se sve funkcije mogu izračunati inkrementalno, što znači da su za dobivanje nove vrijednosti dovoljna dva broja - stara vrijednost i dolazna vrijednost. Na primjer, funkcije max, average i sum imaju ovo svojstvo, ali funkcija medijana nema.
Također ćemo pretpostaviti da je dolazni tok podataka poredan po vremenu. To će nam omogućiti rad samo s najnovijom minutom. U praksi je dovoljno moći raditi s trenutnom i prethodnom minutom u slučaju da neka ažuriranja kasne. Radi jednostavnosti, ovaj slučaj nećemo razmatrati.
Agregacijske funkcije
U nastavku su navedene potrebne funkcije agregacije. Uključio sam ih što više kako bih povećao opterećenje usluge:
- visoka – maksimalna cijena – maksimalna cijena po minuti.
- niska – minimalna cijena – minimalna cijena po minuti.
- firstPrice – first price – prva cijena po minuti.
- lastPrice – zadnja cijena – zadnja cijena po minuti.
- firstSize – prva veličina – veličina prve transakcije po minuti.
- lastSize – zadnja veličina — veličina zadnje transakcije u minuti.
- numTrades – count i – broj trgovina u minuti.
- volumen – zbroj veličina – zbroj veličina transakcija po minuti.
- pvolume – zbroj cijena – zbroj cijena po minuti, potrebno za prosječnu cijenu.
- promet – zbroj cijena*veličina – ukupan volumen transakcija po minuti.
- prosječnaCijena – pvolume%numTrades – prosječna cijena po minuti.
- avgSize – volumen%numTrades – prosječna veličina trgovine po minuti.
- vwap – promet%volumen – prosječna cijena po minuti ponderirana veličinom trgovine.
- cumVolume – zbrojni volumen – akumulirani volumen transakcija tijekom cijelog razdoblja.
Odmah ćemo raspraviti jednu neočitu točku: kako inicijalizirati ove stupce prvi put i za svaku sljedeću minutu. Neki stupci, poput firstPrice, moraju se svaki put inicijalizirati na null; njihova vrijednost je nedefinirana. Drugi, poput volume, moraju uvijek biti postavljeni na 0. Postoje i stupci koji zahtijevaju kombinirani pristup - na primjer, cumVolume se mora kopirati iz prethodne minute i postaviti na 0 za prvu minutu. Definirat ćemo sve ove parametre pomoću tipa podataka rječnika (slično zapisu):
// list ! list – создать словарь, 0n – float null, 0N – long null, `sym – тип символ, `sym1`sym2 – список символов
initWith:`sym`time`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover`avgPrice`avgSize`vwap`cumVolume!(`;00:00;0n;0n;0n;0n;0N;0N;0;0;0.0;0.0;0n;0n;0n;0);
aggCols:reverse key[initWith] except `sym`time; // список всех вычисляемых колонок, reverse объяснен ниже
Dodao sam sym i time u rječnik radi praktičnosti. Sada je initWith gotov redak iz konačne agregirane tablice, gdje je još potrebno odrediti ispravan sym i vrijeme. Možete ga koristiti za dodavanje novih redaka u tablicu.
Trebat će nam aggCols prilikom stvaranja agregacijske funkcije. Popis treba biti invertiran zbog redoslijeda evaluacije izraza u Q (s desna na lijevo). Cilj je osigurati da evaluacija ide od high do cumVolume, budući da neki stupci ovise o prethodnim stupcima.
Stupci koje je potrebno kopirati u novu minutu iz prethodne, stupac sym dodan je radi praktičnosti:
rollColumns:`sym`cumVolume;
Sada podijelimo stupce u grupe na temelju načina na koji ih treba ažurirati. Mogu se razlikovati tri vrste:
- Akumulatori (volumen, promet,..) – moramo dodati ulaznu vrijednost prethodnoj.
- S posebnom točkom (visoka, niska, ..) - prva vrijednost u minuti uzima se iz dolaznih podataka, ostale se izračunavaju pomoću funkcije.
- Ostalo se uvijek izračunava pomoću funkcije.
Definirajmo varijable za ove klase:
accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;
Redoslijed izračuna
Agregatnu tablicu ažurirat ćemo u dva koraka. Radi učinkovitosti, prvo ćemo smanjiti ulaznu tablicu tako da sadrži jedan redak za svaki simbol i minutu. Činjenica da su sve naše funkcije inkrementalne i asocijativne jamči da se rezultat neće promijeniti ovim dodatnim korakom. Tablicu bismo mogli smanjiti pomoću naredbe select:
select high:max price, low:min price … by sym,time.minute from table
Ova metoda ima nedostatak: skup izračunatih stupaca je unaprijed definiran. Srećom, Q također implementira select kao funkciju koja prihvaća dinamički generirane argumente:
?[table;whereClause;byClause;selectClause]
Neću detaljno opisivati format argumenta; u našem slučaju, jedini netrivijalni izrazi su izrazi by i select, i oni moraju biti rječnici oblika columns!expressions. Stoga se funkcija kompresije može definirati na sljedeći način:
selExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover!parse each ("max price";"min price";"first price";"last price";"first size";"last size";"count i";"sum size";"sum price";"sum price*size"); // each это функция map в Q для одного списка
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];
Radi jasnoće, koristio sam funkciju parse koja pretvara niz koji sadrži Q izraz u vrijednost koja se može proslijediti funkciji eval i potrebna je u funkciji select. Također imajte na umu da je predproces definiran kao projekcija (tj. funkcija s djelomično definiranim argumentima) funkcije select; jedan argument (tablica) nedostaje. Ako primijenimo predproces na tablicu, dobivamo komprimiranu tablicu.
Drugi korak je ažuriranje agregirane tablice. Prvo napišimo algoritam u pseudokodu:
for each sym in inputTable
idx: row index in agg table for sym+currentTime;
aggTable[idx;`high]: aggTable[idx;`high] | inputTable[sym;`high];
aggTable[idx;`volume]: aggTable[idx;`volume] + inputTable[sym;`volume];
…
U Q-u je uobičajeno koristiti funkcije map/reduce umjesto petlji. No budući da je Q vektorski jezik i možemo sigurno primijeniti sve operacije na sve simbole odjednom, možemo, kao prvu aproksimaciju, u potpunosti eliminirati petlju izvođenjem operacija na svim simbolima odjednom:
idx:calcIdx inputTable;
row:aggTable idx;
aggTable[idx;`high]: row[`high] | inputTable`high;
aggTable[idx;`volume]: row[`volume] + inputTable`volume;
…
Ali možemo ići i dalje. Q ima jedinstven i iznimno moćan operator - generalizirani operator dodjeljivanja. Omogućuje vam modificiranje skupa vrijednosti u složenoj strukturi podataka pomoću popisa indeksa, funkcija i argumenata. U našem slučaju to izgleda ovako:
idx:calcIdx inputTable;
rows:aggTable idx;
// .[target;(idx0;idx1;..);function;argument] ~ target[idx 0;idx 1;…]: function[target[idx 0;idx 1;…];argument], в нашем случае функция – это присваивание
.[aggTable;(idx;aggCols);:;flip (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];
Nažalost, dodjeljivanje tablici zahtijeva popis redaka, a ne stupaca, te zahtijeva transponiranje matrice (popisa stupaca u popis redaka) pomoću funkcije flip. Za veliku tablicu to je skupo, pa umjesto toga primjenjujemo generalizirano dodjeljivanje svakom stupcu pojedinačno pomoću funkcije map (koja izgleda kao apostrof):
.[aggTable;;:;]'[(idx;)each aggCols; (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];
Ponovno koristimo projekciju funkcija. Također imajte na umu da je u Q-u stvaranje popisa također funkcija i možemo ga pozvati pomoću each(map) za dobivanje popisa popisa.
Kako bismo izbjegli fiksni skup izračunatih stupaca, dinamički ćemo stvoriti gornji izraz. Prvo definiramo funkcije za izračun svakog stupca, koristeći varijable row i inp za referenciranje agregiranih i ulaznih podataka:
aggExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`avgPrice`avgSize`vwap`cumVolume!
("row[`high]|inp`high";"row[`low]&inp`low";"row`firstPrice";"inp`lastPrice";"row`firstSize";"inp`lastSize";"pvolume%numTrades";"volume%numTrades";"turnover%volume";"row[`cumVolume]+inp`volume");
Neki su stupci posebni; njihova prva vrijednost ne bi trebala biti izračunata funkcijom. Možemo odrediti da je prvi prema stupcu ret[`numTrades] - ako je 0, tada je vrijednost prva. Q ima funkciju odabira - ?[Boolean list;list1;list2] - koja odabire vrijednost s liste 1 ili 2 ovisno o uvjetu u prvom argumentu:
// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - тоже обобщенное присваивание для случая когда индекс неглубокий
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];
Ovdje sam pozvao generičku dodjelu s mojom funkcijom (izraz u vitičastim zagradama). Proslijedila je trenutnu vrijednost (prvi argument) i dodatni argument, koji prosljeđujem u četvrtom parametru.
Dodajmo zvučnike na baterije zasebno, budući da imaju istu funkciju:
// volume -> row[`volume]+inp`volume
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
Ovo je tipično dodjeljivanje prema Q standardima, ali ja dodjeljujem popis vrijednosti odjednom. Na kraju, kreirajmo glavnu funkciju:
// ":",/:aggExprs ~ map[{":",x};aggExpr] => ":row[`high]|inp`high" присвоим вычисленное значение переменной, потому что некоторые колонки зависят от уже вычисленных значений
// string[cols],'exprs ~ map[,;string[cols];exprs] => "high:row[`high]|inp`high" завершим создание присваивания. ,’ расшифровывается как map[concat]
// ";" sv exprs – String from Vector (sv), соединяет список строк вставляя “;” посредине
updateAgg:value "{[aggTable;idx;inp] row:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols;(",(";"sv string[aggCols],'":",/:aggExpression aggCols),")]}";
S ovim izrazom dinamički stvaram funkciju iz niza znakova koji sadrži izraz koji sam gore naveo. Rezultat će izgledati ovako:
{[aggTable;idx;inp] rows:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols ;(cumVolume:row[`cumVolume]+inp`cumVolume;… ; high:?[isFirst;inp`high;row[`high]|inp`high])]}
Redoslijed evaluacije stupaca je obrnut, budući da je u Q redoslijed evaluacije s desna na lijevo.
Sada imamo dvije glavne funkcije potrebne za računalstvo, sve što je preostalo je dodati malo infrastrukture i usluga je spremna.
Završni koraci
Imamo funkcije preprocess i updateAgg koje obavljaju sav posao. Ali još uvijek moramo osigurati odgovarajuće prijelaze između minuta i izračunati indekse za agregaciju. Prvo, definirajmo funkciju init:
init:{
tradeAgg:: 0#enlist[initWith]; // создаем пустую типизированную таблицу, enlist превращает словарь в таблицу, а 0# означает взять 0 элементов из нее
currTime::00:00; // начнем с 0, :: означает, что присваивание в глобальную переменную
currSyms::`u#`symbol$(); // `u# - превращает список в дерево, для ускорения поиска элементов
offset::0; // индекс в tradeAgg, где начинается текущая минута
rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg; // кэш для последних значений roll колонок, таблица с ключом sym
}
Također ćemo definirati funkciju rotacije koja će promijeniti trenutnu minutu:
roll:{[tm]
if[currTime>tm; :init[]]; // если перевалили за полночь, то просто вызовем init
rollCache,::offset _ rollColumns#tradeAgg; // обновим кэш – взять roll колонки из aggTable, обрезать, вставить в rollCache
offset::count tradeAgg;
currSyms::`u#`$();
}
Trebat će nam funkcija za dodavanje novih znakova:
addSyms:{[syms]
currSyms,::syms; // добавим в список известных
// добавим в таблицу sym, time и rollColumns воспользовавшись обобщенным присваиванием.
// Функция ^ подставляет значения по умолчанию для roll колонок, если символа нет в кэше. value flip table возвращает список колонок в таблице.
`tradeAgg upsert @[count[syms]#enlist initWith;`sym`time,cols rc;:;(syms;currTime), (initWith cols rc)^value flip rc:rollCache ([] sym: syms)];
}
I na kraju, funkcija upd (tradicionalni naziv ove funkcije za Q servise), koju klijent poziva za dodavanje podataka:
upd:{[tblName;data] // tblName нам не нужно, но обычно сервис обрабатывает несколько таблиц
tm:exec distinct time from data:() xkey preprocess data; // preprocess & calc time
updMinute[data] each tm; // добавим данные для каждой минуты
};
updMinute:{[data;tm]
if[tm<>currTime; roll tm; currTime::tm]; // поменяем минуту, если необходимо
data:select from data where time=tm; // фильтрация
if[count msyms:syms where not (syms:data`sym)in currSyms; addSyms msyms]; // новые символы
updateAgg[`tradeAgg;offset+currSyms?syms;data]; // обновим агрегированную таблицу. Функция ? ищет индекс элементов списка справа в списке слева.
};
To je to. Evo cijelog koda za našu uslugu, kao što je obećano, samo nekoliko redaka:
initWith:`sym`time`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover`avgPrice`avgSize`vwap`cumVolume!(`;00:00;0n;0n;0n;0n;0N;0N;0;0;0.0;0.0;0n;0n;0n;0);
aggCols:reverse key[initWith] except `sym`time;
rollColumns:`sym`cumVolume;
accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;
selExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover!parse each ("max price";"min price";"first price";"last price";"first size";"last size";"count i";"sum size";"sum price";"sum price*size");
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];
aggExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`avgPrice`avgSize`vwap`cumVolume!("row[`high]|inp`high";"row[`low]&inp`low";"row`firstPrice";"inp`lastPrice";"row`firstSize";"inp`lastSize";"pvolume%numTrades";"volume%numTrades";"turnover%volume";"row[`cumVolume]+inp`volume");
@[`aggExpression;specialCols;{"?[isFirst;inp`",y,";",x,"]"};string specialCols];
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
updateAgg:value "{[aggTable;idx;inp] row:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols;(",(";"sv string[aggCols],'":",/:aggExpression aggCols),")]}"; / '
init:{
tradeAgg::0#enlist[initWith];
currTime::00:00;
currSyms::`u#`symbol$();
offset::0;
rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg;
};
roll:{[tm]
if[currTime>tm; :init[]];
rollCache,::offset _ rollColumns#tradeAgg;
offset::count tradeAgg;
currSyms::`u#`$();
};
addSyms:{[syms]
currSyms,::syms;
`tradeAgg upsert @[count[syms]#enlist initWith;`sym`time,cols rc;:;(syms;currTime),(initWith cols rc)^value flip rc:rollCache ([] sym: syms)];
};
upd:{[tblName;data] updMinute[data] each exec distinct time from data:() xkey preprocess data};
updMinute:{[data;tm]
if[tm<>currTime; roll tm; currTime::tm];
data:select from data where time=tm;
if[count msyms:syms where not (syms:data`sym)in currSyms; addSyms msyms];
updateAgg[`tradeAgg;offset+currSyms?syms;data];
};
Testiranje
Testirajmo performanse servisa. Da biste to učinili, pokrenite ga u zasebnom procesu (smjestite kod u datoteku service.q) i pozovite funkciju init:
q service.q –p 5566
q)init[]
U drugoj konzoli pokrenite drugi Q proces i spojite se na prvi:
h:hopen `:host:5566
h:hopen 5566 // если оба на одном хосте
Prvo, stvorimo popis znakova - njih 10 000 - i dodajmo funkciju za generiranje slučajne tablice. U drugoj konzoli:
syms:`IBM`AAPL`GOOG,-9997?`8
rnd:{[n;t] ([] sym:n?syms; time:t+asc n#til 25; price:n?10f; size:n?10)}
Dodao sam tri stvarna simbola na popis kako bi ih bilo lakše pretraživati u tablici. Funkcija rnd stvara slučajnu tablicu s n redaka, gdje se vremena kreću od t do t+25 milisekundi.
Sada možete pokušati poslati podatke usluzi (dodajmo prvih deset sati):
{h (`upd;`trade;rnd[10000;x])} each `time$00:00 + til 60*10
U servisu možete provjeriti je li tablica ažurirana:
c 25 200
select from tradeAgg where sym=`AAPL
-20#select from tradeAgg where sym=`AAPL
Rezultat:
sym|time|high|low|firstPrice|lastPrice|firstSize|lastSize|numTrades|volume|pvolume|turnover|avgPrice|avgSize|vwap|cumVolume
--|--|--|--|--|--------------------------------
AAPL|09:27|9.258904|9.258904|9.258904|9.258904|8|8|1|8|9.258904|74.07123|9.258904|8|9.258904|2888
AAPL|09:28|9.068162|9.068162|9.068162|9.068162|7|7|1|7|9.068162|63.47713|9.068162|7|9.068162|2895
AAPL|09:31|4.680449|0.2011121|1.620827|0.2011121|1|5|4|14|9.569556|36.84342|2.392389|3.5|2.631673|2909
AAPL|09:33|2.812535|2.812535|2.812535|2.812535|6|6|1|6|2.812535|16.87521|2.812535|6|2.812535|2915
AAPL|09:34|5.099025|5.099025|5.099025|5.099025|4|4|1|4|5.099025|20.3961|5.099025|4|5.099025|2919Sada pokrenimo test opterećenja kako bismo utvrdili koliko podataka usluga može obraditi u minuti. Podsjećamo, interval ažuriranja postavili smo na 25 milisekundi. Stoga bi se usluga (u prosjeku) trebala ažurirati unutar najmanje 20 milisekundi kako bi korisnici imali vremena zatražiti podatke. U drugom procesu unesite sljedeće:
tm:10:00:00.000
stressTest:{[n] 1 string[tm]," "; times,::h ({st:.z.T; upd[`trade;x]; .z.T-st};rnd[n;tm]); tm+:25}
start:{[n] times::(); do[4800;stressTest[n]]; -1 " "; `min`avg`med`max!(min times;avg times;med times;max times)}
4800 je dvije minute. Možete pokušati prvo pokrenuti za 1000 redaka svakih 25 milisekundi:
start 1000
U mom slučaju, rezultat je oko nekoliko milisekundi po ažuriranju. Stoga ću odmah povećati broj redaka na 10 000:
start 10000
Rezultat:
min| 00:00:00.004
avg| 9.191458
med| 9f
max| 00:00:00.030
Opet, ništa posebno, ali to je 24 milijuna redaka u minuti, 400 000 u sekundi. Ažuriranje se usporilo samo pet puta za više od 25 milisekundi, očito zbog promjene minuta. Povećajmo to na 100 000:
start 100000
Rezultat:
min| 00:00:00.013
avg| 25.11083
med| 24f
max| 00:00:00.108
q)sum times
00:02:00.532
Kao što vidimo, usluga se jedva nosi sa situacijom, ali ipak uspijeva održati na površini. Ova količina podataka (240 milijuna redaka u minuti) izuzetno je velika; u takvim slučajevima uobičajeno je pokrenuti nekoliko klonova (ili čak desetaka) usluge, od kojih svaki obrađuje samo podskup znakova. Ipak, rezultat je impresivan za interpretirani jezik koji je prvenstveno usmjeren na pohranu podataka.
Moglo bi se pitati zašto vrijeme nelinearno raste s veličinom svakog ažuriranja. Razlog je taj što je funkcija kompresije u biti C funkcija, koja je puno učinkovitija od updateAgg. Počevši od određene veličine ažuriranja (oko 10 000), updateAgg doseže svoj maksimum, a nakon toga, vrijeme izvršavanja neovisno je o veličini ažuriranja. Upravo zbog Q predkoraka usluga je u stanju obraditi takve količine podataka. To naglašava važnost odabira pravog algoritma pri radu s velikim podacima. Drugo razmatranje je pravilna pohrana podataka u memoriju. Ako se podaci ne pohranjuju stupčasto ili vremenski poredani, naišli bismo na nešto što se naziva promašaj TLB predmemorije - neuspjeh u pronalaženju adrese memorijske stranice u adresnoj predmemoriji procesora. Pretrage adresa traju otprilike 30 puta dulje ako su neuspješne, a u slučaju raspršenih podataka to može nekoliko puta usporiti uslugu.
Zaključak
U ovom sam članku pokazao da su KDB+ i Q prikladni ne samo za pohranu velikih skupova podataka i jednostavan pristup njima putem select naredbi, već i za stvaranje usluga obrade podataka sposobnih za obradu stotina milijuna redaka/gigabajta podataka čak i u jednom Q procesu. Sam Q jezik omogućuje iznimno konciznu i učinkovitu implementaciju algoritama za obradu podataka zbog svoje vektorske prirode, ugrađenog SQL interpretera i vrlo uspješnog skupa bibliotečkih funkcija.
Želio bih istaknuti da je gore navedeno samo uzorak Q-ovih mogućnosti; ima i druge jedinstvene značajke. Na primjer, izuzetno jednostavan IPC protokol koji briše granice između pojedinačnih Q procesa i omogućuje povezivanje stotina tih procesa u jednu mrežu, koja može obuhvaćati desetke poslužitelja diljem svijeta.
Izvor: www.habr.com
