Eiginleikar Q og KDB+ tungumálsins með dæmi um rauntímaþjónustu

Þú getur lesið um hvað KDB+ grunnurinn, Q forritunarmálið er, hverjir eru styrkleikar og veikleikar þeirra í fyrri grein og stuttlega í inngangi. Í greininni munum við innleiða þjónustu á Q sem mun vinna úr komandi gagnastraumi og reikna út ýmsar söfnunaraðgerðir á hverri mínútu í „rauntíma“ ham (þ.e. það mun hafa tíma til að reikna allt fyrir næsta hluta gagna). Helsti eiginleiki Q er að það er vektormál sem gerir þér kleift að starfa ekki með stökum hlutum, heldur með fylkjum þeirra, fylkjum og öðrum flóknum hlutum. Tungumál eins og Q og ættingjar þess K, J, APL eru fræg fyrir stuttorð þeirra. Oft er hægt að skrifa forrit sem tekur upp nokkra skjái af kóða á kunnuglegu tungumáli eins og Java á þá í nokkrum línum. Þetta er það sem ég vil sýna fram á í þessari grein.

Eiginleikar Q og KDB+ tungumálsins með dæmi um rauntímaþjónustu

Inngangur

KDB+ er dálkagagnagrunnur með áherslu á mjög mikið magn gagna, raðað á ákveðinn hátt (aðallega eftir tíma). Það er fyrst og fremst notað í fjármálastofnunum - bönkum, fjárfestingarsjóðum, tryggingafélögum. Q tungumálið er innra tungumál KDB+ sem gerir þér kleift að vinna með þessi gögn á áhrifaríkan hátt. Q hugmyndafræðin er stutt og skilvirkni en skýrleika er fórnað. Þetta er réttlætt með því að vektormálið verður í öllum tilvikum erfitt að skilja og stutt og auðlegð upptökunnar gerir þér kleift að sjá mun stærri hluta forritsins á einum skjá, sem gerir það að lokum auðveldara að skilja það.

Í þessari grein innleiðum við fullbúið forrit í Q og þú gætir viljað prófa það. Til að gera þetta þarftu raunverulega Q. Þú getur halað niður ókeypis 32-bita útgáfunni á heimasíðu kx fyrirtækisins – www.kx.com. Þar, ef þú hefur áhuga, finnur þú tilvísunarupplýsingar um Q, bókina Q Fyrir dauðlega og ýmsar greinar um þetta efni.

Samsetning vandans

Það er heimild sem sendir töflu með gögnum á 25 millisekúndna fresti. Þar sem KDB+ er fyrst og fremst notað í fjármálum, gerum við ráð fyrir að þetta sé tafla yfir færslur (viðskipti), sem hefur eftirfarandi dálka: tími (tími í millisekúndum), sym (fyrirtækjaheiti í kauphöll - IBM, AAPL,...), verð (verðið sem hlutabréfin voru keypt á), stærð (stærð viðskipta). 25 millisekúndna bilið er handahófskennt, ekki of lítið og ekki of langt. Tilvist þess þýðir að gögnin koma til þjónustunnar sem þegar er í biðminni. Auðvelt væri að innleiða biðmögnun á þjónustuhliðinni, þar með talið kraftmikla biðminni eftir núverandi álagi, en til einföldunar munum við einbeita okkur að föstu bili.

Þjónustan verður að telja hverja mínútu fyrir hvert komandi tákn úr sym dálknum safn af samansafnunaraðgerðum - hámarksverð, meðalverð, summa stærð osfrv. gagnlegar upplýsingar. Til einföldunar munum við gera ráð fyrir að hægt sé að reikna allar föll í þrep, þ.e. til að fá nýtt gildi er nóg að þekkja tvær tölur - gamla og komandi gildi. Til dæmis hafa föllin max, meðaltal, summa þennan eiginleika, en miðgildi fallið ekki.

Við munum einnig gera ráð fyrir að komandi gagnastraumur sé tímaraðaður. Þetta mun gefa okkur tækifæri til að vinna aðeins með síðustu stundu. Í reynd er nóg að geta unnið með núverandi og fyrri mínútur ef einhverjar uppfærslur eru seinar. Til einföldunar munum við ekki íhuga þetta mál.

Söfnunaraðgerðir

Nauðsynlegar samsöfnunaraðgerðir eru taldar upp hér að neðan. Ég tók eins marga af þeim og hægt var til að auka álagið á þjónustuna:

  • hátt – hámarksverð – hámarksverð á mínútu.
  • lágt – lágmarksverð – lágmarksverð á mínútu.
  • fyrsta verð – fyrsta verð – fyrsta verð á mínútu.
  • lastPrice – síðasta verð – síðasta verð á mínútu.
  • firstSize – fyrsta stærð – fyrsta viðskiptastærð á mínútu.
  • lastSize - síðasta stærð - síðasta viðskiptastærð á mínútu.
  • numTrades – telja i – fjöldi viðskipta á mínútu.
  • rúmmál – summa stærð – summa viðskiptastærða á mínútu.
  • pvolume – summa verð – summa verðs á mínútu, krafist fyrir meðalverð.
  • – heildarveltuverð* stærð – heildarmagn viðskipta á mínútu.
  • meðalverð – pvolume% fjöldaviðskipti – meðalverð á mínútu.
  • avgStærð – rúmmál% fjöldaviðskipti – meðalstærð viðskipta á mínútu.
  • vwap – velta% rúmmál – meðalverð á mínútu vegið eftir stærð viðskipta.
  • cumVolume – summa rúmmál – uppsöfnuð stærð viðskipta yfir allan tímann.

Við skulum strax ræða eitt óljóst atriði - hvernig á að frumstilla þessa dálka í fyrsta skipti og fyrir hverja síðari mínútu. Suma dálka af firstPrice gerðinni verður að frumstilla á núll í hvert skipti; gildi þeirra er óskilgreint. Aðrar hljóðstyrksgerðir verða alltaf að vera stilltar á 0. Það eru líka dálkar sem krefjast samsettrar nálgunar - til dæmis þarf að afrita cumVolume frá fyrri mínútu og fyrir þann fyrsta stilla á 0. Við skulum stilla allar þessar færibreytur með því að nota orðabókargögnin tegund (samlíkt við skrá):

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

Ég bætti sym og tíma við orðabókina til hægðarauka, nú er initWith tilbúin lína úr endanlegri samanlagðri töflu þar sem eftir er að stilla rétt sym og tíma. Þú getur notað það til að bæta nýjum línum við töflu.

Við munum þurfa aggCols þegar við búum til samansafnfall. Listanum verður að snúa við vegna þess í hvaða röð orðasambönd í Q eru metin (frá hægri til vinstri). Markmiðið er að tryggja að útreikningurinn fari úr háu í áfallsstyrk, þar sem sumir dálkar eru háðir fyrri dálkum.

Dálkum sem þarf að afrita á nýja mínútu frá fyrri, sym dálknum er bætt við til hægðarauka:

rollColumns:`sym`cumVolume;

Nú skulum við skipta dálkunum í hópa eftir því hvernig þeir ættu að vera uppfærðir. Þrjár tegundir má greina:

  1. Rafgeymir (magn, velta, ..) - við verðum að bæta innkomandi virði við það fyrra.
  2. Með sérstökum punkti (hátt, lágt, ..) - fyrsta gildi mínútunnar er tekið úr innkomnum gögnum, restin er reiknuð með aðgerðinni.
  3. Hvíldu. Alltaf reiknað með falli.

Við skulum skilgreina breytur fyrir þessa flokka:

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

Útreikningaröð

Við munum uppfæra samansafnaða töfluna í tveimur áföngum. Til hagkvæmni minnkum við fyrst innkomutöfluna þannig að það sé aðeins ein röð fyrir hvern staf og mínútu. Sú staðreynd að allar aðgerðir okkar eru stigvaxandi og tengdar tryggir að niðurstaða þessa viðbótarskrefs breytist ekki. Þú gætir minnkað borðið með því að velja:

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

Þessi aðferð hefur ókosti - mengið af reiknuðum dálkum er fyrirfram skilgreint. Sem betur fer, í Q, er select einnig útfært sem aðgerð þar sem þú getur komið í staðinn fyrir kraftvirkt búnar rök:

?[table;whereClause;byClause;selectClause]

Ég mun ekki lýsa sniði röksemda í smáatriðum; í okkar tilviki munu aðeins með og vali orðasamböndum vera óléttar og þær ættu að vera orðabækur með formdálkum!tjáningum. Þannig er hægt að skilgreina rýrnunaraðgerðina sem hér segir:

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

Til glöggvunar notaði ég þáttunarfallið, sem breytir streng með Q tjáningu í gildi sem hægt er að senda til eval fallsins og sem þarf í valinu fallinu. Athugaðu einnig að forvinnsla er skilgreind sem vörpun (þ.e. fall með að hluta skilgreindum rökum) af valfallinu, eina frumbreytu (töfluna) vantar. Ef við beitum forvinnslu á töflu fáum við þjappaða töflu.

Annað stig er að uppfæra samansafnaða töfluna. Við skulum fyrst skrifa reikniritið í gervikóða:

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 er algengt að nota map/reduce föll í stað lykkja. En þar sem Q er vektormál og við getum auðveldlega beitt öllum aðgerðum á öll tákn í einu, þá getum við í fyrstu nálgun gert án lykkju yfirleitt, framkvæmt aðgerðir á öllum táknum í einu:

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

En við getum gengið lengra, Q hefur einstakan og einstaklega öflugan rekstraraðila - almenna úthlutunaraðilann. Það gerir þér kleift að breyta mengi gilda í flóknu gagnaskipulagi með því að nota lista yfir vísitölur, aðgerðir og rök. Í okkar tilviki lítur þetta svona ú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;…)];

Því miður, til að úthluta til töflu þarftu lista yfir raðir, ekki dálka, og þú verður að yfirfæra fylkið (listi yfir dálka yfir á lista yfir raðir) með því að nota flip-aðgerðina. Þetta er dýrt fyrir stóra töflu, þannig að í staðinn notum við almenna úthlutun á hvern dálk fyrir sig, með því að nota kortaaðgerðina (sem lítur út eins og frávik):

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

Við notum aftur aðgerðarvörpun. Athugaðu líka að í Q er að búa til lista líka aðgerð og við getum kallað það með því að nota hvert(kort) aðgerðina til að fá lista yfir lista.

Til að tryggja að mengið af reiknuðum dálkum sé ekki fast, munum við búa til ofangreinda tjáningu á kraftmikinn hátt. Við skulum fyrst skilgreina aðgerðir til að reikna út hvern dálk með því að nota línu- og inp-breyturnar til að vísa til samansafnaðra gagna og inntaksgagna:

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

Sumir dálkar eru sérstakir; fyrsta gildi þeirra ætti ekki að vera reiknað út af fallinu. Við getum ákvarðað að það sé fyrst með röðinni[`numTrades] dálknum - ef það inniheldur 0, þá er gildið fyrst. Q hefur valfall - ?[Boolean list;list1;list2] - sem velur gildi úr lista 1 eða 2, allt eftir ástandinu í fyrstu röksemdinni:

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

Hér kallaði ég almennt verkefni með fallinu mínu (tjáning í krulluðum axlaböndum). Það fær núverandi gildi (fyrstu rökin) og viðbótarröksemd, sem ég sendi í 4. færibreytu.

Við skulum bæta við rafhlöðuhátölurum sérstaklega, þar sem virknin er sú sama fyrir þá:

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

Þetta er eðlilegt úthlutun samkvæmt Q stöðlum, en ég er að úthluta lista yfir gildi í einu. Að lokum skulum við búa til aðalaðgerðina:

// ":",/: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ð þessari tjáningu bý ég til fall úr streng sem inniheldur tjáninguna sem ég gaf hér að ofan. Útkoman mun líta svona út:

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

Dálkmatsröðinni er snúið við vegna þess að í Q er matsröðin frá hægri til vinstri.

Núna erum við með tvær helstu aðgerðir sem nauðsynlegar eru fyrir útreikninga, við þurfum bara að bæta við smá innviðum og þjónustan er tilbúin.

Lokaskref

Við erum með forvinnslu og updateAgg aðgerðir sem vinna alla vinnuna. En það er samt nauðsynlegt að tryggja rétt umskipti í gegnum mínútur og reikna út vísitölur fyrir samansafn. Fyrst af öllu skulum við skilgreina init aðgerðina:

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
 }

Við munum einnig skilgreina rúlluaðgerðina, sem mun breyta núverandi mínútu:

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

Við þurfum aðgerð til að bæta við nýjum stöfum:

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

Og að lokum, upd aðgerðin (hefðbundið nafn á þessari aðgerð fyrir Q þjónustu), sem viðskiptavinurinn kallar á til að bæta við gögnum:

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

Það er allt og sumt. Hér er heildarkóði þjónustunnar okkar, eins og lofað var, aðeins nokkrar línur:

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

Prófun

Við skulum athuga frammistöðu þjónustunnar. Til að gera þetta skulum við keyra það í sérstöku ferli (setja kóðann í service.q skrána) og kalla á init aðgerðina:

q service.q –p 5566

q)init[]

Í annarri stjórnborði, byrjaðu annað Q ferlið og tengdu við það fyrsta:

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

Fyrst skulum við búa til lista yfir tákn - 10000 stykki og bæta við falli til að búa til handahófskennda töflu. Í annarri vélinni:

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

Ég bætti þremur raunverulegum táknum við listann til að gera það auðveldara að leita að þeim í töflunni. Rnd fallið býr til handahófskennda töflu með n línum, þar sem tíminn er breytilegur frá t til t+25 millisekúndna.

Nú geturðu prófað að senda gögn til þjónustunnar (bæta við fyrstu tíu klukkustundunum):

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

Þú getur athugað í þjónustunni að taflan hafi verið uppfærð:

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

Niðurstaða:

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

Við skulum nú framkvæma álagspróf til að komast að því hversu mikið af gögnum þjónustan getur unnið á mínútu. Leyfðu mér að minna þig á að við stillum uppfærslubilið á 25 millisekúndur. Samkvæmt því verður þjónustan (að meðaltali) að passa inn í að minnsta kosti 20 millisekúndur á hverja uppfærslu til að gefa notendum tíma til að biðja um gögn. Sláðu inn eftirfarandi í seinna ferlinu:

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 eru tvær mínútur. Þú getur prófað að keyra fyrst í 1000 línur á 25 millisekúndna fresti:

start 1000

Í mínu tilfelli er niðurstaðan um nokkrar millisekúndur á hverja uppfærslu. Svo ég mun strax fjölga línunum í 10.000:

start 10000

Niðurstaða:

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

Aftur ekkert sérstakt, en þetta eru 24 milljónir línur á mínútu, 400 þúsund á sekúndu. Í meira en 25 millisekúndur hægði aðeins á uppfærslunni 5 sinnum, greinilega þegar mínútunni breyttist. Hækkum í 100.000:

start 100000

Niðurstaða:

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

Eins og þú sérð ræður þjónustan varla við en tekst engu að síður að halda sér á floti. Slíkt gagnamagn (240 milljónir raðir á mínútu) er gríðarlega mikið, í slíkum tilfellum er algengt að ræsa nokkur klón (eða jafnvel tugi klóna) þjónustunnar, sem hver um sig vinnur aðeins hluta af stöfunum. Samt sem áður er niðurstaðan áhrifamikil fyrir túlkað tungumál sem einbeitir sér fyrst og fremst að gagnageymslu.

Spurningin gæti vaknað hvers vegna tíminn vex ólínulega með stærð hverrar uppfærslu. Ástæðan er sú að rýrnunaraðgerðin er í raun C aðgerð, sem er mun skilvirkari en updateAgg. Byrjað er á ákveðinni uppfærslustærð (um 10.000), updateAgg nær þaki sínu og þá fer framkvæmdartími þess ekki eftir uppfærslustærðinni. Það er vegna fyrsta skrefsins Q sem þjónustan er fær um að melta slíkt magn af gögnum. Þetta undirstrikar hversu mikilvægt það er að velja rétta algrím þegar unnið er með stór gögn. Annar punktur er rétt geymsla gagna í minni. Ef gögnin væru ekki geymd í dálkum eða ekki raðað eftir tíma, þá myndum við kannast við eitthvað eins og TLB skyndiminni miss - skortur á minnissíðu heimilisfangi í vistfanga skyndiminni örgjörva. Leit að heimilisfangi tekur um 30 sinnum lengri tíma ef það tekst ekki og ef gögnin eru dreifð getur það hægt á þjónustunni nokkrum sinnum.

Ályktun

Í þessari grein sýndi ég fram á að KDB+ og Q gagnagrunnurinn hentar ekki aðeins til að geyma stór gögn og fá auðveldlega aðgang að þeim með vali, heldur einnig til að búa til gagnavinnsluþjónustu sem er fær um að melta hundruð milljóna raða/gígabæta af gögnum jafnvel í eitt Q ferli. Q tungumálið sjálft gerir kleift að útfæra ákaflega hnitmiðaða og skilvirka útfærslu á reikniritum sem tengjast gagnavinnslu vegna vektoreðlis þess, innbyggður SQL mállýskur túlkur og mjög vel heppnað safn af aðgerðum bókasafns.

Ég mun taka fram að ofangreint er bara hluti af því sem Q getur gert, það hefur líka aðra einstaka eiginleika. Til dæmis afar einföld IPC-samskiptareglur sem þurrkar út mörkin milli einstakra Q-ferla og gerir þér kleift að sameina hundruð þessara ferla í eitt net, sem getur verið staðsett á tugum netþjóna í mismunandi heimshlutum.

Heimild: www.habr.com

Bæta við athugasemd