Lastnosti jezika Q in KDB+ na primeru storitve v realnem času

Kaj so osnova KDB+, programski jezik Q, kakšne so njihove prednosti in slabosti, si lahko preberete v mojih prejšnjih članek in na kratko v uvodu. V članku bomo implementirali storitev na Q, ki bo obdelala dohodni podatkovni tok in izračunala različne agregacijske funkcije vsako minuto v načinu »realnem času« (t.j. imela bo čas izračunati vse pred naslednjim delom podatkov). Glavna značilnost Q je, da je vektorski jezik, ki vam omogoča, da ne delujete z posameznimi predmeti, temveč z njihovimi nizi, nizi nizov in drugimi kompleksnimi objekti. Jeziki, kot so Q in njegovi sorodniki K, J, APL, slovijo po svoji kratkosti. Program, ki zavzame več zaslonov kode v znanem jeziku, kot je Java, je pogosto mogoče zapisati v nekaj vrsticah. To želim prikazati v tem članku.

Lastnosti jezika Q in KDB+ na primeru storitve v realnem času

Predstavitev

KDB+ je stolpična zbirka podatkov, osredotočena na zelo velike količine podatkov, razvrščenih na določen način (predvsem po času). Uporablja se predvsem v finančnih institucijah – bankah, investicijskih skladih, zavarovalnicah. Jezik Q je notranji jezik KDB+, ki vam omogoča učinkovito delo s temi podatki. Ideologija Q je kratkost in učinkovitost, medtem ko je jasnost žrtvovana. To utemeljujejo s tem, da bo vektorski jezik v vsakem primeru težko razumljiv, kratkost in bogatost posnetka pa omogočata, da na enem zaslonu vidite veliko večji del programa, kar na koncu olajša razumevanje.

V tem članku implementiramo popoln program v Q in morda ga boste želeli preizkusiti. Če želite to narediti, boste potrebovali dejanski Q. Brezplačno 32-bitno različico lahko prenesete s spletne strani podjetja kx – www.kx.com. Tam boste, če vas zanima, našli referenčne informacije o knjigi Q Q Za smrtnike in različne članke na to temo.

Izjava o težavah

Obstaja vir, ki pošilja tabelo s podatki vsakih 25 milisekund. Ker se KDB+ uporablja predvsem v financah, bomo predpostavili, da je to tabela transakcij (poslov), ki ima naslednje stolpce: čas (čas v milisekundah), sym (oznaka podjetja na borzi - IBM, AAPL,…), cena (cena, po kateri so bile delnice kupljene), velikost (velikost posla). Interval 25 milisekund je poljuben, ne premajhen in ne predolg. Njegova prisotnost pomeni, da podatki pridejo v storitev že v medpomnilniku. Enostavno bi bilo implementirati medpomnjenje na strani storitve, vključno z dinamičnim medpomnjenjem glede na trenutno obremenitev, vendar se bomo zaradi enostavnosti osredotočili na fiksni interval.

Storitev mora šteti vsako minuto za vsak dohodni simbol iz stolpca sym nabor agregacijskih funkcij – največja cena, povprečna cena, velikost vsote itd. koristne informacije. Zaradi poenostavitve bomo predpostavili, da je vse funkcije mogoče izračunati postopoma, tj. za pridobitev nove vrednosti je dovolj poznati dve številki - staro in dohodno vrednost. Na primer, funkcije max, povprečje, vsota imajo to lastnost, funkcija mediana pa ne.

Predpostavili bomo tudi, da je dohodni tok podatkov časovno urejen. To nam bo dalo priložnost, da delamo le z zadnjim trenutkom. V praksi je dovolj, da lahko delate s trenutnimi in prejšnjimi minutami, če nekatere posodobitve zamujajo. Zaradi poenostavitve tega primera ne bomo obravnavali.

Funkcije združevanja

Zahtevane funkcije združevanja so navedene spodaj. Vzel sem jih čim več, da bi povečal obremenitev storitve:

  • high – max price – najvišja cena na minuto.
  • low – min price – najnižja cena na minuto.
  • firstPrice – prva cena – prva cena na minuto.
  • lastPrice – zadnja cena – zadnja cena na minuto.
  • firstSize – prva velikost – prva velikost posla na minuto.
  • lastSize – zadnja velikost – zadnja velikost trgovanja v minuti.
  • numTrades – count i – število poslov na minuto.
  • obseg – velikost vsote – vsota velikosti trgovanja na minuto.
  • pvolume – vsota cena – vsota cen na minuto, zahtevana za avgPrice.
  • – vsota prometa cena*velikost – skupni obseg transakcij na minuto.
  • avgPrice – pvolume%numTrades – povprečna cena na minuto.
  • avgSize – volume%numTrades – povprečna velikost posla na minuto.
  • vwap – promet% obseg – povprečna cena na minuto, ponderirana glede na velikost transakcije.
  • cumVolume – vsota obsega – akumulirana velikost transakcij v celotnem času.

Takoj se pogovorimo o eni neočitni točki - kako inicializirati te stolpce prvič in za vsako naslednjo minuto. Nekatere stolpce tipa firstPrice je treba vsakič inicializirati na nič; njihova vrednost je nedefinirana. Druge vrste glasnosti morajo biti vedno nastavljene na 0. Obstajajo tudi stolpci, ki zahtevajo kombiniran pristop - na primer, cumVolume je treba kopirati iz prejšnje minute in za prvo nastavljeno na 0. Nastavimo vse te parametre z uporabo podatkov iz slovarja vrsta (analogno 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 объяснен ниже

Zaradi udobja sem v slovar dodal sym in čas, zdaj je initWith že pripravljena vrstica iz končne združene tabele, kjer je treba nastaviti pravilen sym in čas. Uporabite ga lahko za dodajanje novih vrstic v tabelo.

Pri ustvarjanju funkcije združevanja bomo potrebovali aggCols. Seznam mora biti obrnjen zaradi vrstnega reda, v katerem so ovrednoteni izrazi v Q (od desne proti levi). Cilj je zagotoviti, da gre izračun od visoke do cumVolume, saj so nekateri stolpci odvisni od prejšnjih.

Stolpci, ki jih je treba kopirati v novo minuto iz prejšnjega, je za udobje dodan stolpec sym:

rollColumns:`sym`cumVolume;

Zdaj pa razdelimo stolpce v skupine glede na to, kako naj se posodobijo. Ločimo lahko tri vrste:

  1. Akumulatorji (volumen, promet,..) – vhodno vrednost moramo prišteti prejšnji.
  2. S posebno točko (visoka, nizka,..) – prva vrednost v minuti se vzame iz vhodnih podatkov, ostale se izračunajo s funkcijo.
  3. Počitek. Vedno se izračuna s funkcijo.

Definirajmo spremenljivke za te razrede:

accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;

Vrstni red izračuna

Zbirno tabelo bomo posodabljali v dveh fazah. Zaradi učinkovitosti najprej skrčimo dohodno tabelo, tako da je za vsak znak in minuto samo ena vrstica. Dejstvo, da so vse naše funkcije inkrementalne in asociativne, zagotavlja, da se rezultat tega dodatnega koraka ne bo spremenil. Tabelo lahko skrčite z izbiro:

select high:max price, low:min price … by sym,time.minute from table

Ta metoda ima slabost - nabor izračunanih stolpcev je vnaprej določen. Na srečo je v Q, select implementiran tudi kot funkcija, kjer lahko nadomestite dinamično ustvarjene argumente:

?[table;whereClause;byClause;selectClause]

Ne bom podrobno opisal formata argumentov; v našem primeru bosta samo by in select izraza netrivialna in bi morala biti slovarja obrazca columns!expressions. Tako lahko funkcijo krčenja definiramo na naslednji 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];

Zaradi jasnosti sem uporabil funkcijo razčlenjevanja, ki pretvori niz z izrazom Q v vrednost, ki jo je mogoče posredovati funkciji eval in ki je zahtevana v funkciji select. Upoštevajte tudi, da je predproces opredeljen kot projekcija (tj. funkcija z delno definiranimi argumenti) izbrane funkcije, en argument (tabela) manjka. Če za tabelo uporabimo predproces, bomo dobili stisnjeno tabelo.

Druga faza je posodobitev zbirne tabele. Najprej zapišimo algoritem v psevdokodi:

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];
  …

V Q je običajna uporaba funkcij preslikave/zmanjšanja namesto zank. Toda ker je Q vektorski jezik in lahko enostavno uporabimo vse operacije za vse simbole hkrati, potem lahko v prvem približku sploh storimo brez zanke in izvajamo operacije za vse simbole hkrati:

idx:calcIdx inputTable;
row:aggTable idx;
aggTable[idx;`high]: row[`high] | inputTable`high;
aggTable[idx;`volume]: row[`volume] + inputTable`volume;
…

Lahko pa gremo še dlje, Q ima edinstven in izjemno zmogljiv operator – generalizirani operator dodeljevanja. Omogoča vam spreminjanje niza vrednosti v kompleksni podatkovni strukturi z uporabo seznama indeksov, funkcij in argumentov. V našem primeru je videti takole:

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;…)];

Za dodelitev tabeli na žalost potrebujete seznam vrstic, ne stolpcev, in morate prenesti matriko (seznam stolpcev na seznam vrstic) s funkcijo obračanja. To je drago za veliko tabelo, zato uporabimo posplošeno dodelitev za vsak stolpec posebej z uporabo funkcije zemljevida (ki je videti kot apostrof):

.[aggTable;;:;]'[(idx;)each aggCols; (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];

Ponovno uporabimo projekcijo funkcij. Upoštevajte tudi, da je v Q ustvarjanje seznama tudi funkcija in jo lahko pokličemo s funkcijo each(map), da dobimo seznam seznamov.

Da zagotovimo, da niz izračunanih stolpcev ni fiksen, bomo zgornji izraz ustvarili dinamično. Najprej definirajmo funkcije za izračun vsakega stolpca z uporabo spremenljivk row in inp za sklicevanje na združene in vhodne podatke:

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");

Nekateri stolpci so posebni; njihove prve vrednosti funkcija ne bi smela izračunati. Da je prvi, lahko ugotovimo po stolpcu row[`numTrades] - če vsebuje 0, je vrednost prva. Q ima funkcijo izbire - ?[Boolean list;list1;list2] - ki izbere vrednost s seznama 1 ali 2, odvisno od pogoja v prvem argumentu:

// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - тоже обобщенное присваивание для случая когда индекс неглубокий
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];

Tukaj sem poklical posplošeno dodelitev s svojo funkcijo (izraz v zavitih oklepajih). Prejme trenutno vrednost (prvi argument) in dodatni argument, ki ga posredujem v 4. parametru.

Baterijske zvočnike dodajajmo posebej, saj je funkcija pri njih enaka:

// volume -> row[`volume]+inp`volume
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;

To je običajna dodelitev po standardih Q, vendar naenkrat dodelim seznam vrednosti. Končno ustvarimo glavno funkcijo:

// ":",/: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 tem izrazom dinamično ustvarim funkcijo iz niza, ki vsebuje izraz, ki sem ga dal zgoraj. Rezultat bo videti takole:

{[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])]}

Vrstni red vrednotenja stolpca je obrnjen, ker je v Q vrstni red vrednotenja od desne proti levi.

Zdaj imamo dve glavni funkciji, potrebni za izračune, dodati moramo le malo infrastrukture in storitev je pripravljena.

Zadnji koraki

Imamo funkciji preprocess in updateAgg, ki opravita vse delo. Še vedno pa je treba zagotoviti pravilen prehod skozi minute in izračunati indekse za združevanje. Najprej definirajmo funkcijo 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
 }

Določili bomo tudi funkcijo roll, ki bo spremenila trenutno minuto:

roll:{[tm]
  if[currTime>tm; :init[]]; // если перевалили за полночь, то просто вызовем init
  rollCache,::offset _ rollColumns#tradeAgg; // обновим кэш – взять roll колонки из aggTable, обрезать, вставить в rollCache
  offset::count tradeAgg;
  currSyms::`u#`$();
 }

Potrebovali bomo funkcijo za dodajanje novih znakov:

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)];
 }

In končno, funkcija upd (tradicionalno ime za to funkcijo za storitve Q), ki jo kliče odjemalec za dodajanje podatkov:

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 vse. Tukaj je celotna koda naše storitve, kot je bilo obljubljeno, le nekaj vrstic:

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

Preverimo delovanje storitve. Če želite to narediti, ga zaženite v ločenem procesu (kodo vstavite v datoteko service.q) in pokličite funkcijo init:

q service.q –p 5566

q)init[]

V drugi konzoli zaženite drugi proces Q in se povežite s prvim:

h:hopen `:host:5566
h:hopen 5566 // если оба на одном хосте

Najprej ustvarimo seznam simbolov - 10000 kosov in dodamo funkcijo za ustvarjanje naključne tabele. V drugi 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)}

Na seznam sem dodal tri prave simbole, da jih lažje iščem v tabeli. Funkcija rnd ustvari naključno tabelo z n vrsticami, kjer se čas spreminja od t do t+25 milisekund.

Zdaj lahko poskusite poslati podatke storitvi (dodajte prvih deset ur):

{h (`upd;`trade;rnd[10000;x])} each `time$00:00 + til 60*10

Ali je tabela posodobljena, lahko preverite v storitvi:

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|2919

Izvedimo zdaj testiranje obremenitve, da ugotovimo, koliko podatkov lahko storitev obdela na minuto. Naj vas spomnim, da smo interval posodabljanja nastavili na 25 milisekund. V skladu s tem mora storitev (v povprečju) ustrezati vsaj 20 milisekundam na posodobitev, da imajo uporabniki čas za zahtevanje podatkov. V drugi postopek vnesite naslednje:

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 dve minuti. Lahko poskusite najprej zagnati 1000 vrstic vsakih 25 milisekund:

start 1000

V mojem primeru je rezultat približno nekaj milisekund na posodobitev. Zato bom takoj povečal število vrstic na 10.000:

start 10000

Rezultat:

min| 00:00:00.004
avg| 9.191458
med| 9f
max| 00:00:00.030

Spet nič posebnega, a to je 24 milijonov vrstic na minuto, 400 tisoč na sekundo. Za več kot 25 milisekund se je posodobitev upočasnila le 5-krat, očitno, ko se je spremenila minuta. Povečajmo 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

Kot lahko vidite, se storitev komaj spoprijema, a kljub temu uspe ostati na površju. Takšna količina podatkov (240 milijonov vrstic na minuto) je izjemno velika, v takih primerih je običajno zagnati več klonov (ali celo desetine klonov) storitve, od katerih vsak obdela le del znakov. Kljub temu je rezultat impresiven za interpretirani jezik, ki se osredotoča predvsem na shranjevanje podatkov.

Lahko se pojavi vprašanje, zakaj čas raste nelinearno z velikostjo vsake posodobitve. Razlog je v tem, da je funkcija krčenja pravzaprav funkcija C, ki je veliko učinkovitejša od updateAgg. Začenši z določeno velikostjo posodobitve (približno 10.000), updateAgg doseže zgornjo mejo in takrat njegov čas izvajanja ni odvisen od velikosti posodobitve. Storitev lahko prebavi takšne količine podatkov zaradi predhodnega koraka Q. To poudarja, kako pomembno je izbrati pravi algoritem pri delu z velikimi podatki. Druga točka je pravilno shranjevanje podatkov v pomnilnik. Če podatki ne bi bili shranjeni stolpčno ali ne bi bili urejeni po času, bi se seznanili s tako stvarjo, kot je napaka v predpomnilniku TLB - odsotnost naslova pomnilniške strani v naslovnem predpomnilniku procesorja. Iskanje naslova traja približno 30-krat dlje, če je neuspešno, če pa so podatki razpršeni, lahko storitev večkrat upočasni.

Zaključek

V tem članku sem pokazal, da sta podatkovni zbirki KDB+ in Q primerni ne le za shranjevanje velikih podatkov in enostaven dostop do njih prek select, ampak tudi za ustvarjanje storitev za obdelavo podatkov, ki so sposobne prebaviti na stotine milijonov vrstic/gigabajtov podatkov tudi v en sam Q proces. Sam jezik Q zaradi svoje vektorske narave, vgrajenega tolmača narečij SQL in zelo uspešnega nabora funkcij knjižnice omogoča izjemno jedrnato in učinkovito implementacijo algoritmov, povezanih z obdelavo podatkov.

Opozoril bom, da je zgoraj navedeno le del tega, kar lahko naredi Q, ima pa tudi druge edinstvene funkcije. Na primer izjemno preprost protokol IPC, ki briše mejo med posameznimi procesi Q in omogoča združevanje več sto teh procesov v eno samo omrežje, ki se lahko nahaja na desetinah strežnikov na različnih koncih sveta.

Vir: www.habr.com

Dodaj komentar