Vipengele vya lugha ya Q na KDB+ kwa kutumia mfano wa huduma ya wakati halisi

Unaweza kusoma kuhusu msingi wa KDB+, lugha ya programu ya Q ni nini, uwezo wao na udhaifu wao ni katika uliopita. Ibara ya na kwa ufupi katika utangulizi. Katika makala hiyo, tutatekeleza huduma kwenye Q ambayo itashughulikia mtiririko wa data zinazoingia na kuhesabu kazi mbalimbali za ujumuishaji kila dakika katika hali ya "muda halisi" (yaani, itakuwa na muda wa kuhesabu kila kitu kabla ya sehemu inayofuata ya data). Kipengele kikuu cha Q ni kwamba ni lugha ya vector ambayo inakuwezesha kufanya kazi si kwa vitu moja, lakini kwa safu zao, safu za safu na vitu vingine ngumu. Lugha kama vile Q na jamaa zake K, J, APL ni maarufu kwa ufupi wao. Mara nyingi, programu inayochukua skrini kadhaa za msimbo katika lugha inayojulikana kama Java inaweza kuandikwa kwenye mistari michache. Hii ndio ninataka kuonyesha katika nakala hii.

Vipengele vya lugha ya Q na KDB+ kwa kutumia mfano wa huduma ya wakati halisi

Utangulizi

KDB+ ni hifadhidata ya safu iliyozingatia idadi kubwa sana ya data, iliyopangwa kwa njia maalum (kimsingi kwa wakati). Inatumika hasa katika taasisi za fedha - benki, fedha za uwekezaji, makampuni ya bima. Lugha ya Q ni lugha ya ndani ya KDB+ inayokuruhusu kufanya kazi na data hii kwa ufanisi. Itikadi ya Q ni ufupi na ufanisi, wakati uwazi unatolewa. Hii inathibitishwa na ukweli kwamba lugha ya vector itakuwa vigumu kuelewa kwa hali yoyote, na ufupi na utajiri wa kurekodi utapata kuona sehemu kubwa zaidi ya programu kwenye skrini moja, ambayo hatimaye inafanya iwe rahisi kuelewa.

Katika makala haya tunatekeleza programu kamili katika Q na unaweza kutaka kuijaribu. Ili kufanya hivyo, utahitaji Q halisi. Unaweza kupakua toleo la bure la 32-bit kwenye tovuti ya kampuni ya kx - www.kx.com. Huko, ikiwa una nia, utapata maelezo ya kumbukumbu juu ya Q, kitabu Q Kwa Wanaokufa na makala mbalimbali juu ya mada hii.

Taarifa ya tatizo

Kuna chanzo ambacho hutuma jedwali na data kila baada ya milisekunde 25. Kwa kuwa KDB+ inatumiwa hasa katika masuala ya fedha, tutafikiri kwamba hii ni jedwali la shughuli (biashara), ambalo lina safu wima zifuatazo: wakati (wakati katika milisekunde), sym (jina la kampuni kwenye soko la hisa - IBM, AAPL,…), bei (bei ambayo hisa zilinunuliwa), saizi (ukubwa wa muamala). Muda wa millisecond 25 ni wa kiholela, sio mdogo sana na sio mrefu sana. Uwepo wake unamaanisha kuwa data inakuja kwa huduma ambayo tayari imehifadhiwa. Itakuwa rahisi kutekeleza uakibishaji kwenye upande wa huduma, ikijumuisha kuakibisha kwa nguvu kulingana na mzigo wa sasa, lakini kwa unyenyekevu, tutazingatia muda uliowekwa.

Huduma lazima ihesabu kila dakika kwa kila ishara inayoingia kutoka kwa safu wima seti ya vitendaji vya kujumlisha - bei ya juu, bei ya wastani, saizi ya jumla, n.k. habari muhimu. Kwa unyenyekevu, tutafikiri kwamba kazi zote zinaweza kuhesabiwa kwa kuongezeka, i.e. ili kupata thamani mpya, inatosha kujua nambari mbili - za zamani na zinazoingia. Kwa mfano, nambari za kukokotoa za max, wastani, jumla zina mali hii, lakini kitendakazi cha wastani hakina.

Pia tutachukulia kuwa mtiririko wa data unaoingia umepangwa kwa wakati. Hii itatupa fursa ya kufanya kazi tu na dakika ya mwisho. Kwa mazoezi, inatosha kuweza kufanya kazi na dakika za sasa na zilizopita ikiwa sasisho zingine zimechelewa. Kwa unyenyekevu, hatutazingatia kesi hii.

Vipengele vya kujumlisha

Vipengele vinavyohitajika vya kujumlisha vimeorodheshwa hapa chini. Nilichukua wengi wao iwezekanavyo ili kuongeza mzigo kwenye huduma:

  • bei ya juu - ya juu - bei ya juu kwa dakika.
  • bei ya chini - bei ya chini - bei ya chini kwa dakika.
  • Bei ya kwanza - bei ya kwanza - bei ya kwanza kwa dakika.
  • Bei ya mwisho - bei ya mwisho - bei ya mwisho kwa dakika.
  • saizi ya kwanza - saizi ya kwanza - saizi ya kwanza ya biashara kwa dakika.
  • saizi ya mwisho - saizi ya mwisho - saizi ya mwisho ya biashara kwa dakika.
  • numTrades - count i - idadi ya biashara kwa dakika.
  • kiasi - saizi ya jumla - jumla ya saizi za biashara kwa dakika.
  • pvolume - bei ya jumla - jumla ya bei kwa dakika, inahitajika kwa avgPrice.
  • - Bei ya jumla ya mauzo* saizi - jumla ya kiasi cha miamala kwa dakika.
  • avgPrice – pvolume%numTrades – bei ya wastani kwa dakika.
  • avgSize - kiasi%numTrades - wastani wa ukubwa wa biashara kwa dakika.
  • vwap - mauzo%kiasi - bei ya wastani kwa dakika iliyopimwa na saizi ya ununuzi.
  • cumVolume - kiasi cha jumla - saizi iliyokusanywa ya shughuli kwa muda wote.

Wacha tujadili mara moja jambo moja lisilo dhahiri - jinsi ya kuanzisha safu hizi kwa mara ya kwanza na kwa kila dakika inayofuata. Baadhi ya safu wima za aina ya firstPrice lazima zianzishwe ili kubatilisha kila wakati; thamani yake haijafafanuliwa. Aina zingine za sauti lazima ziwekwe 0 kila wakati. Pia kuna safu wima zinazohitaji mbinu iliyounganishwa - kwa mfano, cumVolume lazima inakiliwe kutoka dakika iliyotangulia, na kwa ile ya kwanza iwe 0. Hebu tuweke vigezo hivi vyote kwa kutumia data ya kamusi. aina (sawa na rekodi):

// 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 объяснСн Π½ΠΈΠΆΠ΅

Niliongeza sym na wakati kwa kamusi kwa urahisi, sasa initWith ni laini iliyotengenezwa tayari kutoka kwa jedwali la mwisho la jumla, ambapo inabaki kuweka sym sahihi na wakati. Unaweza kuitumia kuongeza safu mlalo mpya kwenye jedwali.

Tutahitaji aggCols wakati wa kuunda chaguo za kukokotoa. Orodha lazima igeuzwe kutokana na mpangilio wa maneno katika Q yanatathminiwa (kutoka kulia kwenda kushoto). Kusudi ni kuhakikisha hesabu inatoka juu hadi cumVolume, kwani safu wima zingine hutegemea zilizotangulia.

Safu ambazo zinahitaji kunakiliwa hadi dakika mpya kutoka kwa ile iliyotangulia, safu wima ya ulinganifu huongezwa kwa urahisi:

rollColumns:`sym`cumVolume;

Sasa hebu tugawanye safu katika vikundi kulingana na jinsi zinapaswa kusasishwa. Aina tatu zinaweza kutofautishwa:

  1. Wakusanyaji (kiasi, mauzo, ..) - lazima tuongeze thamani inayoingia kwa uliopita.
  2. Kwa hatua maalum (ya juu, ya chini, ..) - thamani ya kwanza katika dakika inachukuliwa kutoka kwa data inayoingia, wengine huhesabiwa kwa kutumia kazi.
  3. Pumzika. Hukokotoa kila wakati kwa kutumia chaguo za kukokotoa.

Wacha tufafanue anuwai za madarasa haya:

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

Agizo la hesabu

Tutasasisha jedwali lililojumlishwa katika hatua mbili. Kwa ufanisi, kwanza tunapunguza meza inayoingia ili kuwe na safu moja tu kwa kila herufi na dakika. Ukweli kwamba kazi zetu zote ni za nyongeza na za ushirika huhakikisha kwamba matokeo ya hatua hii ya ziada hayatabadilika. Unaweza kupunguza meza kwa kutumia select:

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

Njia hii ina hasara - seti ya nguzo zilizohesabiwa zimefafanuliwa. Kwa bahati nzuri, katika Q, select pia inatekelezwa kama kazi ambapo unaweza kubadilisha hoja zilizoundwa kwa nguvu:

?[table;whereClause;byClause;selectClause]

Sitaelezea kwa undani muundo wa hoja; kwa upande wetu, tu baada ya kuchagua misemo itakuwa isiyo ya maana na inapaswa kuwa kamusi za safu wima!maneno. Kwa hivyo, kazi ya kupungua inaweza kufafanuliwa kama ifuatavyo:

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

Kwa uwazi, nilitumia chaguo la kukokotoa, ambalo hugeuza kamba na usemi wa Q kuwa thamani inayoweza kupitishwa kwa chaguo la kukokotoa la eval na inayohitajika katika chaguo la kukokotoa. Pia kumbuka kuwa preprocess inafafanuliwa kama makadirio (yaani, chaguo la kukokotoa lenye hoja zilizofafanuliwa sehemu) ya chaguo la kukokotoa, hoja moja (meza) haipo. Ikiwa tunatumia preprocess kwenye meza, tutapata meza iliyoshinikwa.

Hatua ya pili ni kusasisha jedwali lililojumlishwa. Wacha kwanza tuandike algorithm katika pseudocode:

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

Katika Q, ni kawaida kutumia ramani/punguza vitendaji badala ya vitanzi. Lakini kwa kuwa Q ni lugha ya vekta na tunaweza kutumia shughuli zote kwa urahisi kwa alama zote mara moja, basi kwa makadirio ya kwanza tunaweza kufanya bila kitanzi hata kidogo, tukifanya shughuli kwenye alama zote mara moja:

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

Lakini tunaweza kwenda mbali zaidi, Q ina opereta wa kipekee na mwenye nguvu sana - opereta wa kazi ya jumla. Inakuruhusu kubadilisha seti ya maadili katika muundo changamano wa data kwa kutumia orodha ya fahirisi, kazi na hoja. Kwa upande wetu inaonekana kama hii:

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

Kwa bahati mbaya, ili kugawa jedwali unahitaji orodha ya safu mlalo, sio safu wima, na lazima ubadilishe matrix (orodha ya safu wima hadi orodha ya safu) kwa kutumia kitendakazi cha kugeuza. Hii ni ghali kwa jedwali kubwa, kwa hivyo badala yake tunatumia mgawo wa jumla kwa kila safu kando, kwa kutumia kazi ya ramani (ambayo inaonekana kama apostrofi):

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

Tunatumia tena makadirio ya kazi. Pia kumbuka kuwa katika Q, kuunda orodha pia ni kazi na tunaweza kuiita kwa kutumia kila(ramani) kazi kupata orodha ya orodha.

Ili kuhakikisha kuwa seti ya safu wima zilizohesabiwa haijasanikishwa, tutaunda usemi ulio hapo juu kwa nguvu. Hebu kwanza tufafanue chaguo za kukokotoa ili kukokotoa kila safu, kwa kutumia safu mlalo na vigeu vya inp kurejelea data iliyojumlishwa na ingizo:

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

Baadhi ya safu wima ni maalum; thamani yake ya kwanza haipaswi kuhesabiwa kwa chaguo za kukokotoa. Tunaweza kubainisha kuwa ni ya kwanza kwa safu mlalo[`numTrades] - ikiwa ina 0, basi thamani ni ya kwanza. Q ina chaguo la kukokotoa - ?[Boolean list;list1;list2] - ambayo huchagua thamani kutoka orodha ya 1 au 2 kulingana na hali katika hoja ya kwanza:

// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - Ρ‚ΠΎΠΆΠ΅ ΠΎΠ±ΠΎΠ±Ρ‰Π΅Π½Π½ΠΎΠ΅ присваиваниС для случая ΠΊΠΎΠ³Π΄Π° индСкс Π½Π΅Π³Π»ΡƒΠ±ΠΎΠΊΠΈΠΉ
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];

Hapa niliita mgawo wa jumla na kazi yangu (msemo katika brashi za curly). Inapokea thamani ya sasa (hoja ya kwanza) na hoja ya ziada, ambayo ninapitisha katika paramu ya 4.

Wacha tuongeze spika za betri kando, kwani kazi ni sawa kwao:

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

Huu ni mgawo wa kawaida kwa viwango vya Q, lakini ninakabidhi orodha ya maadili mara moja. Mwishowe, wacha tuunda kazi kuu:

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

Kwa usemi huu, ninaunda kitendakazi kwa nguvu kutoka kwa kamba ambayo ina usemi niliotoa hapo juu. Matokeo yake yataonekana kama hii:

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

Agizo la tathmini ya safu wima limegeuzwa kwa sababu katika Q mpangilio wa tathmini ni kutoka kulia kwenda kushoto.

Sasa tuna kazi kuu mbili muhimu kwa mahesabu, tunahitaji tu kuongeza miundombinu kidogo na huduma iko tayari.

Hatua za mwisho

Tuna preprocess na updateAgg kazi ambayo hufanya kazi yote. Lakini bado ni muhimu kuhakikisha mpito sahihi kupitia dakika na kuhesabu faharisi za kujumlisha. Kwanza kabisa, hebu tufafanue kazi ya 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
 }

Pia tutafafanua kazi ya roll, ambayo itabadilisha dakika ya sasa:

roll:{[tm]
  if[currTime>tm; :init[]]; // Ссли ΠΏΠ΅Ρ€Π΅Π²Π°Π»ΠΈΠ»ΠΈ Π·Π° ΠΏΠΎΠ»Π½ΠΎΡ‡ΡŒ, Ρ‚ΠΎ просто Π²Ρ‹Π·ΠΎΠ²Π΅ΠΌ init
  rollCache,::offset _ rollColumns#tradeAgg; // ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ кэш – Π²Π·ΡΡ‚ΡŒ roll ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ ΠΈΠ· aggTable, ΠΎΠ±Ρ€Π΅Π·Π°Ρ‚ΡŒ, Π²ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ Π² rollCache
  offset::count tradeAgg;
  currSyms::`u#`$();
 }

Tutahitaji chaguo za kukokotoa ili kuongeza herufi mpya:

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

Na mwishowe, kazi ya kusasisha (jina la kitamaduni la kazi hii kwa huduma za Q), ambayo inaitwa na mteja kuongeza data:

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]; // ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ Π°Π³Ρ€Π΅Π³ΠΈΡ€ΠΎΠ²Π°Π½Π½ΡƒΡŽ Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ. Ѐункция ? ΠΈΡ‰Π΅Ρ‚ индСкс элСмСнтов списка справа Π² спискС слСва.
 };

Ni hayo tu. Hapa kuna nambari kamili ya huduma yetu, kama ilivyoahidiwa, mistari michache tu:

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

Upimaji

Wacha tuangalie utendaji wa huduma. Ili kufanya hivyo, wacha tuiendeshe kwa mchakato tofauti (weka nambari kwenye faili ya service.q) na uite kazi ya init:

q service.q –p 5566

q)init[]

Kwenye koni nyingine, anza mchakato wa pili wa Q na uunganishe na ya kwanza:

h:hopen `:host:5566
h:hopen 5566 // Ссли ΠΎΠ±Π° Π½Π° ΠΎΠ΄Π½ΠΎΠΌ хостС

Kwanza, hebu tutengeneze orodha ya alama - vipande 10000 na kuongeza kazi ili kuunda meza ya random. Katika console ya pili:

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

Niliongeza alama tatu halisi kwenye orodha ili iwe rahisi kuzitafuta kwenye jedwali. Chaguo za kukokotoa za rnd huunda jedwali nasibu lenye safu mlalo n, ambapo muda hutofautiana kutoka t hadi t+25 milisekunde.

Sasa unaweza kujaribu kutuma data kwa huduma (ongeza saa kumi za kwanza):

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

Unaweza kuangalia katika huduma ambayo jedwali limesasishwa:

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

Matokeo:

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

Wacha sasa tufanye upimaji wa mzigo ili kujua ni data ngapi ambayo huduma inaweza kuchakata kwa dakika. Acha nikukumbushe kwamba tuliweka muda wa kusasisha hadi milisekunde 25. Kwa hivyo, huduma lazima (kwa wastani) ilingane na angalau milisekunde 20 kwa kila sasisho ili kuwapa watumiaji muda wa kuomba data. Ingiza yafuatayo katika mchakato wa pili:

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 ni dakika mbili. Unaweza kujaribu kukimbia kwanza kwa safu mlalo 1000 kila baada ya milisekunde 25:

start 1000

Kwa upande wangu, matokeo yake ni karibu na milisekunde kadhaa kwa kila sasisho. Kwa hivyo nitaongeza mara moja idadi ya safu hadi 10.000:

start 10000

Matokeo:

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

Tena, hakuna kitu maalum, lakini hii ni mistari milioni 24 kwa dakika, elfu 400 kwa sekunde. Kwa zaidi ya milisekunde 25, sasisho lilipungua mara 5 tu, inaonekana wakati dakika ilibadilika. Wacha tuongeze hadi 100.000:

start 100000

Matokeo:

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

Kama unavyoona, huduma haiwezi kustahimili, lakini hata hivyo inafanikiwa kusalia. Kiasi kama hicho cha data (safu milioni 240 kwa dakika) ni kubwa sana; katika hali kama hizi, ni kawaida kuzindua clones kadhaa (au hata clones kadhaa) za huduma, ambayo kila moja husindika sehemu tu ya wahusika. Bado, matokeo yake ni ya kuvutia kwa lugha iliyotafsiriwa ambayo inalenga hasa kuhifadhi data.

Swali linaweza kutokea kwa nini wakati hukua bila mpangilio na saizi ya kila sasisho. Sababu ni kwamba kitendakazi cha shrink kwa kweli ni kazi ya C, ambayo ni bora zaidi kuliko updateAgg. Kuanzia saizi fulani ya sasisho (karibu 10.000), sasishoAgg hufikia dari yake na kisha wakati wake wa utekelezaji hautegemei saizi ya sasisho. Ni kutokana na hatua ya awali Q kwamba huduma inaweza kuchimba kiasi kama hicho cha data. Hii inaangazia jinsi ilivyo muhimu kuchagua algoriti sahihi unapofanya kazi na data kubwa. Jambo lingine ni uhifadhi sahihi wa data kwenye kumbukumbu. Ikiwa data haikuhifadhiwa kwa safu au haikuagizwa kwa wakati, basi tungefahamiana na kitu kama kukosa kashe ya TLB - kutokuwepo kwa anwani ya ukurasa wa kumbukumbu kwenye kashe ya anwani ya kichakataji. Kutafuta anwani huchukua takriban mara 30 zaidi ikiwa haifaulu, na ikiwa data imetawanyika, inaweza kupunguza kasi ya huduma mara kadhaa.

Hitimisho

Katika nakala hii, nilionyesha kuwa hifadhidata ya KDB+ na Q zinafaa sio tu kwa kuhifadhi data kubwa na kuipata kwa urahisi kupitia chaguo, lakini pia kwa kuunda huduma za usindikaji wa data ambazo zina uwezo wa kusaga mamia ya mamilioni ya safu/gigabytes ya data hata katika mchakato mmoja wa Q. Lugha ya Q yenyewe inaruhusu utekelezaji mafupi na mzuri sana wa algoriti zinazohusiana na usindikaji wa data kwa sababu ya asili yake ya vekta, mkalimani wa lahaja wa SQL na seti iliyofanikiwa sana ya kazi za maktaba.

Nitagundua kuwa yaliyo hapo juu ni sehemu tu ya kile Q inaweza kufanya, ina sifa zingine za kipekee pia. Kwa mfano, itifaki rahisi sana ya IPC ambayo inafuta mpaka kati ya michakato ya mtu binafsi ya Q na kukuruhusu kuchanganya mamia ya michakato hii hadi mtandao mmoja, ambao unaweza kupatikana kwenye seva nyingi katika sehemu tofauti za ulimwengu.

Chanzo: mapenzi.com

Kuongeza maoni