Q ja KDB+ keele omadused reaalajas teenuse näitel

KDB+ and its programming language Q's strengths and weaknesses can be read in my previous article artiklis and briefly in the introduction. In this article, we will implement a service in Q that will process incoming data streams and calculate various aggregating functions in real-time (i.e., it will be able to compute everything before the next batch of data arrives). The main feature of Q is its vector-based nature, allowing operations on not just individual objects but also on arrays, arrays of arrays, and other complex objects. Languages like Q and its relatives K, J, and APL are known for their conciseness. Often, a program that takes several screens of code in a familiar language like Java can be written in just a few lines in these languages. This is precisely what I want to demonstrate in this article.

Q ja KDB+ keele omadused reaalajas teenuse näitel

Sissejuhatus

KDB+ on veergukeskne andmebaas, mis on suunatud väga suurtele andmehulkadele, mis on korraldatud teatud viisil (peamiselt ajaliselt). Seda kasutatakse peamiselt finantsasutustes – pankades, investeerimisfondides ja kindlustusseltsides. Q keel on KDB+ sisemine keel, mis võimaldab tõhusat andmetöötlust. Q ideoloogia tugineb kokkuvõtlikkusele ja efektiivsusele, arusaadavus tuleb seeläbi ohvriks. Seda põhjendatakse asjaoluga, et vektorikeel on igal juhul keeruline arusaada, ning lühidus ja tähendusrikkus võimaldavad näha suurema osa programmist ühel ekraanil, mis omakorda lihtsustab selle mõistmist.

Artiklis rakendame täisfunktsionaalset programmi Q keeles ja te võiksite soovi korral proovida seda praktikas. Selleks on teil vaja Q-d. Tasuta 32-bitise versiooni saate alla laadida ettevõtte kx veebilehelt – www.kx.com. Samuti leiate sealt, kui teid huvitab, teavet Q kohta, raamatu Q For Mortals ja mitmesuguseid artikleid selle teema kohta.

Ülesande seadmine

On olemas allikas, mis saadab iga 25 millisekundi järel andmetabeli. Kuna KDB+ kasutatakse peamiselt rahanduses, oletame, et see on tehingute (trades) tabel, milles on järgmised veergud: time (aeg millisekundites), sym (börsi ettevõtte tähis – IBM, AAPL,…), price (hind, millega aktsiad 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 saabuvad teenusesse juba puhverdatud kujul. Puuduks probleeme puhvri rakendamisega teenuse poolel, sealhulgas dünaamilise lahendusega, mis sõltub praegusest koormusest, kuid lihtsuse huvides keskendume fikseeritud intervallile.

Teenuse ülesanne on igal minutil iga sissetuleva sümboli jaoks veerus sym arvutada hulk kokkuvõtvaid funktsioone – max price, avg price, sum size jne. kasulikku teavet. Lihtsuse huvides oletame, et kõiki funktsioone saab arvutada järk-järgult, s.t. uue väärtuse saamiseks piisab kahe numbri – vana ja sissetuleva väärtuse – teadmisest. Näiteks omadustel max, average, sum on see omadus, kuid mediani funktsioonil seda ei ole.

Eeldame samuti, et sissetulev andmevoog on ajaliselt järjekindel. See annab meile võimaluse töötada ainult viimase minutiga. Praktiliselt piisab töötamisest käesoleva ja eelneva minutiga, juhuks kui mõned uuendused on hilinenud. Lihtsuse huvides ei arva me seda juhtumit.

Agregeerimisfunktsioonid

Allpool on loetletud vajalikud agregeerimisfunktsioonid. Olen neid võtnud 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 – tehingute 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 suurusele kaalutud keskmine hind minuti kohta.
  • cumVolume – summa mahust – kogu tehingute mahud aja jooksul.

Käime kohe läbi ühe mitte nii ilmse asja – kuidas initsialiseerida neid veerge esmakordselt ja iga järgmise minuti jooksul. Mõned veerud, näiteks firstPrice, tuleb igal korral initsialiseerida väärtusega null, kuna nende väärtus ei ole määratletud. Teised, näiteks volume, tuleb alati seadistada 0. On ka veerge, mis vajavad kombineeritud lähenemist – näiteks cumVolume tuleb kopeerida eelmisest minutist, samas kui esimeses tuleb see seadistada 0. Seame kõik need parameetrid, kasutades andmetüüpi sõnastik (analoog kirjele):

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

Olen lisanud sym ja time sõnastikku mugavuse huvides, nüüd on initWith – see on valmis rida lõplikust agreggeeritud tabelist, kus tuleb määrata õiged sym ja time. Seda saab kasutada uute ridade lisamiseks tabelisse.

aggCols on meil vajalik agreggeerimise funktsiooni loomisel. Loend tuleb pöörata, kuna Q-s väljendite arvutamise järjekorra eripära (paremalt vasakule). Eesmärk on tagada arvutamine suunas kõrgest cumVolume'ni, kuna mõned veerud sõltuvad eelmistest.

Veergud, mida tuleb uude minutisse eelmiste hulgast kopeerida, veerg `sym` on mugavuse huvides lisatud:

rollColumns:`sym`cumVolume;

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

  1. Kogujad (volume, turnover, ..) – me peame sisendi väärtuse eelnevale lisama.
  2. Eriti punkti puhul (high, low, ..) – esimene väärtus minutil saadakse sisendandmetest, ülejäänud arvutatakse funktsiooni abil.
  3. Ülejäänud. Arvutatakse alati funktsiooni abil.

Määratleme muutujaid nende klasside jaoks:

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

Arvutuste järjekord

Uuendame koondtabelit kahes etapis. Tõhususe huvides pigistame algse tabeli kokku nii, et igale sümbolile ja minutile jääb üks rida. Fakt, et kõik meie funktsioonid on inkrementaalsed ja assotsiatiivsed, garanteerib, et see täiendav etapp ei muuda tulemust. Tabelit oleks võimalik kokku pigistada, kasutades selekti:

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

Sellel lähenemisel on miinus – arvutatud veergude hulk on ette määratud. Õnneks on Q-s select rakendatud ka funktsioonina, kuhu saab dünaamiliselt loodud argumente sisestada:

?[table;whereClause;byClause;selectClause]

Ma ei hakka põhjalikult kirjeldama argumentide formaati, meie puhul on mitte triviaalseteks ainult by ja select väljendid ja need peaksid olema sõnakatte vormis columns!expressions. Nii saab kokkuvõtva funktsiooni määrata järgmiselt:

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

Selguse huvides kasutasin funktsiooni parse, mis muundab Q väljendiga stringi väärtuseks, mida saab edastada funktsioonile eval ja millist on vaja funktsionaalses selectis. Samuti tasub mainida, et preprocess on määratud projektsioonina (st funktsioon osaliselt määratletud argumentidega) select funktsioonist, üks argument (tabel) on puudu. Kui me rakendame preprocessi tabelile, saame kokkusurutud tabeli.

Teine etapp on agreggeeritud tabeli uuendamine. Kirjutame kõigepealt algoritmi pseudokoodis:

iga sümbol inputTable'is
  idx: row indeks aggregeeritud tabelis sümbol+currentTime;
  aggTable[idx;`high]: aggTable[idx;`high] | inputTable[sym;`high];
  aggTable[idx;`volume]: aggTable[idx;`volume] + inputTable[sym;`volume];
  …

Q-s on tavaks kasutada kaarte/reduktse. Kuid kuna Q on vektorkeel ja kõiki tehteid saame rahulikult rakendada kõikidele sümbolitele korraga, siis esimeses lähenemises saame üldse ilma tsükliteta hakkama, tehes tehteid kõigi sümbolitega korraga:

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

Kuid saame minna ka kaugemale, Q-s on ainulaadne ja ülimalt võimas operaator – üldistatud määramise operaator. See võimaldab muuta väärtuste kogu 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 rida, mitte veergude loend ning seetõttu tuleb maatriks (veergude loend ridade loendiks) transponida funktsiooniga flip. Suure tabeli puhul on see aeganõudev, seega rakendame üldist määramist eraldi igale veerule, kasutades funktsiooni map (mis näeb välja nagu apostroof):

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

Kasutame taas funktsiooni projektsiooni. Samuti pange tähele, et Q-s loendi loomine on samuti funktsioon ja saame seda kutsuda funktsiooni each(map) kaudu, et saada loendi loendeid.

Kuna arvutatavate veergude komplekt ei tohiks olla fikseeritud, loome ülaltoodud väljendi dünaamiliselt. Esiteks määratleme funktsioonid iga veeru arvutamiseks, 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 funktsiooni poolt arvutatud. Saame määrata, et see on esimene veerus row[`numTrades] – kui seal on 0, siis on väärtus esimene. Q-s on olemas valiku funktsioon — ?[Boolean list;list1;list2] – mis valib väärtuse nimekirjast 1 või 2, sõltuvalt esimesest argumendist:

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

Siin kutsusin välja üldiselt määramise oma funktsiooniga (avaldis kuppeldus). Sellesse edastatakse praegune väärtus (esimene argument) ja lisaparameeter, mille ma edastan 4. parameetrina.

Lisa eraldi akumuleerivad veerud, kuna nende jaoks on funktsioon sama:

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

See on tavaline määramine Q tasandil, ainult et määran kohe väärtuste nimekirja. Lõpuks loome põhifunktsiooni:

// ":",/: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 avaldisega loon dünaamiliselt funktsiooni stringist, mis sisaldab eelnevalt mainitud avaldist. 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ärjekord on pööratud, kuna Q-s on arvutamise järjekord paremalt vasakule.

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

Lõppfaasid

Meil on funktsioonid preprocess ja updateAgg, mis teevad kogu töö ära. Kuid on veel vajalik tagada õige üleminek minutite vahel ja arvutada indekse agregatsiooni jaoks. Esiteks määratleme funktsiooni init:

init:{
  tradeAgg:: 0#enlist[initWith]; ── loome tühja tüpiseeritud tabeli, enlist muundab sõnastiku tabeliks ja 0# tähendab võtta sealt 0 elementi
  currTime::00:00; ── alustame nullist, :: tähendab, et määramine toimub globaalsetesse muutujatesse
  currSyms::`u#`symbol$(); ── `u# - muundab loendi puuks, et elemente kiiremini otsida
  offset::0; ── indeks tradeAggis, kus praegune minut algab 
  rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg; ── vahemälu viimaste roll veergude väärtuste jaoks, tabel võtme sym
 }

Määratleme ka funktsiooni roll, mis muudab praegust minutit:

roll:{[tm]
  if[currTime>tm; :init[]]; ── kui oleme kesköö ületanud, kutsume lihtsalt init'i
  rollCache,::offset _ rollColumns#tradeAgg; ── värskendame vahemälu – võtame roll veerud aggTable'ist, kärbime, sisestame rollCache'i
  offset::count tradeAgg;
  currSyms::`u#`$();
 }

Me vajame funktsiooni uute sümbolite lisamiseks:

addSyms:{[syms]
  currSyms,::syms; // lisame tuntud sümbolite loendisse
  // lisame sümboli, aja ja rollColumns tabelisse kasutades üldist määramist.
  // Funktsioon ^ asendab vaikimisi väärtused rolli veergude jaoks, kui sümbolit pole vahemikus. value flip table tagastab veergude loendi 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 (selle funktsiooni traditsiooniline nimi Q teenustes), mida kutsub klient välja, et lisada andmeid:

upd:{[tblName;data] // tblName pole meile vajalik, kuid tavaliselt teenus töötleb mitu tabelit 
  tm:exec distinct time from data:() xkey preprocess data; // eelprotsess & arvuta aeg
  updMinute[data] each tm; // lisame andmed igaks minutiks
};
updMinute:{[data;tm]
  if[tm<>currTime; roll tm; currTime::tm]; // muudame minuti vajadusel
  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 aggregeeritud tabelit. Funktsioon ? otsib valu loendi elemente paremal pool loendis vasakul.
 };

Siin see on. Siin on kogu meie teenuse kood, nagu lubatud, vaid mõned read:

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 (pange kood faili service.q) ja kutsume 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 ühel hostil

Esmalt loome sümbolite loendi – 10000 tükki ja lisame funktsiooni juhuslikuks 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 loendisse lisanud kolm tõelist sümbolit, et neid tabelis paremini leida. Funktsioon rnd loob juhusliku tabeli, kus on n rida, kus aeg varieerub t-st t+25 millisekundini.

Nüüd saab proovida andmeid teenusesse saata (lisame esimesed kümme tundi):

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

Võite 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

Teeme nüüd koormustestimist, et välja selgitada, kui palju andmeid teenus minutis töödelda suudab. Meenus, et seadsime värskenduste intervalli 25 millisekundiks. Vastavalt peaks teenus (keskmiselt) mahtuma vähemalt 20 millisekundi värskendusse, et kasutajatele andmete pärimiseks aega anda. 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õite proovida alguses 1000 rida iga 25 millisekundi järel:

start 1000

Minu puhul on tulemus umbes paar millisekundit värskenduse kohta. Nii et ma 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

Taaskordust ei ole midagi erilist, kuid see on 24 miljonit rida minutis, 400 tuhat sekundis. Üle 25 millisekundi uuendamine jäi maha ainult 5 korda, ilmselt minuti vahetumise ajal. Suurendame 100 000 peale:

alusta 100000

Tulemus:

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

Nagu näeme, suudab teenus vaevu toime tulla, kuid ometi suudab see püsima jääda. Selline andmemaht (240 miljonit rida minutis) on erakordselt suur, sellistes olukordades on tavaline käivitada mitu klooni (või isegi kümneid kloone) teenusest, millest igaüks töötleb vaid osa sümbolitest. Siiski, tulemus on muljetavaldav tõlgitava keele jaoks, mis on esmajoones suunatud andmete salvestamisele.

Võib tekkida küsimus, miks aeg kasvab mitteelinearselt koos iga uuenduse suurusega. Põhjus on selles, et tihendamisfunktsioon on tegelikult C-funktsioon, mis töötab oluliselt efektiivsemalt kui updateAgg. Alates mingist uuenduse suurusest (umbes 10 000) saavutab updateAgg oma piigi ja edaspidi ei sõltu selle täitmise aeg uuenduse suurusest. Just tänu eelprogrammeerimise sammu Q-le suudab teenus selliseid andmemahu töödelda. See rõhutab, kui oluline on suurte andmete töötlemisel valida õige algoritm. Veel üks aspekt on andmete õige säilitamine mälus. Kui andmed ei oleks salvestatud veergudena või ei oleks ajaliselt järjestatud, siis oleksime tutvunud sellise nähtusega nagu TLB cache miss – mäluaadressi puudumine protsessori aadresside vahemälus. Aadressi otsimine võtab ebaõnnestumise korral umbes 30 korda rohkem aega ja hajutatud andmete puhul võib see teenuse kiirus mitu korda aeglustada.

Kokkuvõte

Selles artiklis näitasin, et KDB+ and Q on sobivad mitte ainult suurte andmete salvestamiseks ja nendele lihtsaks juurdepääsuks selekteerimise kaudu, vaid ka andmete töötlemise teenuste loomiseks, mis suudavad töödelda sadu miljoneid ridu/ gigabaite andmeid isegi ühes eraldi Q protsessis. Q keel ise võimaldab äärmiselt lühidalt ja efektiivselt rakendada andmetöötlusega seotud algoritme tänu oma vektorkarakterile, integreeritud SQL dialekti tõlgendajale ja väga hästi läbimõeldud raamatukogufunktsioonide kogumile.

Mainin, et ülaltoodud on vaid osa Q võimalustest, sellel on ka teisi ainulaadseid omadusi. Näiteks äärmiselt lihtne IPC protokoll, mis hävitab piiri eraldi Q protsesside vahel ja võimaldab sadade neist protsessidest kokku liita ühte võrku, mis võib paikneda kümnetes serverites erinevates maailma nurkades.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster