Q ja KDB+ keele omadused reaalajas teenuse näitel

Mis on KDB+ baas, Q programmeerimiskeel, millised on nende tugevad ja nõrgad küljed, saate lugeda minu eelmisest siit ja lühidalt sissejuhatuses. Artiklis rakendame Q-s teenust, mis töötleb sissetulevat andmevoogu ja arvutab iga minut erinevaid liitmisfunktsioone "reaalajas" režiimis (st tal on aega enne järgmist andmete osa arvutada). Q põhiomadus seisneb selles, et see on vektorkeel, mis võimaldab opereerida mitte üksikute objektidega, vaid nende massiivide, massiivide massiivide ja muude keerukate objektidega. Sellised keeled nagu Q ja selle sugulased K, J, APL on kuulsad oma lühiduse poolest. Sageli saab programmi, mis võtab enda alla mitu ekraanikuva tuttavas keeles, näiteks Java, kirjutada neile mõne rea kaupa. Seda tahan selles artiklis näidata.

Q ja KDB+ keele omadused reaalajas teenuse näitel

Sissejuhatus

KDB+ on veeruline andmebaas, mis keskendub väga suurtele andmemahtudele, mis on järjestatud kindlal viisil (peamiselt aja järgi). Seda kasutatakse peamiselt finantsasutustes - pangad, investeerimisfondid, kindlustusseltsid. Q-keel on KDB+ sisemine keel, mis võimaldab teil nende andmetega tõhusalt töötada. Q ideoloogia on lühidus ja tõhusus, samas kui selgus tuuakse ohvriks. Seda põhjendatakse asjaoluga, et vektorkeelest on igal juhul raske aru saada ning salvestuse lühidus ja rikkalikkus võimaldab ühel ekraanil näha palju suuremat osa programmist, mis teeb sellest lõppkokkuvõttes arusaadavuse.

Selles artiklis rakendame Q-s täisväärtuslikku programmi ja võite seda proovida. Selleks vajate tegelikku Q. Tasuta 32-bitise versiooni saate alla laadida ettevõtte kx veebisaidilt – www.kx.com. Sealt leiate huvi korral viiteteavet raamatu Q kohta Q Surelike jaoks ja erinevaid artikleid sellel teemal.

Probleemi avaldus

On olemas allikas, mis saadab tabeli andmetega iga 25 millisekundi järel. Kuna KDB+ kasutatakse peamiselt rahanduses, siis eeldame, et tegemist on tehingute (tehingute) tabeliga, milles on järgmised veerud: aeg (aeg millisekundites), sym (ettevõtte nimetus börsil - IBM, AAPL,…), hind (hind, millega aktsiad osteti), suurus (tehingu suurus). 25 millisekundi intervall on meelevaldne, mitte liiga väike ega liiga pikk. Selle olemasolu tähendab, et andmed jõuavad teenusesse juba puhverdatuna. Teenuse poolel oleks lihtne rakendada puhverdamist, sealhulgas dünaamilist puhverdamist sõltuvalt hetkekoormusest, kuid lihtsuse huvides keskendume kindlale intervallile.

Teenus peab iga minuti järel iga sümboli veerust sissetuleva sümboli kohta loendama koondfunktsioonide komplekti - max hind, keskmine hind, summa suurus jne. kasulik informatsioon. Lihtsuse huvides eeldame, et kõiki funktsioone saab arvutada astmeliselt, st. uue väärtuse saamiseks piisab kahe numbri – vana ja sissetuleva väärtuse – teadmisest. Näiteks funktsioonidel max, keskmine, summa on see omadus, kuid mediaanfunktsioonil mitte.

Samuti eeldame, et sissetulev andmevoog on ajajärjestatud. See annab meile võimaluse töötada ainult viimase minutiga. Praktikas piisab sellest, kui oskad töötada jooksvate ja eelmiste minutitega juhuks, kui mõni uuendus hilineb. Lihtsuse huvides me seda juhtumit ei käsitle.

Liitmisfunktsioonid

Nõutavad liitmisfunktsioonid on loetletud allpool. Teenuse koormuse suurendamiseks võtsin neist võimalikult palju:

  • kõrge – max hind – maksimaalne minutihind.
  • madal – min hind – miinimumhind minutis.
  • firstPrice – esimene hind – esimene minutihind.
  • lastPrice – viimane hind – viimane hind minuti kohta.
  • firstSize – esimene suurus – esimene tehingu suurus minutis.
  • lastSize – viimane suurus – viimane tehingu suurus minuti pärast.
  • numTrades – count i – tehingute arv minutis.
  • maht – summa suurus – tehingute suuruste summa minutis.
  • pvolume – summa hind – hindade summa minutis, nõutav avgPrice jaoks.
  • – summa käibe hind*suurus – tehingute kogumaht minutis.
  • avgPrice – pvolume%numTrades – keskmine hind minutis.
  • avgSize – maht%numTrades – keskmine tehingu suurus minutis.
  • vwap – käive%maht – tehingu suuruse järgi kaalutud keskmine minutihind.
  • cumVolume – summa maht – tehingute akumuleeritud suurus kogu aja jooksul.

Arutleme kohe ühe mitteilmse punkti üle – kuidas neid veerge esimest korda ja iga järgneva minuti initsialiseerida. Mõned FirstPrice-tüüpi veerud tuleb iga kord nullida; nende väärtus on määramata. Muude helitugevuse tüüpide väärtuseks tuleb alati määrata 0. On ka veerge, mis nõuavad kombineeritud lähenemist - näiteks cumVolume tuleb kopeerida eelmisest minutist ja esimese jaoks määrata 0. Määrame kõik need parameetrid sõnastiku andmete abil tüüp (analoogselt kirjega):

// 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 mugavuse huvides sõnaraamatusse sym ja time, nüüd on initWith valmis rida lõplikust koondtabelist, kus jääb üle õige süm ja kellaaeg määrata. Saate seda kasutada tabelisse uute ridade lisamiseks.

Koondamisfunktsiooni loomisel vajame aggCols. Loend tuleb ümber pöörata, kuna Q avaldiste hindamise järjekord (paremalt vasakule). Eesmärk on tagada, et arvutus läheks suurest väärtusest cumVolume'i, kuna mõned veerud sõltuvad eelmistest.

Veerud, mis tuleb eelmisest minutist uuele minutile kopeerida, lisatakse mugavuse huvides sümboli veerg:

rollColumns:`sym`cumVolume;

Nüüd jagame veerud rühmadesse vastavalt sellele, kuidas neid värskendada. Eristada saab kolme tüüpi:

  1. Akumulaatorid (maht, käive jne) – sissetuleva väärtuse peame lisama eelmisele.
  2. Spetsiaalse punktiga (kõrge, madal, ..) – sissetulevatest andmetest võetakse esimene väärtus minutis, ülejäänud arvutatakse funktsiooni abil.
  3. Puhka. Arvutatakse alati funktsiooni abil.

Määratleme nende klasside muutujad:

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

Arvutamise järjekord

Värskendame koondtabelit kahes etapis. Tõhususe huvides kahandame esmalt sissetulevat tabelit nii, et iga tähemärgi ja minuti kohta oleks ainult üks rida. Asjaolu, et kõik meie funktsioonid on inkrementaalsed ja assotsiatiivsed, tagab, et selle lisaetapi tulemus ei muutu. Saate tabelit kahandada, kasutades valikut:

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

Sellel meetodil on puudus - arvutatud veergude komplekt on eelnevalt määratletud. Õnneks on Q-s valik realiseeritud ka funktsioonina, kus saate dünaamiliselt loodud argumente asendada:

?[table;whereClause;byClause;selectClause]

Ma ei kirjelda üksikasjalikult argumentide vormingut, meie puhul on ainult by ja select avaldised mittetriviaalsed ning need peaksid olema vormi veergude!avaldiste sõnastikud. Seega saab kahanemisfunktsiooni määratleda 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 это функция map в Q для одного списка
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];

Selguse huvides kasutasin parse funktsiooni, mis muudab Q-avaldisega stringi väärtuseks, mida saab edastada funktsioonile eval ja mis on vajalik funktsiooni valimisel. Samuti pange tähele, et eeltöötlus on määratletud kui funktsiooni Select projektsioon (st osaliselt määratletud argumentidega funktsioon), üks argument (tabel) puudub. Kui rakendame tabelile eeltöötluse, saame tihendatud tabeli.

Teine etapp on koondtabeli värskendamine. Esmalt kirjutame 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 silmuste asemel kaardistamise/vähendamise funktsioone. Kuid kuna Q on vektorkeel ja me saame hõlpsalt rakendada kõiki tehteid korraga kõikidele sümbolitele, siis esimese lähendusena saame täiesti ilma tsüklita, tehes toiminguid 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 me võime minna kaugemale, Q-l on ainulaadne ja äärmiselt võimas operaator - üldistatud määramise operaator. See võimaldab teil muuta keerukas andmestruktuuris väärtuste komplekti, kasutades indeksite, funktsioonide ja argumentide loendit. Meie puhul näeb see välja selline:

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

Kahjuks on tabelile määramiseks vaja ridade, mitte veergude loendit ja maatriksi (veergude loendi ridade loendiks) tuleb ümber pöörata, kasutades ümberpööramisfunktsiooni. See on suure tabeli puhul kallis, nii et selle asemel rakendame igale veerule eraldi üldistatud määrangut, kasutades kaardifunktsiooni (mis näeb välja nagu apostroof):

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

Kasutame jällegi funktsiooniprojektsiooni. Pange tähele ka seda, et Q-s on loendi loomine samuti funktsioon ja loendite loendi saamiseks saame seda kutsuda funktsiooni every(map) abil.

Tagamaks, et arvutatud veergude komplekt pole fikseeritud, loome ülaltoodud avaldise dünaamiliselt. Esmalt defineerime funktsioonid iga veeru arvutamiseks, kasutades koond- ja sisendandmetele viitamiseks rea ja inp muutujaid:

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 esimest väärtust ei tohiks funktsioon arvutada. Saame määrata, et see on esimene, veeru row[`numTrades] järgi – kui see sisaldab 0, siis on väärtus esimene. Q-l on valikufunktsioon - ?[tõve loend; loend1; loend2] -, mis valib loendist 1 või 2 väärtuse sõltuvalt esimese argumendi tingimusest:

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

Siin nimetasin üldistatud määramist oma funktsiooniga (avaldis lokkis sulgudes). See saab praeguse väärtuse (esimese argumendi) ja täiendava argumendi, mille annan edasi 4. parameetris.

Lisame akukõlarid eraldi, kuna nende funktsioon on sama:

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

See on Q-standardite järgi tavaline määramine, kuid ma määran väärtuste loendi korraga. 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 avaldise abil loon dünaamiliselt funktsiooni stringist, mis sisaldab ülaltoodud avaldist. Tulemus näeb välja selline:

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

Veeru hindamise järjekord on ümberpööratud, kuna Q-s on hindamise järjekord paremalt vasakule.

Nüüd on meil kaks peamist arvutusteks vajalikku funktsiooni, tuleb vaid veidi infrastruktuuri lisada ja teenus ongi valmis.

Viimased sammud

Meil on eeltöötlus- ja updateAgg-funktsioonid, mis teevad kogu töö ära. Kuid ikkagi on vaja tagada õige üleminek minutite kaupa ja arvutada indeksid liitmiseks. Kõigepealt defineerime funktsiooni 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
 }

Määratleme ka veeremisfunktsiooni, mis muudab praegust minutit:

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

Uute märkide lisamiseks vajame funktsiooni:

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

Ja lõpuks funktsioon upd (selle funktsiooni traditsiooniline nimi Q-teenuste jaoks), mida klient kutsub andmete lisamiseks:

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]; // обновим агрегированную таблицу. Функция ? ищет индекс элементов списка справа в списке слева.
 };

See on kõik. Siin on meie teenuse täielik kood, 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];
 };

Katsetamine

Kontrollime teenuse toimivust. Selleks käivitame selle eraldi protsessina (paneme kood faili service.q) ja kutsume välja init funktsiooni:

q service.q –p 5566

q)init[]

Käivitage teises konsoolis teine ​​Q-protsess ja looge ühendus esimesega:

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

Kõigepealt koostame sümbolite loendi – 10000 XNUMX 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)}

Lisasin nimekirja kolm pärissümbolit, et oleks lihtsam neid tabelist otsida. Funktsioon rnd loob juhusliku n reaga tabeli, kus aeg varieerub vahemikus t kuni t+25 millisekundit.

Nüüd saate proovida teenusesse andmete saatmist (lisada esimesed kümme tundi):

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

Teenuses saate kontrollida, kas 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 koormustesti, et teada saada, kui palju andmeid teenus minutis töödelda suudab. Lubage mul teile meelde tuletada, et seadsime värskendamise intervalliks 25 millisekundit. Sellest lähtuvalt peab teenus (keskmiselt) mahutama vähemalt 20 millisekundit värskenduse kohta, et anda kasutajatele aega andmete küsimiseks. Sisestage teises protsessis järgmine:

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 on kaks minutit. Võite proovida esmalt käivitada 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 XNUMX-ni:

start 10000

Tulemus:

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

Jällegi ei midagi erilist, aga see on 24 miljonit rida minutis, 400 tuhat sekundis. Rohkem kui 25 millisekundi jooksul aeglustus värskendus ainult 5 korda, ilmselt siis, kui minut muutus. Suurendame 100.000 XNUMX-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äete, saab teenus vaevu hakkama, kuid suudab siiski pinnal püsida. Selline andmemaht (240 miljonit rida minutis) on äärmiselt suur, sellistel puhkudel on tavaline käivitada mitu teenuse klooni (või isegi kümneid kloone), millest igaüks töötleb ainult osa tegelastest. Sellegipoolest on tulemus muljetavaldav tõlgendatud keele puhul, mis keskendub peamiselt andmete salvestamisele.

Võib tekkida küsimus, miks aeg kasvab iga uuenduse suurusega mittelineaarselt. Põhjus on selles, et kahandamise funktsioon on tegelikult C-funktsioon, mis on palju tõhusam kui updateAgg. Alates teatud värskenduse suurusest (umbes 10.000 30) saavutab updateAgg lae ja siis ei sõltu selle täitmise aeg värskenduse suurusest. Tänu eeltoimingule Q suudab teenus selliseid andmemahtusid seedida. See rõhutab, kui oluline on suurandmetega töötamisel valida õige algoritm. Teine punkt on andmete õige salvestamine mällu. Kui andmeid ei salvestataks veergude kaupa või need ei oleks aja järgi järjestatud, saaksime tuttavaks sellise asjaga nagu TLB vahemälu miss - mälulehe aadressi puudumine protsessori aadressi vahemälus. Ebaõnnestumise korral võtab aadressi otsimine umbes XNUMX korda kauem aega ning andmete hajumine võib teenust mitu korda aeglustada.

Järeldus

Käesolevas artiklis näitasin, et andmebaasid KDB+ ja Q sobivad mitte ainult suurte andmete salvestamiseks ja nendele valiku kaudu hõlpsasti juurde pääsemiseks, vaid ka andmetöötlusteenuste loomiseks, mis suudavad seedida sadu miljoneid ridu/gigabaite andmeid isegi üksainus Q-protsess. Q-keel ise võimaldab tänu oma vektorloomusele, sisseehitatud SQL murdetõlgile ja väga edukale raamatukogu funktsioonide komplektile andmetöötlusega seotud algoritmide ülimalt kokkuvõtlikult ja tõhusalt realiseerida.

Märgin, et ülaltoodu on vaid osa sellest, mida Q saab teha, sellel on ka muid ainulaadseid funktsioone. Näiteks ülilihtne IPC-protokoll, mis kustutab piiri üksikute Q-protsesside vahel ja võimaldab ühendada sadu neid protsesse ühtsesse võrku, mis võib asuda kümnetel serveritel maailma eri paigus.

Allikas: www.habr.com

Lisa kommentaar