Q ir KDB+ kalbos ypatybės, naudojant realaus laiko paslaugos pavyzdį

Apie tai, kas yra KDB+ bazė, Q programavimo kalba, kokios jų stipriosios ir silpnosios pusės, galite paskaityti mano ankstesniame straipsnis ir trumpai įžangoje. Straipsnyje įdiegsime paslaugą Q, kuri apdoros gaunamą duomenų srautą ir kiekvieną minutę „realiuoju laiku“ režimu skaičiuos įvairias agregavimo funkcijas (t.y. turės laiko viską suskaičiuoti iki kitos duomenų dalies). Pagrindinė Q ypatybė yra ta, kad tai vektorinė kalba, leidžianti dirbti ne su atskirais objektais, o su jų masyvais, masyvų masyvais ir kitais sudėtingais objektais. Tokios kalbos kaip Q ir jo giminės K, J, APL garsėja savo trumpumu. Dažnai programa, kuri užima kelis kodo ekranus pažįstama kalba, pvz., Java, gali būti įrašyta keliomis eilutėmis. Būtent tai noriu parodyti šiame straipsnyje.

Q ir KDB+ kalbos ypatybės, naudojant realaus laiko paslaugos pavyzdį

įvedimas

KDB+ yra stulpelių duomenų bazė, sutelkta į labai didelius duomenų kiekius, suskirstytus tam tikru būdu (pirmiausia pagal laiką). Jis pirmiausia naudojamas finansų institucijose – bankuose, investiciniuose fonduose, draudimo bendrovėse. Q kalba yra vidinė KDB+ kalba, leidžianti efektyviai dirbti su šiais duomenimis. Q ideologija yra trumpumas ir efektyvumas, o aiškumas paaukotas. Tai pateisinama tuo, kad vektorinė kalba bet kokiu atveju bus sunkiai suprantama, o įrašo trumpumas ir turtingumas leidžia viename ekrane matyti daug didesnę programos dalį, o tai galiausiai padeda lengviau suprasti.

Šiame straipsnyje mes įdiegiame visavertę programą Q ir galbūt norėsite ją išbandyti. Norėdami tai padaryti, jums reikės tikrojo Q. Nemokamą 32 bitų versiją galite atsisiųsti iš kx įmonės svetainės – www.kx.com. Ten, jei jus domina, rasite informacijos apie Q, knygą Q Mirtingiesiems ir įvairių straipsnių šia tema.

Problemos teiginys

Yra šaltinis, kuris siunčia lentelę su duomenimis kas 25 milisekundes. Kadangi KDB+ daugiausia naudojamas finansams, manysime, kad tai yra sandorių (sandorių) lentelė, kurioje yra šie stulpeliai: laikas (laikas milisekundėmis), sym (įmonės pavadinimas biržoje - IBM, AAPL,…), kaina (kaina, už kurią buvo įsigytos akcijos), dydis (sandorio dydis). 25 milisekundžių intervalas yra savavališkas, ne per mažas ir ne per ilgas. Jo buvimas reiškia, kad duomenys į paslaugą ateina jau supakuoti. Būtų nesunku įdiegti buferį aptarnavimo pusėje, įskaitant dinaminį buferį, atsižvelgiant į esamą apkrovą, tačiau paprastumo dėlei sutelksime dėmesį į fiksuotą intervalą.

Paslauga turi skaičiuoti kiekvieną minutę už kiekvieną įeinantį simbolį iš simbolio stulpelio agregavimo funkcijų rinkinį - maksimali kaina, vidutinė kaina, sumos dydis ir kt. Naudinga informacija. Paprastumo dėlei manysime, kad visos funkcijos gali būti skaičiuojamos laipsniškai, t.y. norint gauti naują reikšmę, pakanka žinoti du skaičius – senąją ir įeinančias reikšmes. Pavyzdžiui, funkcijos max, vidutinis, suma turi šią savybę, o mediana neturi.

Taip pat manysime, kad gaunamų duomenų srautas yra pagal laiką. Tai suteiks mums galimybę dirbti tik paskutinę minutę. Praktiškai pakanka mokėti dirbti su esamomis ir ankstesnėmis minutėmis, jei kai kurie atnaujinimai vėluoja. Dėl paprastumo mes nenagrinėsime šio atvejo.

Agregavimo funkcijos

Toliau pateikiamos reikalingos agregavimo funkcijos. Paėmiau jų kuo daugiau, kad padidinčiau paslaugos apkrovą:

  • high – max price – maksimali kaina už minutę.
  • žema – minimali kaina – minimali minutės kaina.
  • firstPrice – pirmoji kaina – pirmoji minutės kaina.
  • lastPrice – paskutinė kaina – paskutinė minutės kaina.
  • firstSize – pirmasis dydis – pirmasis prekybos dydis per minutę.
  • lastSize – paskutinis dydis – paskutinis prekybos dydis per minutę.
  • numTrades – count i – sandorių skaičius per minutę.
  • volume – sumos dydis – prekybos dydžių suma per minutę.
  • pvolume – suma kaina – kainų suma per minutę, reikalinga avgPrice.
  • – sumos apyvartos kaina*dydis – bendra operacijų apimtis per minutę.
  • avgPrice – pvolume%numTrades – vidutinė minutės kaina.
  • avgSize – volume%numTrades – vidutinis prekybos dydis per minutę.
  • vwap – apyvarta%apimtis – vidutinė minutės kaina, pasverta pagal sandorio dydį.
  • cumVolume – sumos apimtis – sukauptas operacijų dydis per visą laiką.

Iš karto aptarkime vieną neakivaizdų dalyką – kaip inicijuoti šiuos stulpelius pirmą kartą ir kiekvienai paskesnei minutei. Kai kurie „firstPrice“ tipo stulpeliai kiekvieną kartą turi būti inicijuojami į nulį; jų reikšmė neapibrėžta. Kiti apimties tipai visada turi būti nustatyti į 0. Taip pat yra stulpelių, kuriems reikia kombinuoto požiūrio – pavyzdžiui, cumVolume reikia nukopijuoti iš ankstesnės minutės, o pirmajam – 0. Visus šiuos parametrus nustatykime naudodami žodyno duomenis. tipas (analogiškas įrašui):

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

Patogumui į žodyną įtraukiau sym ir time, dabar initWith yra paruošta eilutė iš galutinės suvestinės lentelės, kurioje belieka nustatyti teisingą sym ir laiką. Jį galite naudoti norėdami į lentelę įtraukti naujų eilučių.

Kuriant agregavimo funkciją mums reikės aggCols. Sąrašas turi būti apverstas dėl Q išraiškų vertinimo tvarkos (iš dešinės į kairę). Tikslas yra užtikrinti, kad skaičiavimas būtų nuo didelio iki cumVolume, nes kai kurie stulpeliai priklauso nuo ankstesnių.

Stulpeliai, kuriuos reikia nukopijuoti į naują minutę iš ankstesnio, patogumui pridedamas simbolis:

rollColumns:`sym`cumVolume;

Dabar suskirstykime stulpelius į grupes pagal tai, kaip jie turėtų būti atnaujinami. Galima išskirti tris tipus:

  1. Akumuliatoriai (apimtis, apyvarta,..) – gaunamą vertę turime pridėti prie ankstesnės.
  2. Su specialiu tašku (aukšta, žema, ..) – pirmoji minutės reikšmė paimama iš gaunamų duomenų, likusi dalis apskaičiuojama naudojant funkciją.
  3. Poilsis. Visada apskaičiuojama naudojant funkciją.

Apibrėžkime šių klasių kintamuosius:

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

Skaičiavimo tvarka

Suvestinę lentelę atnaujinsime dviem etapais. Siekiant efektyvumo, pirmiausia sutraukiame gaunamą lentelę, kad kiekvienam simboliui ir minutei būtų tik viena eilutė. Tai, kad visos mūsų funkcijos yra laipsniškos ir asociatyvios, garantuoja, kad šio papildomo žingsnio rezultatas nepasikeis. Galite sumažinti lentelę pasirinkdami:

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

Šis metodas turi trūkumą - apskaičiuotų stulpelių rinkinys yra iš anksto nustatytas. Laimei, Q programoje Select taip pat įdiegtas kaip funkcija, kuria galite pakeisti dinamiškai sukurtus argumentus:

?[table;whereClause;byClause;selectClause]

Argumentų formato detaliai neaprašysiu, mūsų atveju tik by ir select posakiai bus netrivialūs ir tai turėtų būti formų stulpelių!reiškinių žodynai. Taigi susitraukimo funkciją galima apibrėžti taip:

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

Aiškumo dėlei panaudojau parse funkciją, kuri eilutę su Q išraiška paverčia reikšme, kurią galima perduoti funkcijai eval ir kuri reikalinga funkcijai select. Taip pat atkreipkite dėmesį, kad išankstinis apdorojimas apibrėžiamas kaip pasirinkimo funkcijos projekcija (t. y. funkcija su iš dalies apibrėžtais argumentais), trūksta vieno argumento (lentelės). Jei lentelei pritaikysime išankstinį apdorojimą, gausime suspaustą lentelę.

Antrasis etapas – suvestinės lentelės atnaujinimas. Pirmiausia parašykime algoritmą pseudokodu:

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, vietoj kilpų įprasta naudoti žemėlapio / mažinimo funkcijas. Bet kadangi Q yra vektorinė kalba ir mes galime lengvai pritaikyti visas operacijas visiems simboliams vienu metu, tada pagal pirmąjį aproksimaciją galime apsieiti visai be ciklo, atlikdami operacijas su visais simboliais iš karto:

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

Tačiau galime eiti toliau, Q turi unikalų ir itin galingą operatorių – apibendrintą priskyrimo operatorių. Tai leidžia keisti reikšmių rinkinį sudėtingoje duomenų struktūroje naudojant indeksų, funkcijų ir argumentų sąrašą. Mūsų atveju tai atrodo taip:

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

Deja, norint priskirti lentelę, jums reikia eilučių, o ne stulpelių sąrašo, ir jūs turite perkelti matricą (stulpelių sąrašą į eilučių sąrašą) naudodami apvertimo funkciją. Tai brangu didelėje lentelėje, todėl kiekvienam stulpeliui atskirai pritaikome apibendrintą priskyrimą, naudodami žemėlapio funkciją (kuri atrodo kaip apostrofas):

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

Vėl naudojame funkcijų projekciją. Taip pat atkreipkite dėmesį, kad programoje Q sąrašo kūrimas taip pat yra funkcija ir mes galime jį iškviesti naudodami funkciją kiekvienas (žemėlapis), kad gautume sąrašų sąrašą.

Norėdami užtikrinti, kad apskaičiuotų stulpelių rinkinys nebūtų fiksuotas, aukščiau pateiktą išraišką sukursime dinamiškai. Pirmiausia apibrėžkime funkcijas, skirtas kiekvienam stulpeliui apskaičiuoti, naudodami eilutės ir įvesties kintamuosius, kad būtų nurodyti suvestiniai ir įvesties duomenys:

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

Kai kurie stulpeliai yra specialūs, jų pirmoji reikšmė neturėtų būti skaičiuojama pagal funkciją. Mes galime nustatyti, kad jis yra pirmasis pagal eilutės [`numTrades] stulpelį – jei jame yra 0, tada reikšmė yra pirmoji. Q turi pasirinkimo funkciją – ?[Boolean list;list1;list2], kuri pasirenka reikšmę iš 1 arba 2 sąrašo, atsižvelgiant į sąlygą pirmame argumente:

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

Čia aš pavadinau apibendrintą užduotį su savo funkcija (išraiška garbanotuose petnešose). Jis gauna dabartinę reikšmę (pirmasis argumentas) ir papildomą argumentą, kurį perduodu 4-ame parametre.

Atskirai pridėkime akumuliatoriaus garsiakalbius, nes jų funkcija ta pati:

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

Tai yra įprastas priskyrimas pagal Q standartus, bet aš priskiriu verčių sąrašą iš karto. Galiausiai sukurkime pagrindinę funkciją:

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

Naudodamas šią išraišką aš dinamiškai sukuriu funkciją iš eilutės, kurioje yra aukščiau pateikta išraiška. Rezultatas atrodys taip:

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

Stulpelio vertinimo tvarka yra apversta, nes Q vertinimo tvarka yra iš dešinės į kairę.

Dabar turime dvi pagrindines skaičiavimams reikalingas funkcijas, tereikia pridėti šiek tiek infrastruktūros ir paslauga paruošta.

Paskutiniai žingsniai

Turime išankstinio apdorojimo ir atnaujinimo Agg funkcijas, kurios atlieka visą darbą. Tačiau vis tiek būtina užtikrinti teisingą perėjimą per minutes ir apskaičiuoti agregavimo indeksus. Visų pirma, apibrėžkime init funkciją:

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
 }

Taip pat apibrėšime ritinio funkciją, kuri pakeis esamą minutę:

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

Norėdami pridėti naujų simbolių, mums reikės funkcijos:

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

Ir galiausiai, funkcija upd (tradicinis šios funkcijos pavadinimas Q paslaugoms), kurią klientas iškviečia, kad pridėtų duomenis:

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

Tai viskas. Štai visas mūsų paslaugos kodas, kaip buvo žadėta, tik kelios eilutės:

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

Bandymai

Patikrinkime paslaugos veikimą. Norėdami tai padaryti, paleiskite jį atskiru procesu (įdėkite kodą į failą service.q) ir iškvieskime init funkciją:

q service.q –p 5566

q)init[]

Kitoje konsolėje paleiskite antrąjį Q procesą ir prisijunkite prie pirmojo:

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

Pirmiausia sukurkime simbolių sąrašą – 10000 XNUMX vienetų ir pridėkime funkciją, kad sukurtume atsitiktinę lentelę. Antroje konsolėje:

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

Į sąrašą įtraukiau tris tikrus simbolius, kad būtų lengviau juos ieškoti lentelėje. Funkcija rnd sukuria atsitiktinę lentelę su n eilučių, kur laikas svyruoja nuo t iki t+25 milisekundžių.

Dabar galite pabandyti siųsti duomenis į paslaugą (pridėkite pirmąsias dešimt valandų):

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

Tarnyboje galite patikrinti, ar lentelė atnaujinta:

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

Rezultatas:

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

Dabar atlikime apkrovos testavimą, kad sužinotume, kiek duomenų paslauga gali apdoroti per minutę. Leiskite jums priminti, kad mes nustatėme atnaujinimo intervalą iki 25 milisekundžių. Atitinkamai, paslauga turi (vidutiniškai) tilpti į mažiausiai 20 milisekundžių vienam atnaujinimui, kad naudotojai turėtų laiko prašyti duomenų. Antrame procese įveskite:

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 yra dvi minutės. Pirmiausia galite pabandyti paleisti 1000 eilučių kas 25 milisekundes:

start 1000

Mano atveju, vieno atnaujinimo rezultatas yra maždaug pora milisekundžių. Taigi tuoj pat padidinsiu eilučių skaičių iki 10.000 XNUMX:

start 10000

Rezultatas:

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

Vėlgi, nieko ypatingo, bet tai yra 24 milijonai eilučių per minutę, 400 tūkstančių per sekundę. Daugiau nei 25 milisekundes atnaujinimas sulėtėjo tik 5 kartus, matyt, pasikeitus minutei. Padidinkime iki 100.000 XNUMX:

start 100000

Rezultatas:

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

Kaip matote, tarnyba vos susidoroja, bet vis dėlto sugeba išsilaikyti. Toks duomenų kiekis (240 mln. eilučių per minutę) yra itin didelis, tokiais atvejais įprasta paleisti kelis paslaugos klonus (ar net keliasdešimt klonų), kurių kiekvienas apdoroja tik dalį simbolių. Vis dėlto rezultatas yra įspūdingas interpretuojamai kalbai, kuri daugiausia dėmesio skiria duomenų saugojimui.

Gali kilti klausimas, kodėl laikas auga netiesiškai su kiekvieno atnaujinimo dydžiu. Priežastis ta, kad susitraukimo funkcija iš tikrųjų yra C funkcija, kuri yra daug efektyvesnė nei updateAgg. Pradedant nuo tam tikro atnaujinimo dydžio (apie 10.000 30), updateAgg pasiekia lubas ir tada jo vykdymo laikas nepriklauso nuo atnaujinimo dydžio. Būtent dėl ​​preliminaraus žingsnio Q paslauga gali suvirškinti tokius duomenų kiekius. Tai pabrėžia, kaip svarbu pasirinkti tinkamą algoritmą dirbant su dideliais duomenimis. Kitas dalykas yra teisingas duomenų saugojimas atmintyje. Jei duomenys nebūtų saugomi stulpeliais arba nebūtų suskirstyti pagal laiką, mes susipažintume su tokiu dalyku kaip TLB talpyklos praleidimas - atminties puslapio adreso nebuvimas procesoriaus adreso talpykloje. Adreso paieška nesėkmingai užtrunka apie XNUMX kartų ilgiau, o jei duomenys yra išbarstyti, tai gali kelis kartus sulėtinti paslaugą.

išvada

Šiame straipsnyje parodžiau, kad KDB+ ir Q duomenų bazės yra tinkamos ne tik dideliems duomenims saugoti ir lengvai prieiti per Select, bet ir duomenų apdorojimo paslaugoms, galinčioms suvirškinti šimtus milijonų eilučių/gigabaitų duomenų, kurti. vienas Q procesas. Pati Q kalba leidžia itin glaustai ir efektyviai įgyvendinti su duomenų apdorojimu susijusius algoritmus dėl savo vektorinės prigimties, įmontuoto SQL dialekto interpretatoriaus ir labai sėkmingo bibliotekos funkcijų rinkinio.

Pastebėsiu, kad tai, kas išdėstyta aukščiau, yra tik dalis to, ką gali padaryti Q, jis turi ir kitų unikalių savybių. Pavyzdžiui, itin paprastas IPC protokolas, kuris ištrina ribą tarp atskirų Q procesų ir leidžia sujungti šimtus šių procesų į vieną tinklą, kuris gali būti išdėstytas dešimtyse serverių įvairiose pasaulio vietose.

Šaltinis: www.habr.com

Добавить комментарий