Funksjes fan 'e Q- en KDB+-taal mei it foarbyld fan in real-time tsjinst

Jo kinne lêze oer wat de KDB + basis, de programmeartaal Q binne, wat har sterke en swakke punten binne yn myn foarige artikel en koart yn 'e ynlieding. Yn it artikel sille wy in tsjinst implementearje op Q dy't de ynkommende gegevensstream ferwurkje en elke minút ferskate aggregaasjefunksjes berekkenje yn 'e "echte tiid" modus (dat wol sizze, it sil tiid hawwe om alles te berekkenjen foar it folgjende diel fan gegevens). It wichtichste skaaimerk fan Q is dat it in fektortaal is wêrmei jo net mei inkele objekten kinne operearje, mar mei har arrays, arrays fan arrays en oare komplekse objekten. Talen lykas Q en syn sibben K, J, APL binne ferneamd om har koarteheid. Faak kin in programma dat ferskate skermen mei koade yn in fertroude taal lykas Java opnimt, yn in pear rigels op har skreaun wurde. Dit is wat ik wol demonstrearje yn dit artikel.

Funksjes fan 'e Q- en KDB+-taal mei it foarbyld fan in real-time tsjinst

Ynlieding

KDB+ is in kollumnêre databank dy't rjochte is op tige grutte hoemannichten gegevens, oardere op in spesifike manier (foaral troch tiid). It wurdt benammen brûkt yn finansjele ynstellingen - banken, ynvestearringsfûnsen, fersekeringsbedriuwen. De Q-taal is de ynterne taal fan KDB+ wêrmei jo effektyf wurkje kinne mei dizze gegevens. De Q-ideology is koartens en effisjinsje, wylst dúdlikens wurdt opoffere. Dit wurdt rjochtfeardige troch it feit dat de fektortaal yn alle gefallen lestich te begripen sil wêze, en de koarteheid en rykdom fan 'e opname kinne jo in folle grutter diel fan it programma op ien skerm sjen, wat it úteinlik makliker makket om te begripen.

Yn dit artikel implementearje wy in folweardich programma yn Q en jo wolle it miskien besykje. Om dit te dwaan, hawwe jo de eigentlike Q nedich. Jo kinne de fergese 32-bit ferzje downloade op 'e kx-bedriuwwebside - www.kx.com. Dêr, as jo ynteressearre binne, fine jo referinsjeynformaasje oer Q, it boek Q Foar Mortals en ferskate artikels oer dit ûnderwerp.

Probleemintwurding

D'r is in boarne dy't elke 25 millisekonden in tabel mei gegevens stjoert. Om't KDB+ primêr brûkt wurdt yn finânsjes, sille wy oannimme dat dit in tabel fan transaksjes (hannels) is, dy't de folgjende kolommen hat: tiid (tiid yn millisekonden), sym (bedriuwsbetsjutting op 'e beurs - IBM, Aapl,...), priis (de priis wêrop de oandielen waarden kocht), grutte (grutte fan 'e transaksje). It ynterval fan 25 millisekonden is willekeurich, net te lyts en net te lang. Syn oanwêzigens betsjut dat de gegevens komme nei de tsjinst al buffered. It soe wêze maklik in útfiere buffering oan de tsjinst kant, ynklusyf dynamyske buffering ôfhinklik fan de hjoeddeiske lading, mar foar ienfâld, wy sille rjochtsje op in fêst ynterval.

De tsjinst moat elke minút telle foar elk ynkommende symboal út 'e sym-kolom in set fan aggregearjende funksjes - maksimum priis, gemiddelde priis, somgrutte, ensfh. brûkbere ynformaasje. Foar de ienfâld sille wy oannimme dat alle funksjes ynkrementeel berekkene wurde kinne, d.w.s. om in nije wearde te krijen, is it genôch om twa nûmers te witten - de âlde en de ynkommende wearden. Bygelyks, de funksjes max, gemiddelde, som hawwe dizze eigenskip, mar de mediaan funksje net.

Wy sille ek oannimme dat de ynkommende gegevensstream is tiid besteld. Dit sil ús de kâns jaan om allinich mei de lêste minút te wurkjen. Yn 'e praktyk is it genôch om te wurkjen mei de aktuele en foarige minuten foar it gefal dat guon updates te let binne. Foar ienfâld, wy sille net beskôgje dit gefal.

Aggregaasjefunksjes

De fereaske aggregaasjefunksjes wurde hjirûnder neamd. Ik naam safolle mooglik fan har om de lading op 'e tsjinst te fergrutsjen:

  • hege - maksimum priis - maksimum priis per minuut.
  • leech - min priis - minimum priis per minuut.
  • firstPrice - earste priis - earste priis per minút.
  • lastPrice - lêste priis - lêste priis per minút.
  • firstSize - earste grutte - earste hannel grutte per minút.
  • lastSize - lêste grutte - lêste hannelsgrutte yn in minút.
  • numTrades - telle i - oantal hannelingen per minút.
  • folume - som grutte - som fan hannel maten per minuut.
  • pvolume - sompriis - som fan prizen per minuut, fereaske foar avgPrice.
  • - som omsetpriis * grutte - totale folume fan transaksjes per minuut.
  • avgPrice - pvolume% numTrades - gemiddelde priis per minút.
  • avgSize - volume% numTrades - gemiddelde hannelsgrutte per minút.
  • vwap - omset% folume - gemiddelde priis per minuut gewogen troch transaksjegrutte.
  • cumVolume - sum folume - opboude grutte fan transaksjes oer de hiele tiid.

Litte wy fuortendaliks ien net foar de hân lizzend punt besprekke - hoe't jo dizze kolommen foar it earst en foar elke folgjende minút inisjalisearje. Guon kolommen fan it type firstPrice moatte elke kear inisjalisearre wurde om nul te wurden; har wearde is net definieare. Oare folumetypen moatte altyd ynsteld wurde op 0. D'r binne ek kolommen dy't in kombinearre oanpak fereaskje - bygelyks cumVolume moat kopiearre wurde fan 'e foarige minút, en foar de earste op 0. Litte wy al dizze parameters ynstelle mei de wurdboekgegevens type (analooch oan in record):

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

Ik haw sym en tiid tafoege oan it wurdboek foar gemak, no is initWith in klearmakke rigel út 'e definitive aggregearre tabel, wêr't it bliuwt om de juste sym en tiid yn te stellen. Jo kinne it brûke om nije rigen ta te foegjen oan in tabel.

Wy sille aggCols nedich wêze by it meitsjen fan in aggregaasjefunksje. De list moat omkeard wurde fanwege de folchoarder wêryn útdrukkingen yn Q wurde evaluearre (fan rjochts nei lofts). It doel is om te soargjen dat de berekkening fan heech nei cumVolume giet, om't guon kolommen ôfhinklik binne fan eardere.

Kolommen dy't moatte wurde kopiearre nei in nije minút fan 'e foarige, de sym-kolom wurdt tafoege foar it gemak:

rollColumns:`sym`cumVolume;

Litte wy no de kolommen ferdielen yn groepen neffens hoe't se bywurke wurde moatte. Trije soarten kinne wurde ûnderskieden:

  1. Akkumulators (folume, omset, ..) - wy moatte de ynkommende wearde tafoegje oan 'e foarige.
  2. Mei in spesjaal punt (heech, leech, ..) - de earste wearde yn 'e minút wurdt nommen út de ynkommende gegevens, de rest wurdt berekkene mei de funksje.
  3. Rêst. Altyd berekkene mei in funksje.

Litte wy fariabelen definiearje foar dizze klassen:

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

Berekkening folchoarder

Wy sille de aggregearre tabel yn twa stadia bywurkje. Foar effisjinsje krimp wy earst de ynkommende tabel sadat der mar ien rige foar elk karakter en minút. It feit dat al ús funksjes inkrementeel en assosjatyf binne garandearret dat it resultaat fan dizze ekstra stap net sil feroarje. Jo kinne de tabel krimp mei in selektear:

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

Dizze metoade hat in neidiel - de set fan berekkene kolommen is foarôf definiearre. Gelokkich, yn Q, wurdt selektearje ek ymplementearre as in funksje wêr't jo dynamysk oanmakke arguminten kinne ferfange:

?[table;whereClause;byClause;selectClause]

Ik sil it formaat fan 'e arguminten net yn detail beskriuwe; yn ús gefal sille allinich troch en selekteare útdrukkingen net-trivial wêze en se moatte wurdboeken wêze fan 'e foarmkolommen!-útdrukkingen. Sa kin de krimpfunksje as folget definieare wurde:

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

Foar dúdlikens haw ik de parsefunksje brûkt, dy't in tekenrige mei in Q-ekspresje feroaret yn in wearde dy't trochjûn wurde kin oan 'e evalfunksje en dy't nedich is yn 'e funksje selektearje. Tink derom ek dat preprocess wurdt definiearre as in projeksje (d.w.s. in funksje mei foar in part definiearre arguminten) fan de selektearre funksje, ien argumint (de tabel) ûntbrekt. As wy foarferwurking tapasse op in tabel, krije wy in komprimearre tabel.

De twadde etappe is it bywurkjen fan de aggregearre tabel. Litte wy earst it algoritme yn pseudokoade skriuwe:

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

Yn Q is it gewoan om map-/ferminderingsfunksjes te brûken ynstee fan loops. Mar om't Q in fektortaal is en wy alle operaasjes maklik op alle symboalen tagelyk kinne tapasse, dan kinne wy ​​​​nei in earste approximaasje hielendal sûnder lus dwaan, operaasjes op alle symboalen tagelyk útfiere:

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

Mar wy kinne gean fierder, Q hat in unyk en ekstreem krêftige operator - de generalisearre opdracht operator. It lit jo in set wearden feroarje yn in komplekse gegevensstruktuer mei in list mei yndeksen, funksjes en arguminten. Yn ús gefal sjocht it der sa út:

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

Spitigernôch, foar in tawize oan in tabel jo nedich in list fan rigen, net kolommen, en jo moatte transponearje de matrix (list fan kolommen nei list fan rigen) mei help fan de flip funksje. Dit is djoer foar in grutte tabel, dus ynstee tapasse wy in generalisearre opdracht oan elke kolom apart, mei de kaartfunksje (dy't liket op in apostrof):

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

Wy brûke wer funksje projeksje. Tink derom ek dat yn Q, it meitsjen fan in list ek in funksje is en wy kinne it neame mei de each(kaart) funksje om in list mei listen te krijen.

Om derfoar te soargjen dat de set fan berekkene kolommen net fêst is, sille wy de boppesteande útdrukking dynamysk meitsje. Litte wy earst funksjes definiearje om elke kolom te berekkenjen, mei de rige en inp fariabelen om te ferwizen nei de aggregearre en ynfiergegevens:

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

Guon kolommen binne spesjaal; har earste wearde moat net berekkene wurde troch de funksje. Wy kinne bepale dat it de earste is troch de kolom rige [`numTrades] - as it 0 befettet, dan is de wearde earst. Q hat in selektefunksje - ?[Boolean list;list1;list2] - dy't in wearde selektearret út list 1 of 2 ôfhinklik fan de betingst yn it earste argumint:

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

Hjir neamde ik in generalisearre opdracht mei myn funksje (in útdrukking yn krullende beugels). It ûntfangt de aktuele wearde (it earste argumint) en in ekstra argumint, dat ik trochjaan yn 'e 4e parameter.

Litte wy batterijluidsprekers apart tafoegje, om't de funksje foar har itselde is:

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

Dit is in normale opdracht troch Q-standerts, mar ik jou in list mei wearden yn ien kear ta. As lêste, litte wy de haadfunksje oanmeitsje:

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

Mei dizze útdrukking meitsje ik dynamysk in funksje fan in tekenrige dy't de útdrukking befettet dy't ik hjirboppe joech. It resultaat sil der sa útsjen:

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

De kolom evaluaasje folchoarder wurdt omkeard omdat yn Q de evaluaasje folchoarder is fan rjochts nei lofts.

No hawwe wy twa haadfunksjes nedich foar berekkeningen, wy moatte gewoan in bytsje ynfrastruktuer tafoegje en de tsjinst is klear.

Finale stappen

Wy hawwe preprocess en updateAgg funksjes dy't dogge al it wurk. Mar it is noch altyd nedich om de juste oergong troch minuten te garandearjen en yndeksen foar aggregaasje te berekkenjen. Lit ús earst de init-funksje definiearje:

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
 }

Wy sille ek de rolfunksje definiearje, dy't de aktuele minút sil feroarje:

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

Wy sille in funksje nedich wêze om nije karakters ta te foegjen:

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

En as lêste, de upd-funksje (de tradisjonele namme foar dizze funksje foar Q-tsjinsten), dy't troch de kliïnt neamd wurdt om gegevens ta te foegjen:

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

Da's alles. Hjir is de folsleine koade fan ús tsjinst, lykas tasein, mar in pear rigels:

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

Testing

Litte wy de prestaasjes fan 'e tsjinst kontrolearje. Om dit te dwaan, litte wy it yn in apart proses útfiere (set de koade yn it service.q-bestân) en neame de init-funksje:

q service.q –p 5566

q)init[]

Start yn in oare konsole it twadde Q-proses en ferbine mei de earste:

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

Lit ús earst in list mei symboalen meitsje - 10000 stikken en in funksje tafoegje om in willekeurige tabel te meitsjen. Yn de twadde konsole:

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

Ik haw trije echte symboalen tafoege oan 'e list om it makliker te meitsjen om se yn' e tabel te sykjen. De rnd-funksje makket in willekeurige tabel mei n rigen, wêrby't de tiid fariearret fan t oant t + 25 millisekonden.

No kinne jo besykje gegevens te ferstjoeren nei de tsjinst (foegje de earste tsien oeren ta):

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

Jo kinne yn 'e tsjinst kontrolearje dat de tabel is bywurke:

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

Resultaat:

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

Litte wy no loadtesten útfiere om út te finen hoefolle gegevens de tsjinst per minút kin ferwurkje. Lit my jo herinnerje dat wy it update-ynterval ynstelle op 25 millisekonden. Dêrtroch moat de tsjinst (gemiddeld) passe yn op syn minst 20 millisekonden per update om brûkers tiid te jaan om gegevens oan te freegjen. Fier it folgjende yn yn it twadde proses:

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 is twa minuten. Jo kinne besykje earst foar 1000 rigen elke 25 millisekonden te rinnen:

start 1000

Yn myn gefal is it resultaat sawat in pear millisekonden per update. Dat ik sil it oantal rigen daliks ferheegje nei 10.000:

start 10000

Resultaat:

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

Nochris neat spesjaal, mar dit is 24 miljoen rigels per minuut, 400 tûzen per sekonde. Foar mear dan 25 millisekonden fertrage de fernijing mar 5 kear, blykber doe't de minút feroare. Lit ús ferheegje nei 100.000:

start 100000

Resultaat:

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

Sa't jo sjen kinne, kin de tsjinst amper oan, mar dochs slagget it driuwend te bliuwen. Sa'n folume fan gegevens (240 miljoen rigen per minút) is ekstreem grut; yn sokke gefallen is it gewoan om ferskate klonen (of sels tsientallen klonen) fan 'e tsjinst te lansearjen, dy't elk mar in part fan' e karakters ferwurkje. Dochs is it resultaat yndrukwekkend foar in ynterpretearre taal dy't him benammen rjochtet op gegevensopslach.

De fraach kin opkomme wêrom't tiid net-lineêr groeit mei de grutte fan elke fernijing. De reden is dat de krimpfunksje eins in C-funksje is, wat folle effisjinter is as updateAgg. Begjinnend fan in bepaalde updategrutte (sawat 10.000), berikt updateAgg syn plafond en dan hinget syn útfieringstiid net ôf fan 'e updategrutte. It is te tankjen oan de foarriedige stap Q dat de tsjinst yn steat is om sokke voluminten gegevens te fertarren. Dit markearret hoe wichtich it is om it juste algoritme te kiezen as jo wurkje mei grutte data. In oar punt is de juste opslach fan gegevens yn it ûnthâld. As de gegevens waarden net opslein columnar of waarden net oardere troch de tiid, dan soene wy ​​wurde bekend mei sa'n ding as in TLB cache miss - it ûntbrekken fan in ûnthâld side adres yn de prosessor adres cache. It sykjen nei in adres duorret sawat 30 kear langer as it net slagget, en as de gegevens ferspraat binne, kin it de tsjinst ferskate kearen fertrage.

konklúzje

Yn dit artikel liet ik sjen dat de KDB+ en Q-database net allinich geskikt binne foar it bewarjen fan grutte gegevens en maklik tagong ta se troch selekteare, mar ek foar it meitsjen fan gegevensferwurkingstsjinsten dy't yn steat binne om hûnderten miljoenen rigen / gigabytes oan gegevens te fertarren, sels yn ien inkeld Q proses. De Q-taal sels soarget foar ekstreem beknopte en effisjinte ymplemintaasje fan algoritmen dy't relatearre binne oan gegevensferwurking fanwegen syn fektoraard, ynboude SQL-dialektinterpreter en in heul suksesfolle set biblioteekfunksjes.

Ik sil opmerke dat it boppesteande is gewoan in part fan wat Q kin dwaan, it hat ek oare unike funksjes. Bygelyks, in ekstreem ienfâldich IPC protokol dat wisket de grins tusken yndividuele Q prosessen en kinne jo kombinearje hûnderten fan dizze prosessen yn ien netwurk, dat kin lizze op tsientallen tsjinners yn ferskate dielen fan 'e wrâld.

Boarne: www.habr.com

Add a comment