Për atë se çfarë është një bazë KDB+, gjuha e programimit Q, cilat janë përparësitë dhe disavantazhet e tyre, mund të lexoni në artikullin tim të mëparshëm dhe përmbledhtas në hyrje. Në këtë artikull ne do të implementojmë në Q një shërbim që do të përpunojë fluksin hynës të të dhënave dhe do të llogarisë në minutë funksione të ndryshme agreguese në kohë reale (dmth. do të përfundojë të gjitha llogaritë para pjesës tjetër të të dhënave). Karakteristika kryesore e Q-së është se është një gjuhë vektorike, e cila lejon manipulimin jo vetëm të objekteve të vetme, por edhe të masave, masave të masave dhe objekteve të tjera komplekse. Gjuhë të tilla si Q dhe ato të afërta K, J, APL janë të njohura për shkurtësinë e tyre. Shpesh një program që zë disa ekrane kodi në gjuhë të njohura si Java mund të shkruhet me to në disa rreshta. Këtë dëshiroj ta demonstroj në këtë artikull.

Hyrje
KDB+ është një bazë e dhënash kolonore, e orientuar në volume shumë të mëdha të të dhënave, të renditura në një mënyrë të caktuar (për më tepër sipas kohës). Ajo përdoret kryesisht në organizata financiare - banka, fonde investimi, kompani sigurimi. Gjuha Q është gjuha e brendshme e KDB+, që lejon punimin efikas me këto të dhëna. Ideologjia e Q-së është shkurtësia dhe efikasiteti, ku qartësia shkakton sakrificë. Kjo justifikohet me faktin se gjuhët vektorike gjithsesi janë të vështira për t'u kuptuar, ndërsa shkurtësia dhe dendurësia e shkrimit lejon që të shihni një pjesë shumë më të madhe të programit në një ekran, çka përfundimisht e lehtëson kuptimin e tij.
Në këtë artikull ne do të implementojmë një program të plotë në Q dhe mbase do të dëshironi ta provoni atë në praktikë. Për këtë, do t'ju nevojitet vetë Q. Mund ta shkarkoni versionin 32-bit falas nga faqja e kompanisë kx - . Aty, nëse jeni të interesuar, do të gjeni informacion ndihmës mbi Q, librin dhe artikuj të ndryshëm mbi këtë temë.
Formulimi i detyrës
Ka një burim që dërgon një tabelë me të dhëna çdo 25 milisekonda. Duke qënë se KDB+ përdoret në radhë të parë në financa, le të supozojmë se kjo është një tabelë tregimesh (trades), në të cilën ka kolonat e mëposhtme: time (koha në milisekonda), sym (shkurtesa e kompanisë në bursë - IBM, AAPL,…), price (çmimi, me të cilat u blen aksionet), size (madhësia e transaksionit). Intervali 25 milisekonda është zgjedhur rastësisht, nuk është shumë i vogël dhe as shumë i madh. Prania e tij tregon se të dhënat po vijnë në shërbim të ruajtur tashmë. Do të ishte e lehtë të realizoje ruajtjen në anën e shërbimit, duke përfshirë dinamikën e varur nga ngarkesa aktuale, por për thjeshtësi do të ndalemi në një interval të fiksuar.
Shërbimi duhet të llogarisë çdo minutë për çdo karakter të hyrjes nga kolona e simbolit një grup funksionesh agreguese – cmimi maksimal, cmimi mesatar, shuma e madhësisë etj. informacione të dobishme. Për thjeshtësi, do të pranojmë se të gjitha funksionet mund të llogariten inkrementalisht, pra për të marrë një vlerë të re mjafton të njohësh dy numra – vlerën e vjetër dhe vlerën e hyrjes. Për shembull, funksionet maks, mesatare, shuma e kanë këtë pronë, ndërsa funksioni median nuk e ka atë.
Po ashtu, ne do të supozojmë se fluksi i të dhënave të hyrjes është i renditur sipas kohës. Kjo do t'u japë mundësinë të punojmë vetëm me minutën e fundit. Në praktikë, është e mjaftueshme të jemi në gjendje të punojmë me minutën aktuale dhe minutat e mëparshme, për rast se disa përditësime vonohen. Për thjeshtësi, nuk do të shqyrtojmë këtë rast.
Funksionet agreguese
Më poshtë janë renditur funksionet e nevojshme agreguese. I kam marrë sa më shumë të jetë e mundur për të rritur ngarkesën në shërbim:
- high – max price – çmimi maksimal për minutë.
- low – min price – çmimi minimal për minutë.
- firstPrice – first price – çmimi i parë për minutë.
- lastPrice – last price – çmimi i fundit për minutë.
- firstSize – first size – madhësia e parë e transaksionit për minutë.
- lastSize – last size – madhësia e fundit e transaksionit për minutë.
- numTrades – count i – numri i transaksioneve për minutë.
- volume – sum size – shuma e madhësive të transaksioneve për minutë.
- pvolume – sum price – shuma e çmimeve për minutë, e nevojshme për avgPrice.
- turnover – sum price*size – vëllimi total i transaksioneve për minutë.
- avgPrice – pvolume%numTrades – çmimi mesatar për minutë.
- avgSize – volume%numTrades – madhësia mesatare e transaksionit për minutë.
- vwap – turnover%volume – çmimi mesatar i peshuar sipas madhësisë së transaksionit për minutë.
- cumVolume – sum volume – madhësia e akumuluar e transaksioneve për të gjithë kohën.
Le të diskutojmë menjëherë një aspekt të padukshëm – se si t’i infrastrukturoni këto kolona herën e parë dhe për çdo minutë të mëpasshme. Disa kolona si firstPrice duhet të inicializohen gjithmonë me vlerën null, pasi vlera e tyre s’është e përcaktuar. Kolonat e tjera si volume duhet gjithmonë të vendosen në 0. Gjithashtu, ka kolona që kërkojnë një qasje të kombinuar – për shembull, cumVolume duhet të kopjohet nga minuta e kaluar, ndërsa për të parën të vendoset në 0. Do t’i caktojmë të gjitha këto parametra duke përdorur tipin e të dhënave fjalor (analog me shkrimin):
// 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 объяснен ниже
Shtova sym dhe time në fjalor për lehtësi, tani initWith është një rresht i gatshëm nga tabela e fundit të aggreguar, ku duhet të caktojmë sim dhe kohën e saktë. Mund të përdoret për të shtuar rreshta të rinj në tabelë.
aggCols do të na nevojitet gjatë krijimit të funksionit agregues. Lista duhet të jetë e invertuar për shkak të karakteristikave të rendit të llogaritjes së shprehjeve në Q (nga e djathta në të majtë). Qëllimi është të sigurojmë llogaritjen në drejtim nga high në cumVolume, pasi disa kolona varen nga të kaluarat.
Kolonat që duhet të kopjohen në minutën e re nga e kaluara, kolona sym është shtuar për lehtësi:
rollColumns:`sym`cumVolume;
Tani do ta ndajmë kolonat në grupe sipas mënyrës se si duhet të përditësohen. Mund të evidentojmë tre lloje:
- Akumulatorët (volume, turnover,..) – ne duhet të shtojmë vlerën hyrëse te e kaluara.
- Me një pikë specifike (high, low, ..) – vlera e parë në minutë merret nga të dhënat hyrëse, ndërsa të tjerat llogariten përmes funksionit.
- Të tjerat. Gjithmonë llogariten përmes funksionit.
Të përcaktojmë variablat për këto klasa:
accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;
Rendi i llogaritjeve
Do ta përditësojmë tabelën e agreguar në dy faza. Për efikasitet, së pari do ta reduktojmë tabelën hyrëse që të ketë një rresht për çdo simbol dhe minutë. Fakti që të gjitha funksionet tona janë inkrementale dhe asociative na garanton që rezultati nga ky hap shtesë nuk do të ndryshojë. Tabela mund të kompaktohet me një selektim:
select high:max price, low:min price … by sym,time.minute from table
Ky mënyrë ka një disavantazh – seti i kolonave të llogaritura është i përcaktuar paraprakisht. Fatmirësisht, në Q selektirealizohet edhe si funksion, ku mund të fusim argumente të krijuara dinamikisht:
?[table;whereClause;byClause;selectClause]
Nuk do të përshkruaj detajisht formatin e argumenteve, në rastin tonë vetëm shprehjet by dhe select do të kenë rëndësi, dhe ato duhet të jenë në formatin e fjalorit columns!expressions. Kështu, funksioni kompresues mund të përcaktohet si:
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 është funksioni map në Q për një listë
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];
Për të qenë më të qartë, kam përdorur funksionin parse, i cili kthen një varg me shprehje Q në një vlerë që mund të dërgohet në funksionin eval dhe që kërkohet në zgjedhjen funksionale. Po ashtu, duhet theksuar se preprocess është përcaktuar si projekcion (dmth. funksion me argumente të pjesshme të përcaktuara) të funksionit select, një argument (tabela) mungon. Nëse e aplikojmë preprocess në tabelë, do të marrim një tabelë të kompresuar.
Hapi i dytë është përditësimi i tabelës së agreguar. Le të shkruajmë së pari algoritmin në pseudokod:
for each sym in inputTable
idx: rreshti në tabelën agg për sym+currentTime;
aggTable[idx;`high]: aggTable[idx;`high] | inputTable[sym;`high];
aggTable[idx;`volume]: aggTable[idx;`volume] + inputTable[sym;`volume];
…
Në Q është zakon që të përdoren funksionet map/reduce në vend të cikleve. Por, pasi Q është një gjuhë vektoriale dhe të gjitha operacionet mund të aplikohen pa shqetësim në të gjitha simbolet njëherësh, në një aproximim të parë mund të kalojmë plotësisht pa cikël, duke kryer operacione me të gjitha simbolet njëherësh:
idx:calcIdx inputTable;
row:aggTable idx;
aggTable[idx;`high]: row[`high] | inputTable`high;
aggTable[idx;`volume]: row[`volume] + inputTable`volume;
…
Por mund të shkojmë edhe më tej, në Q ka një operator unik dhe me fuqinë e jashtëzakonshme – operatorin e caktimit të përgjithësuar. Ai lejon ndryshimin e grupit të vlerave në një strukturë të ndërlikuar të dhënash duke përdorur një listë indeksesh, funksionesh dhe argumentesh. Në rastin tonë ai duket kështu:
idx:calcIdx inputTable;
rows:aggTable idx;
// .[target;(idx0;idx1;..);function;argument] ~ target[idx 0;idx 1;…]: function[target[idx 0;idx 1;…];argument], në rastin tonë funksioni është caktimi
.[aggTable;(idx;aggCols);:;flip (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];
Për fat të keq, për caktimin në një tabelë ne na nevojitet një listë rreshtash, jo kolonash, dhe është e nevojshme të transponohet matrica (lista e kolonave në listë rreshtash) me ndihmën e funksionit flip. Për një tabelë të madhe, kjo është e kushtueshme, kështu që në vend të kësaj do të aplikojmë caktimin e përgjithësuar për secilën kolonë veçmas, duke përdorur funksionin map (i cili duket si apostrof):
.[aggTable;;:;]'[(idx;)each aggCols; (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];
Përsëri po përdorim projekcionin e funksionit. Po ashtu vini re se në Q, krijimi i listës është gjithashtu një funksion dhe ne mund ta thërrasim atë duke përdorur funksionin each(map) për të marrë një listë listes.
Për të mos qenë i fiksuar seti i kolonave të llogaritura, do të krijojmë shprehjen lart dinamiksht. Së pari do të përcaktojmë funksionet për të llogaritur çdo kolonë, duke përdorur variablat row dhe inp për të referuar të dhënat agreguese dhe hyrëse:
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");
Disa kolona janë speciale, vlera e tyre e parë nuk duhet të llogaritet nga funksioni. Ne mund të përcaktojmë se është e para sipas kolonës row[`numTrades] – nëse në të është 0, atëherë vlera është e para. Në Q ekziston një funksion seleksioni — ?[Boolean list;list1;list2] – i cili zgjedh vlerën nga lista 1 ose 2 në varësi të kushteve në argumentin e parë:
// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - тоже обобщенное присваивание для случая когда индекс неглубокий
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];
Këtu kam thirrur caktimin e përgjithshëm me funksionin tim (shprehja në kaçkavall). Në të kaloj vlerën aktuale (argumenti i parë) dhe një argument shtesë që e kaloj në parametrin e 4-të.
Ndryshe do të shtojmë kolonat akumuluese, pasi për to funksioni është i njëjtë:
// volume -> row[`volume]+inp`volume
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
Kjo është një caktim i zakonshëm për standardet Q, vetëm se po caktoj një listë vlerash menjëherë. Së fundi, do të krijojmë funksionin kryesor:
// ":",/: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),")]}";
Me këtë shprehje krijoj dinamiksht një funksion nga një varg, i cili përmban shprehjen që kam cituar më sipër. Rezultati do të duket kështu:
{[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])]}
Renditja e llogaritjes së kolonave është inversuar, pasi në Q renditja e llogaritjes është nga e djathta në të majtë.
Tani kemi dy funksione kryesore, të nevojshme për llogaritjet, mbetet të shtojmë pak infrastrukturë dhe shërbimi është në gatishmëri.
Hapat përfundimtarë
Kemi funksionet preprocess dhe updateAgg, të cilat bëjnë punën e gjithë. Por është e nevojshme gjithashtu të sigurojmë kalimin e saktë përmes minutave dhe të llogarisim indekset për agregimin. Së pari, do të përcaktojmë funksionin init:
init:{
tradeAgg:: 0#enlist[initWith];
currTime::00:00;
currSyms::`u#`symbol$();
offset::0;
rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg;
}
Ne gjithashtu do të përcaktojmë funksionin roll, i cili do të ndryshojë minutën aktuale:
roll:{[tm]
if[currTime>tm; :init[]];
rollCache,::offset _ rollColumns#tradeAgg;
offset::count tradeAgg;
currSyms::`u#`$();
}
Do të na duhen një funksion për të shtuar simbole të reja:
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)];
}
Dhe, në fund, funksioni upd (emri tradicional i këtij funksioni për shërbimet Q), i cili thirret nga klienti, për të shtuar të dhëna:
upd:{[tblName;data]
tm:exec distinct time from data:() xkey preprocess data;
updMinute[data] each tm;
};
updMinute:{[data;tm]
if[tmcurrTime; 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];
};
Këtu e kemi. Këtu është kodi i plotë i shërbimit tonë, ashtu siç u premtuar, vetëm disa rreshta:
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];
};
Testimi
Le të shqyrtojmë performancën e shërbimit. Për këtë, do ta nisni atë në një proces të veçantë (vendosni kodin në skedarin service.q) dhe thirrni funksionin init:
q service.q –p 5566
q)init[]
Në një konsol tjetër, nisni një proces të dytë Q dhe lidhuni me të parin:
h:hopen `:host:5566
h:hopen 5566 // nëse të dy janë në të njëjtin host
Së pari do të krijojmë një listë simboresh - 10000 copë dhe do të shtojmë një funksion për krijimin e një tabele rastësore. Në konsolën e dytë:
syms:`IBM`AAPL`GOOG,-9997?`8
rnd:{[n;t] ([] sym:n?syms; time:t+asc n#til 25; price:n?10f; size:n?10)}
Kam shtuar në listën e simboleve tre të vërteta, në mënyrë që të jetë më e lehtë t'i kërkosh ato në tabelë. Funksioni rnd krijon një tabelë rastësore me n rreshta, ku koha ndryshon nga t deri në t+25 milisekonda.
Tani mund të përpiqemi të dërgojmë të dhënat në shërbim (shtojmë dhjetë orët e para):
{h (`upd;`trade;rnd[10000;x])} each `time$00:00 + til 60*10
Mund të kontrolloni në shërbim që tabela është përditësuar:
c 25 200
select from tradeAgg where sym=`AAPL
-20#select from tradeAgg where sym=`AAPL
Rezultati:
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|2919Tani do të kryejmë testimin e ngarkesës për të zbuluar sa të dhëna mund të përpunojë shërbimi për minuta. Kujtojmë se kemi vendosur një interval për azhurnimet në 25 milisekonda. Për rrjedhojë, shërbimi duhet (në mesatare) të përfundojë të paktën në 20 milisekonda për azhurnim, për të dhënë kohë përdoruesve të kërkojnë të dhënat. Futni këtë në procesin e dytë:
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 – janë dy minuta. Mund të provoni fillimisht për 1000 rreshta çdo 25 milisekonda:
start 1000
Në rastin tim, rezultati del në rreth disa milisekonda për azhurnim. Pra, menjëherë do ta rris numrin e rreshtave në 10.000:
start 10000
Rezultati:
min| 00:00:00.004
avg| 9.191458
med| 9f
max| 00:00:00.030
Sërish nuk ka asgjë të veçantë, por kjo është 24 milion rreshta në minutë, 400 mijë në sekondë. Më shumë se 25 milisekonda azhurnimi ngadalësoi vetëm 5 herë, duket se gjatë kalimit të minutës. Le ta rrisim në 100.000:
start 100000
Rezultati:
min| 00:00:00.013
avg| 25.11083
med| 24f
max| 00:00:00.108
q)sum times
00:02:00.532
Siç e shohim, shërbimi sapo po e mban dot, por megjithatë arrin të mbetet në ujë. Një volum i tillë të dhënash (240 milion rreshta në minutë) është jashtëzakonisht i madh, në raste të tilla është zakon të aktivizosh disa klone (apo edhe dhjetëra klone) të shërbimit, çdo njëri prej të cilëve përpunon vetëm një pjesë të simboleve. Megjithatë, rezultati është mbresëlënës për një gjuhë të interpretuar, e cila është orientuar kryesisht në ruajtjen e të dhënave.
mund të lindë pyetja, pse koha rritet në mënyrë jo lineare me madhësinë e çdo azhurnimi. Arsyeja është se funksioni i kompresimit është në të vërtetë një funksion C, i cili punon shumë më efektivisht se updateAgg. Duke filluar nga një madhësi e caktuar e azhurnimit (rreth 10,000), updateAgg arrin kufirin e tij, dhe koha e ekzekutimit nuk varet më nga madhësia e azhurnimit. Pikërisht përmes hapit paraprak Q shërbimi mund të përpunojë këto sasi të dhënash. Kjo thekson sa e rëndësishme është, kur punoni me të dhëna të mëdha, të zgjidhni algoritmin e duhur. Një moment tjetër është ruajtja e duhur e të dhënave në memorie. Po të ishin të dhënat të ruajtura jo kolonalisht ose nuk ishin të renditura sipas kohës, do të ndeshnim një situatë të tillë si TLB cache miss – mungesa e adresës së faqes së memories në cache-in e adresave të procesorit. Kërkimi i adresës merr rreth 30 herë më shumë kohë në rast dështimi dhe në rast të dhënash të shpërndara mund të ngadalësojë shërbimin disa herë.
Përfundim
Në këtë artikull kam treguar se baza KDB+ dhe Q janë të përshtatshme jo vetëm për ruajtjen e të dhënave të mëdha dhe aksesin e thjeshtë në to përmes selektimit, por edhe për krijimin e shërbimeve të përpunimit të të dhënave, të cilat janë në gjendje të përpunojnë qindra miliona rreshta/giga byte të dhënash edhe në një proces Q të vetëm. Gjuha Q lejon që të implementohen algoritme të lidhura me përpunimin e të dhënave në mënyrë jashtëzakonisht të shkurtër dhe efektive përmes natyrës së saj vektoriale, interpretuesit të integruar të dialektit SQL dhe setit të shkëlqyer të funksioneve bibliotekore.
Dua të theksoj se ajo që u përmend më lart, është vetëm një pjesë e mundësive të Q, ka edhe veçori të tjera unike. Për shembull, një protokoll IPC jashtëzakonisht i thjeshtë, i cili fshin kufirin ndërmjet proceseve të veçanta Q dhe lejon bashkimin e qindra këtyre proceseve në një rrjet të vetëm, i cili mund të jetë i vendosur në dhjetëra serverë në pjesët më të ndryshme të botës.
Burimi: habr.com
