Q ja KDB+ keele võimalused reaalajas teenuse näitel

Subjekti KDB+ and Q keele, nende tugevuste ja nõrkuste kohta saab lugeda minu varasematest artiklitest. artiklis Vaatame lühidalt ka sissejuhatuses. Artiklis rakendame Q keeles teenuse, mis töötleb sisenevat andmevoogu ja arvutab reaalajas minutipõhiseid erinevaid agregaatfunktsioone (st see suudab kõik arvutada enne järgmise andmepartii saabumist). Q keele peamine omadus on see, et see on vektorikeel, mis võimaldab manipuleerida mitte üksikute objektidega, vaid nende massiividega, massiivide massiividega ja teiste komplekssete objektidega. Sellised keeled nagu Q ning sellega seotud K, J, APL on tuntud oma lühiduse poolest. Tihti võib programmi, mis tavakeeles nagu Java hõivab mitu ekraanipinda koodi, nendes keeltes kokku kirjutada paaris real. Just seda soovin ma selles artiklis demonstreerida.

Q ja KDB+ keele võimalused reaalajas teenuse näitel

Sissejuhatus

KDB+ on veergude andmebaas, mis on suunatud väga suurtele andmemahtudele, järjestatud teatud viisil (esmajärjekorras ajas). Seda kasutatakse peamiselt finantsasutustes – pankades, investeerimisfondides, kindlustusfirmades. Q keel on KDB+ sisemine keel, mis võimaldab efektiivselt neid andmeid töödelda. Q ideoloogia on lühidus ja efektiivsus, samas arusaadavus tuleb ohverdada. Seda põhjendatakse sellega, et vektorikeel on igal juhul keeruline ja lühidus ning tihedus võimaldavad näha ühel ekraanil palju suuremat osa programmist, mis lõpuks kergendab selle mõistmist.

Selles artiklis rakendame täisfunktsionaalset programmi Q keeles ning teil võib tekkida soov proovida seda praktikas. Selleks vajate Q keelt. Tasuta 32-bitise versiooni saab alla laadida ettevõtte kx veebilehelt – www.kx.com. Seal leiate ka Q kohta juhendavat teavet, raamatu Q For Mortals ja mitmesuguseid artikleid selle teema kohta.

Ülesande seadmine

On allikas, mis saadab iga 25 millisekundi järel tabeli andmetega. Kuna KDB+ kasutatakse peamiselt finantsvaldkonnas, eeldame, et see on tehingute tabel (trades), millel on järgmised veerud: time (aeg millisekundites), sym (aktsia tähis – IBM, AAPL,…), price (hind, millega aktsiad on ostetud), size (tehingu suurus). 25 millisekundi intervall on valitud juhuslikult, see ei ole liiga väike ega liiga suur. Selle olemasolu tähendab, et andmed jõuavad teenusesse juba puhverdatud kujul. Puudub vajadus rakendada puhverdamist teenuse poolel, sealhulgas dünaamiliselt, sõltuvalt praegusest koormusest, kuid lihtsuse huvides jääme fikseeritud intervalli juurde.

Teenust tuleb iga sissetuleva sümboli (column sym) kohta minutiti arvestada komplekti agregatsioonifunktsioonidest – max price, avg price, sum size ja sarnased kasulikud andmed. Lihtsuse huvides eeldame, et kõiki funktsioone saab arvutada inkrementaalselt, st uue väärtuse saamiseks piisab, kui teada kahte numbrit – vana ja sissetulevat väärtust. Näiteks funktsioonid max, average, sum omavad seda omadust, kuid funktsioon median ei oma.

Samuti eeldame, et sissetulev andmevoog on ajaliselt järjestatud. See annab meile võimaluse töötada ainult viimase minutiga. Praktikas on piisav osata töötada praeguste ja eelnevate minutitega juhuks, kui mõni uuendus jääb hiljaks. Lihtsuse huvides ei käsitle me seda juhtumit.

Agregatsioonifunktsioonid

Allpool on loetletud vajalikud agregatsioonifunktsioonid. Olen need valinud võimalikult palju, et suurendada teenuse koormust:

  • high – max price – maksimaalne hind minuti kohta.
  • low – min price – minimaalne hind minuti kohta.
  • firstPrice – first price – esimene hind minuti kohta.
  • lastPrice – last price – viimane hind minuti kohta.
  • firstSize – first size – esimene tehingu suurus minuti kohta.
  • lastSize – last size – viimane tehingu suurus minuti kohta.
  • numTrades – count i – tehingute arv minuti kohta.
  • volume – sum size – tehingu suuruste summa minuti kohta.
  • pvolume – sum price – hindade summa minuti kohta, vajalik avgPrice jaoks.
  • turnover – sum price*size – tehingute kogumaht minuti kohta.
  • avgPrice – pvolume%numTrades – keskmine hind minuti kohta.
  • avgSize – volume%numTrades – keskmine tehingu suurus minuti kohta.
  • vwap – turnover%volume – tehingu suuruse keskmine hind minuti kohta.
  • cumVolume – sum volume – akumuleeritud tehingu suurus kogu aja jooksul.

Arutame kohe ühe mitteilmse hetke – kuidas algselt initsialiseerida neid veerge esmakordselt ja iga järgmise minuti jaoks. Teatud tüüpi veerge nagu firstPrice tuleb iga kord initsialiseerida väärtusega null, nende väärtus ei ole määratletud. Teised, nagu volume, tuleb alati seadistada 0-le. Veel on veerge, mis nõuavad kombineeritud lähenemist – näiteks cumVolume tuleb kopeerida eelmise minutiga, kuid esimese jaoks seadistada see 0-le. Seame kõik need parameetrid andmetüübi sõnastiku (sünonüüm kirjalikuga) abil.

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

Lisasin sõnastikku sym ja time mugavuse huvides, nüüd initWith on valmis rida lõplikust koondtabelist, kus on jäänud määrata õiged sym ja time. Seda saab kasutada uute ridade lisamiseks tabelisse.

aggCols on vajalikud koondamistoimingu loomisel. Loend tuleb pöörata ümber, kuna Q-s väljendite arvutamise järjekord on paremalt vasakule. Eesmärk on tagada arvutamine suunal kõrge kuni cumVolume, kuna mõned veerud sõltuvad eelnevatest.

Veerud, mida tuleb uude minutisse eelmisest kopeerida, veerg sym on lisatud mugavuse huvides:

rollColumns:`sym`cumVolume;

Nüüd jagame veerud gruppidesse vastavalt sellele, kuidas neid tuleks värskendada. Saame eristada kolme tüüpi:

  1. Akumulaatorid (volume, turnover,..) – peame lisama siseneva väärtuse eelnevale.
  2. Spetsiaalne punktiga (high, low, ..) – esimene väärtus minutis võetakse sisendandmetest, teised arvutatakse funktsiooni abil.
  3. Ülejäänud. Arvutatakse alati funktsiooni abil.

Määratleme nende klasside muutujad:

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

Arvutuste järjekord

Värskendame koondtabelit kahel etapil. Tõhususe huvides tihendame esmalt sisenditabelit nii, et iga sümboli ja minuti kohta jääb alles üks rida. Et kõik meie funktsioonid on inkrementaalsed ja assotsiatiivsed, tagab see, et täiendava sammu tulemus ei muutu. Tabelit saaks ehk tihendada valiku abil:

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

Selle meetodi miinus on see, et arvutatavate veergude komplekt on ette määratud. Õnneks on Q-s selekt teostatud ka funktsioonina, kuhu saab sisestada dünaamiliselt loodud argumente:

?[table;whereClause;byClause;selectClause]

Ma ei hakka põhjalikult kirjeldama argumentide formaati, meie puhul on ainus keeruline asi by ja select väljendid ning need peavad olema sõnastike kujul columns!expressions. Seega saab kokkuvõtte funktsiooni määrata nii:

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 on Q-s map funktsioon ühe nimekirja jaoks
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];

Selguse huvides kasutasin funktsiooni parse, mis muudab Q väljendi stringi väärtuseks, mida saab edastada eval funktsioonile ja mis on vajalik funktsionaalses selektsioonis. Samuti tasub märkida, et preprocess on määratud projektsioonina (st osaliselt määratletud argumentidega funktsioon) selektsioonifunktsioonist, üks argument (tabel) puudub. Kui me rakendame preprocess tabelile, saame kokkuvõtlikku tabelit.

Teine etapp on kokkuvõtliku tabeli uuendamine. Kirjutame esmalt algoritmi pseudokoodis:

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

Q-s on tavaline kasutada map/reduce funktsioone tsüklite asemel. Kuid kuna Q on vektorkeel ja me saame kõik operatsioonid edukalt rakendada kõigi sümbolite peale korraga, saame esialgu isegi ilma tsüklita hakkama, tehes operatsioone kõigi sümbolitega üheaegselt:

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

Kuid me võime minna veel kaugemale, Q-s on ainulaadne ja erakordselt võimas operaator – üldistatud määramisoperaator. See võimaldab muuta väärtuste komplekti keerulises andmestruktuuris, kasutades indeksite, funktsioonide ja argumentide loendit. Meie puhul näeb see välja nii:

idx:calcIdx inputTable;
rows:aggTable idx;
\/\/ .[target;(idx0;idx1;..);function;argument] ~ target[idx 0;idx 1;…]: function[target[idx 0;idx 1;…];argument], meie puhul funktsioon – see on määramine
.[aggTable;(idx;aggCols);:;flip (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];

Kahjuks on tabelisse määramiseks vajalik ridade loend, mitte veergude loend, ja seetõttu tuleb maatriks (veergete loend ridade loendisse) transponeerida funktsiooni flip abil. Suure tabeli puhul on see kulukas, seetõttu rakendame üldistatud määramist iga veeru kohta eraldi, kasutades funktsiooni map (mis näeb välja nagu apostroof):

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

Me jälle kasutame funktsiooni projekteerimist. Pange tähele, et Q-s loendi loomine on samuti funktsioon ja saame seda kutsuda funktsiooni each(map) abil, et saada loendite loend.

Kuna arvutatavate veergude kogum ei tohiks olla fikseeritud, loome ülaltoodud väljendi dünaamiliselt. Esiteks määratleme funktsioonid, et arvutada iga veerg, kasutades muutujaid row ja inp, et viidata koondatud ja sisendandmetele:

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

Mõned veerud on erilised, nende esimene väärtus ei tohiks olla funktsiooniga arvutatud. Saame määratleda, et see on esimene veerus row[`numTrades] – kui seal on 0, siis on väärtus esimene. Q-s on valimise funktsioon — ?[Boolean list;list1;list2] – mis valib väärtuse loendist 1 või 2 sõltuvalt tingimusest esimeses argumendis:

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

Siin kutsusin ma üles üldist määramist oma funktsiooniga (väljend sulgudes). Sellele edastatakse praegune väärtus (esimene argument) ja täiendav argument, mille edastan 4. parameetris.

Eraldi lisame akumuleerivad veerud, kuna nende puhul on funktsioon sama:

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

See on Q jaoks tavaline määramine, ainult et määran korraga väärtuste loendi. Lõpuks loome peamise funktsiooni:

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

Selle väljendiga loon ma dünaamiliselt funktsiooni stringist, mis sisaldab väljendit, mida ma ülaltoodud. Tulemuse välimus on järgmine:

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

Veergude arvutamise järjestus on pööratud, kuna Q-s toimub arvutamine paremalt vasakule.

Nüüd on meil kaks peamist funktsiooni, mis on vajalikud arvutuste tegemiseks, jääb lisada veidi infrastruktuuri ja teenus on valmis.

Lõppastmed

Meil on funktsioonid preprocess ja updateAgg, mis teevad kogu töö. Kuid tuleb veel tagada õige üleminek minutide vahel ja arvutada indeksid koondamiseks. Esiteks määratlege funktsioon init:

init:{
  tradeAgg:: 0#enlist[initWith]; // loome tühi tüübitud tabel, enlist muudab sõnastiku tabeliks ja 0# tähendab, et võtame sellest 0 elementi
  currTime::00:00; // alustame 0-st, :: tähendab, et omistamine globaalsesse muutujasse
  currSyms::`u#`symbol$(); // `u# - muudab nimekirja puuks, et kiirendada elementide otsingut
  offset::0; // indeks tradeAgg-is, kust praegune minut algab
  rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg; // kihlvedude tulude viimaste väärtuste vahemälu, tabel võtmega sym
 }

Samuti määratleme roll-funktsiooni, mis muudab praegust minutit:

roll:{[tm]
  if[currTime>tm; :init[]]; // kui oleme ööpäevast üle läinud, siis kutsume lihtsalt välja init
  rollCache,::offset _ rollColumns#tradeAgg; // uuendame vahemälu – võtame roll veerud aggTable-st, lõikame ja paneme rollCache'i
  offset::count tradeAgg;
  currSyms::`u#`$();
 }

Meie jaoks vajame funktsiooni uute sümbolite lisamiseks:

addSyms:{[syms]
  currSyms,::syms; // lisame tuntud loendisse
  // lisame tabelisse sym, time ja rollColumns kasutades üldist määrangut.
  // Funktsioon ^ määrab vaikesed väärtused roll veergude jaoks, kui sümbolit ei leidu vahemälus. value flip table tagastab veergude nimekirja tabelis.
  `tradeAgg upsert @[count[syms]#enlist initWith;`sym`time,cols rc;:;(syms;currTime), (initWith cols rc)^value flip rc:rollCache ([] sym: syms)];
 }

Ja lõpuks funktsioon upd (traditsiooniline selle funktsiooni nimi Q teenustes), mida kutsub klient, et lisada andmeid:

upd:{[tblName;data] // tblName pole meile vajalik, kuid tavaliselt teenus töötleb mitmeid tabeleid 
  tm:exec distinct time from data:() xkey preprocess data; // eelprotsess ja arvuta aeg
  updMinute[data] each tm; // lisame andmed igaks minutiks
};
updMinute:{[data;tm]
  if[tmcurrTime; roll tm; currTime::tm]; // muudame minuti, kui see on vajalik
  data:select from data where time=tm; // filtreerimine
  if[count msyms:syms where not (syms:data`sym)in currSyms; addSyms msyms]; // uued sümbolid
  updateAgg[`tradeAgg;offset+currSyms?syms;data]; // uuendame agrgeeritud tabelit. Funktsioon ? otsib elemendi indeksi, mille loend on paremal loendis vasakul.
 };

Ja see on kõik. Siin on meie teenuse täiskood, nagu lubatud, vaid paar rida:

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

Testimine

Kontrollime teenuse jõudlust. Selleks käivitame selle eraldi protsessis (asetage kood faili service.q) ja kutsume välja funktsiooni init:

q service.q –p 5566

q)init[]

Teises konsoolis käivitage teine Q protsess ja ühendage see esimesega:

h:hopen `:host:5566
h:hopen 5566 // kui mõlemad on samal hostil

Alustame sümbolite nimekirja koostamisest – 10000 tükki ja lisame funktsiooni juhusliku tabeli loomiseks. Teises konsoolis:

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

Olen sümbolite nimekirja lisanud kolm reaalset sümbolit, et neid tabelist lihtsam leida. Funktsioon rnd loob juhusliku tabeli, kus on n rida, kus aeg varieerub t kuni t+25 millisekundit.

Nüüd võime proovida andmeid teenusesse saata (lisame esimesed kümme tundi):

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

Saame teenuses kontrollida, et tabel on uuendatud:

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

Tulemus:

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

Käime nüüd läbi koormustestimise, et välja selgitada, kui palju andmeid teenus minutis töödelda suudab. Tuletan meelde, et oleme seadnud värskendamise intervalli 25 millisekundiks. Seega peaks teenus (keskmiselt) mahtuma vähemalt 20 millisekundi sisse värskendamise jaoks, et kasutajatel oleks aega andmete pärimiseks. Sisestage järgmine teises protsessis:

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 – see on kaks minutit. Võib proovida esmalt käivitada 1000 rida iga 25 millisekundi järel:

start 1000

Minu juhul tuleb tulemus välja umbes paar millisekundit värskenduse kohta. Seega suurendan kohe ridade arvu 10,000-ni:

start 10000

Tulemus:

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

Taaskord ei ole midagi erilist, kuigi see on 24 miljonit rida minutis, 400 tuhat sekundis. Rohkem kui 25 millisekundit värskendamine peatas ainult 5 korda, ilmselt minuti vahetumise ajal. Suurendame 100 000-ni:

start 100000

Tulemus:

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

Nagu näeme, suudab teenus vaevu hakkama saada, ent tal õnnestub siiski pinnal püsida. Selline andmemaht (240 miljonit rida minutis) on äärmiselt suur, sellistes olukordades on tavaline käivitada mitu klooni (või isegi tosin klooni) teenusest, kus igaüks töötleb ainult osa sümbolitest. Sellegipoolest on tulemus muljetavaldav interpreteeritava keele jaoks, mis on suunatud peamiselt andmete salvestamisele.

Võib tekkida küsimus, miks aeg kasvab mitte-lineaarselt koos iga uuenduse suurusega. Põhjus on see, et kokkusurumisfunktsioon on tegelikult C funktsioon, mis töötab palju tõhusamalt kui updateAgg. Alates mingist uuenduse suurusest (umbes 10 000) jõuab updateAgg oma lae määrani ja edasine täitmise aeg ei sõltu enam uuenduse suurusest. Just eelneva Q sammu abil on teenus võimeline läbi töötama selliseid andmemahte. See rõhutab, kui oluline on suurte andmete töötlemisel valida õige algoritm. Veel üks aspekt on andmete õige hoidmine mälus. Kui andmed ei oleks veergude kaupa salvestatud või ajaliselt järjestatud, siis puutuksime kokku sellise nähtusega nagu TLB cache miss – mäluaadressi puudumine protsessori aadressikäimises. Aadressi otsimine võtab ebaõnnestumise korral umbes 30 korda rohkem aega ja hajutatud andmete korral võib see teenuse aeglustada mitmekordselt.

Kokkuvõte

Selles artiklis näitasin, et KDB+ andmebaas ja Q on sobivad mitte ainult suurte andmete hoidmiseks ja lihtsaks juurdepääsuks nendele selektsiooni kaudu, vaid ka andmete töötlemise teenuste loomiseks, mis suudavad läbi töötada sadu miljoneid ridu/ gigabaite andmeid isegi ühes eraldi Q protsessis. Q keel võimaldab erakordselt lühidalt ja tõhusalt rakendada andmetöötlusalgoritme oma vektorilise loomuse, sisseehitatud SQL dialekti tõlgendaja ja väga hästi välja töötatud teegifunktsioonide komplekti tõttu.

Tahan märkida, et ülaltoodud on vaid osa Q võimalustest, sellel on ka muid ainulaadseid omadusi. Näiteks äärmiselt lihtne IPC protokoll, mis kõrvaldab piiri eraldi Q protsesside vahel ja võimaldab liita sadu neid protsesse ühte võrku, mis võib paikneda kümnetes serverites eri maailma nurkades.

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster