Funkcie jazyka Q a KDB+ na príklade služby v reálnom čase

O tom, čo je základ KDB+, programovací jazyk Q, aké sú ich silné a slabé stránky, si môžete prečítať v mojom predchádzajúcom článok a krátko v úvode. V článku implementujeme službu na Q, ktorá bude spracovávať prichádzajúci dátový tok a každú minútu počítať rôzne agregačné funkcie v režime “real time” (t.j. stihne všetko vypočítať pred ďalšou porciou dát). Hlavnou črtou Q je, že ide o vektorový jazyk, ktorý vám umožňuje pracovať nie s jednotlivými objektmi, ale s ich poľami, poľami polí a inými komplexnými objektmi. Jazyky ako Q a jeho príbuzní K, J, APL sú známe svojou stručnosťou. Program, ktorý zaberá niekoľko obrazoviek kódu v známom jazyku, ako je Java, sa na nich často dá napísať v niekoľkých riadkoch. To je to, čo chcem ukázať v tomto článku.

Funkcie jazyka Q a KDB+ na príklade služby v reálnom čase

Úvod

KDB+ je stĺpcová databáza zameraná na veľmi veľké množstvo údajov, usporiadaných špecifickým spôsobom (predovšetkým podľa času). Používa sa predovšetkým vo finančných inštitúciách – banky, investičné fondy, poisťovne. Jazyk Q je interný jazyk KDB+, ktorý vám umožňuje efektívne pracovať s týmito údajmi. Ideológiou Q je stručnosť a efektívnosť, zatiaľ čo jasnosť je obetovaná. Je to odôvodnené tým, že vektorový jazyk bude v každom prípade ťažko zrozumiteľný a stručnosť a bohatosť záznamu umožňuje vidieť oveľa väčšiu časť programu na jednej obrazovke, čo v konečnom dôsledku uľahčuje pochopenie.

V tomto článku implementujeme plnohodnotný program v Q a možno ho budete chcieť vyskúšať. Na to budete potrebovať skutočný Q. Bezplatnú 32-bitovú verziu si môžete stiahnuť na webovej stránke spoločnosti kx - www.kx.com. Tam, ak máte záujem, nájdete referenčné informácie o knihe Q Q Pre smrteľníkov a rôzne články na túto tému.

Vyhlásenie o probléme

Existuje zdroj, ktorý každých 25 milisekúnd odošle tabuľku s údajmi. Keďže KDB+ sa používa predovšetkým vo finančníctve, budeme predpokladať, že ide o tabuľku transakcií (obchodov), ktorá má tieto stĺpce: čas (čas v milisekundách), sym (označenie firmy na burze - IBM, AAPL,…), cena (cena, za ktorú boli akcie nakúpené), veľkosť (veľkosť transakcie). Interval 25 milisekúnd je ľubovoľný, nie je príliš malý ani príliš dlhý. Jeho prítomnosť znamená, že údaje prichádzajú do služby už vo vyrovnávacej pamäti. Bolo by jednoduché implementovať ukladanie do vyrovnávacej pamäte na strane služieb vrátane dynamického vyrovnávania v závislosti od aktuálnej záťaže, ale pre jednoduchosť sa zameriame na pevný interval.

Služba musí každú minútu počítať pre každý prichádzajúci symbol zo stĺpca sym sadu agregačných funkcií – max. cena, priemerná cena, veľkosť sumy atď. užitočná informácia. Pre jednoduchosť budeme predpokladať, že všetky funkcie je možné vypočítať inkrementálne, t.j. na získanie novej hodnoty stačí poznať dve čísla - staré a prichádzajúce hodnoty. Napríklad funkcie max, priemer, súčet majú túto vlastnosť, ale mediánová funkcia nie.

Budeme tiež predpokladať, že prichádzajúci dátový tok je časovo usporiadaný. To nám dá možnosť pracovať len na poslednú chvíľu. V praxi stačí vedieť pracovať s aktuálnou a predošlou minútou v prípade, že niektoré aktualizácie meškajú. Pre jednoduchosť nebudeme tento prípad uvažovať.

Agregačné funkcie

Požadované agregačné funkcie sú uvedené nižšie. Vzal som ich čo najviac, aby som zvýšil zaťaženie služby:

  • vysoká – maximálna cena – maximálna cena za minútu.
  • nízka – minimálna cena – minimálna cena za minútu.
  • firstPrice – prvá cena – prvá cena za minútu.
  • lastPrice – posledná cena – posledná cena za minútu.
  • firstSize – prvá veľkosť – prvá veľkosť obchodu za minútu.
  • lastSize – posledná veľkosť – posledná obchodná veľkosť za minútu.
  • numTrades – count i – počet obchodov za minútu.
  • objem – suma sumy – suma objemov obchodu za minútu.
  • pvolume – sum price – suma cien za minútu, požadovaná pre avgPrice.
  • – suma obratu cena*veľkosť – celkový objem transakcií za minútu.
  • avgPrice – pvolume%numTrades – priemerná cena za minútu.
  • avgSize – volume%numTrades – priemerná veľkosť obchodu za minútu.
  • vwap – obrat%objem – priemerná cena za minútu vážená veľkosťou transakcie.
  • cumVolume – sumárny objem – akumulovaná veľkosť transakcií za celý čas.

Okamžite si pohovorme o jednom nie samozrejmom bode – ako inicializovať tieto stĺpce prvýkrát a pre každú ďalšiu minútu. Niektoré stĺpce typu firstPrice musia byť zakaždým inicializované na hodnotu null; ich hodnota nie je definovaná. Ostatné typy zväzkov musia byť vždy nastavené na 0. Existujú aj stĺpce, ktoré vyžadujú kombinovaný prístup - napríklad cumVolume je potrebné skopírovať z predchádzajúcej minúty a pre prvý nastaviť na 0. Nastavme všetky tieto parametre pomocou údajov zo slovníka typ (analogicky k záznamu):

// 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 объяснен ниже

Do slovníka som pre pohodlie pridal sym a čas, teraz je initWith hotový riadok z finálnej agregovanej tabuľky, kde zostáva nastaviť správny sym a čas. Môžete ho použiť na pridanie nových riadkov do tabuľky.

Pri vytváraní agregačnej funkcie budeme potrebovať aggCols. Zoznam musí byť invertovaný kvôli poradiu, v ktorom sa vyhodnocujú výrazy v Q (sprava doľava). Cieľom je zabezpečiť, aby výpočet prechádzal od vysokej k cumVolume, pretože niektoré stĺpce závisia od predchádzajúcich.

Stĺpce, ktoré je potrebné skopírovať do novej minúty z predchádzajúcej, sa pre pohodlie pridáva stĺpec sym:

rollColumns:`sym`cumVolume;

Teraz si rozdeľme stĺpce do skupín podľa toho, ako sa majú aktualizovať. Možno rozlíšiť tri typy:

  1. Akumulátory (objem, obrat,..) – vstupnú hodnotu musíme pripočítať k predchádzajúcej.
  2. So špeciálnym bodom (vysoký, nízky, ..) – prvá hodnota v minúte sa berie z došlých údajov, zvyšok sa vypočíta pomocou funkcie.
  3. Oddych. Vždy vypočítané pomocou funkcie.

Definujme premenné pre tieto triedy:

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

Poradie výpočtu

Súhrnnú tabuľku aktualizujeme v dvoch fázach. Kvôli efektívnosti najprv zmenšíme prichádzajúcu tabuľku tak, aby pre každý znak a minútu bol len jeden riadok. Skutočnosť, že všetky naše funkcie sú inkrementálne a asociatívne, zaručuje, že výsledok tohto dodatočného kroku sa nezmení. Tabuľku môžete zmenšiť pomocou výberu:

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

Táto metóda má nevýhodu - množina vypočítaných stĺpcov je preddefinovaná. Našťastie v Q je select implementovaný aj ako funkcia, kde môžete nahradiť dynamicky vytvorené argumenty:

?[table;whereClause;byClause;selectClause]

Nebudem podrobne popisovať formát argumentov, v našom prípade budú netriviálne iba výrazy typu a select a mali by to byť slovníky formulárových stĺpcov!výrazov. Funkciu zmršťovania možno teda definovať takto:

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

Pre prehľadnosť som použil funkciu parse, ktorá zmení reťazec s výrazom Q na hodnotu, ktorú je možné odovzdať funkcii eval a ktorá je vyžadovaná vo funkcii select. Všimnite si tiež, že predspracovanie je definované ako projekcia (t. j. funkcia s čiastočne definovanými argumentmi) funkcie select, jeden argument (tabuľka) chýba. Ak aplikujeme predspracovanie na tabuľku, dostaneme komprimovanú tabuľku.

Druhou fázou je aktualizácia agregovanej tabuľky. Najprv napíšme algoritmus v pseudokóde:

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 bežné používať funkcie map/reduce namiesto slučiek. Ale keďže Q je vektorový jazyk a môžeme ľahko aplikovať všetky operácie na všetky symboly naraz, potom sa pri prvej aproximácii môžeme zaobísť bez slučky a vykonávať operácie so všetkými symbolmi naraz:

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

Ale môžeme ísť ďalej, Q má jedinečný a mimoriadne výkonný operátor – operátor zovšeobecneného priraďovania. Umožňuje vám meniť množinu hodnôt v komplexnej dátovej štruktúre pomocou zoznamu indexov, funkcií a argumentov. V našom prípade to vyzerá takto:

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

Bohužiaľ, na priradenie k tabuľke potrebujete zoznam riadkov, nie stĺpcov, a musíte transponovať maticu (zoznam stĺpcov na zoznam riadkov) pomocou funkcie flip. To je drahé pre veľkú tabuľku, takže namiesto toho aplikujeme zovšeobecnené priradenie na každý stĺpec samostatne pomocou funkcie mapy (ktorá vyzerá ako apostrof):

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

Opäť použijeme projekciu funkcie. Všimnite si tiež, že v Q je vytváranie zoznamu tiež funkciou a môžeme ju zavolať pomocou funkcie every(map), aby sme získali zoznam zoznamov.

Aby sme zabezpečili, že množina vypočítaných stĺpcov nebude pevná, vytvoríme vyššie uvedený výraz dynamicky. Najprv definujme funkcie na výpočet každého stĺpca pomocou premenných riadok a inp na odkazovanie na agregované a vstupné údaje:

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

Niektoré stĺpce sú špeciálne, ich prvú hodnotu by funkcia nemala vypočítať. To, že je to prvé, môžeme určiť podľa stĺpca row[`numTrades] - ak obsahuje 0, potom je hodnota prvá. Q má funkciu výberu - ?[Booleovský zoznam;zoznam1;zoznam2] - ktorá vyberie hodnotu zo zoznamu 1 alebo 2 v závislosti od podmienky v prvom argumente:

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

Tu som nazval zovšeobecnené priradenie s mojou funkciou (výraz v zložených zátvorkách). Prijíma aktuálnu hodnotu (prvý argument) a dodatočný argument, ktorý odovzdávam v 4. parametri.

Pridajme reproduktory na batérie samostatne, pretože funkcia je pre nich rovnaká:

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

Toto je normálne priradenie podľa štandardov Q, ale zoznam hodnôt priraďujem naraz. Nakoniec vytvoríme hlavnú funkciu:

// ":",/: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),")]}";

Týmto výrazom dynamicky vytvorím funkciu z reťazca, ktorý obsahuje výraz, ktorý som uviedol vyššie. Výsledok bude vyzerať takto:

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

Poradie vyhodnocovania stĺpcov je obrátené, pretože v Q je poradie vyhodnocovania sprava doľava.

Teraz máme dve hlavné funkcie potrebné na výpočty, stačí pridať trochu infraštruktúry a služba je pripravená.

Záverečné kroky

Máme funkcie predbežného spracovania a aktualizácie Agg, ktoré robia všetku prácu. Stále je však potrebné zabezpečiť správny prechod cez minúty a vypočítať indexy pre agregáciu. Najprv definujme funkciu 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
 }

Zadefinujeme aj funkciu rolovania, ktorá zmení aktuálnu minútu:

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

Na pridanie nových znakov budeme potrebovať funkciu:

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

A nakoniec funkcia upd (tradičný názov tejto funkcie pre služby Q), ktorú klient volá na pridanie údajov:

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 všetko. Tu je kompletný kód našej služby, ako sme sľúbili, len pár riadkov:

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

Testovanie

Poďme skontrolovať výkon služby. Ak to chcete urobiť, spustite ho v samostatnom procese (kód vložte do súboru service.q) a zavolajte funkciu init:

q service.q –p 5566

q)init[]

V inej konzole spustite druhý proces Q a pripojte sa k prvému:

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

Najprv si vytvoríme zoznam symbolov – 10000 XNUMX kusov a pridáme funkciu na vytvorenie náhodnej tabuľky. V druhej konzole:

syms:`IBM`AAPL`GOOG,-9997?`8
rnd:{[n;t] ([] sym:n?syms; time:t+asc n#til 25; price:n?10f; size:n?10)}

Do zoznamu som pridal tri skutočné symboly, aby som ich v tabuľke ľahšie hľadal. Funkcia rnd vytvorí náhodnú tabuľku s n riadkami, kde sa čas mení od t do t+25 milisekúnd.

Teraz môžete skúsiť odoslať údaje do služby (pridajte prvých desať hodín):

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

V službe môžete skontrolovať, či bola tabuľka aktualizovaná:

c 25 200
select from tradeAgg where sym=`AAPL
-20#select from tradeAgg where sym=`AAPL

Výsledok:

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

Teraz vykonajte záťažové testovanie, aby sme zistili, koľko údajov dokáže služba spracovať za minútu. Pripomínam, že interval aktualizácie sme nastavili na 25 milisekúnd. Služba sa teda musí (v priemere) zmestiť aspoň do 20 milisekúnd na aktualizáciu, aby používatelia získali čas na vyžiadanie údajov. V druhom procese zadajte nasledovné:

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 sú dve minúty. Môžete skúsiť spustiť najskôr 1000 25 riadkov každých XNUMX milisekúnd:

start 1000

V mojom prípade je výsledok okolo niekoľkých milisekúnd na aktualizáciu. Takže okamžite zvýšim počet riadkov na 10.000 XNUMX:

start 10000

Výsledok:

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

Opäť nič zvláštne, ale toto je 24 miliónov riadkov za minútu, 400 tisíc za sekundu. Na viac ako 25 milisekúnd sa aktualizácia spomalila iba 5-krát, zrejme pri zmene minúty. Zvýšime na 100.000 XNUMX:

start 100000

Výsledok:

min| 00:00:00.013
avg| 25.11083
med| 24f
max| 00:00:00.108
q)sum times
00:02:00.532

Ako vidíte, služba si sotva poradí, no napriek tomu sa dokáže udržať nad vodou. Takýto objem dát (240 miliónov riadkov za minútu) je extrémne veľký, v takýchto prípadoch je bežné spustiť niekoľko klonov (alebo aj desiatky klonov) služby, z ktorých každý spracováva len časť znakov. Napriek tomu je výsledok pôsobivý pre interpretovaný jazyk, ktorý sa zameriava predovšetkým na ukladanie údajov.

Môže vzniknúť otázka, prečo čas rastie nelineárne s veľkosťou každej aktualizácie. Dôvodom je, že funkcia shrink je vlastne funkcia C, ktorá je oveľa efektívnejšia ako updateAgg. Počnúc od určitej veľkosti aktualizácie (okolo 10.000 30) dosiahne updateAgg svoj strop a potom čas jeho vykonania nezávisí od veľkosti aktualizácie. Vďaka predbežnému kroku Q je služba schopná stráviť takéto objemy údajov. To zdôrazňuje, aké dôležité je zvoliť správny algoritmus pri práci s veľkými údajmi. Ďalším bodom je správne ukladanie dát do pamäte. Ak by dáta neboli ukladané stĺpcovo alebo neboli zoradené podľa času, potom by sme sa zoznámili s takou vecou, ​​akou je TLB cache miss – absencia adresy pamäťovej stránky v cache adries procesora. Hľadanie adresy trvá pri neúspešnom asi XNUMX-krát dlhšie a pri rozsypaní dát môže službu niekoľkonásobne spomaliť.

Záver

V tomto článku som ukázal, že databázy KDB+ a Q sú vhodné nielen na ukladanie veľkých dát a jednoduchý prístup k nim cez select, ale aj na vytváranie služieb spracovania dát, ktoré sú schopné spracovať stovky miliónov riadkov/gigabajtov dát aj v jeden jediný Q proces. Samotný jazyk Q umožňuje vďaka svojej vektorovej povahe, vstavanému interpreteru dialektu SQL a veľmi úspešnej sade knižničných funkcií mimoriadne stručnú a efektívnu implementáciu algoritmov súvisiacich so spracovaním údajov.

Poznamenám, že vyššie uvedené je len časťou toho, čo Q dokáže, má aj ďalšie jedinečné vlastnosti. Napríklad extrémne jednoduchý IPC protokol, ktorý stiera hranicu medzi jednotlivými Q procesmi a umožňuje spojiť stovky týchto procesov do jednej siete, ktorá môže byť umiestnená na desiatkach serverov v rôznych častiach sveta.

Zdroj: hab.com

Pridať komentár