Nodweddion yr iaith Q a KDB+ gan ddefnyddio'r enghraifft o wasanaeth amser real

Gallwch ddarllen beth yw sylfaen KDB+, yr iaith raglennu Q, beth yw eu cryfderau a'u gwendidau yn fy nghynghrair Erthygl ac yn fyr yn y rhagymadrodd. Yn yr erthygl, byddwn yn gweithredu gwasanaeth ar Q a fydd yn prosesu'r llif data sy'n dod i mewn ac yn cyfrifo amrywiol swyddogaethau agregu bob munud yn y modd “amser real” (hy, bydd ganddo amser i gyfrifo popeth cyn y gyfran nesaf o ddata). Prif nodwedd Q yw ei fod yn iaith fector sy'n eich galluogi i weithredu nid gyda gwrthrychau sengl, ond gyda'u haraeau, araeau o araeau a gwrthrychau cymhleth eraill. Mae ieithoedd fel Q a'i berthnasau K, J, APL yn enwog am eu crynoder. Yn aml, gellir ysgrifennu rhaglen sy'n cymryd sawl sgrin o god mewn iaith gyfarwydd fel Java arnynt mewn ychydig linellau. Dyma beth rydw i eisiau ei ddangos yn yr erthygl hon.

Nodweddion yr iaith Q a KDB+ gan ddefnyddio'r enghraifft o wasanaeth amser real

Cyflwyniad

Mae KDB+ yn gronfa ddata golofnog sy'n canolbwyntio ar symiau mawr iawn o ddata, wedi'i archebu mewn ffordd benodol (yn ôl amser yn bennaf). Fe'i defnyddir yn bennaf mewn sefydliadau ariannol - banciau, cronfeydd buddsoddi, cwmnïau yswiriant. Yr iaith Q yw iaith fewnol KDB+ sy'n eich galluogi i weithio'n effeithiol gyda'r data hwn. Yr ideoleg Q yw crynoder ac effeithlonrwydd, tra bod eglurder yn cael ei aberthu. Mae hyn yn cael ei gyfiawnhau gan y ffaith y bydd yr iaith fector yn anodd ei deall beth bynnag, ac mae crynoder a chyfoeth y recordiad yn caniatáu ichi weld rhan lawer mwy o'r rhaglen ar un sgrin, sydd yn y pen draw yn ei gwneud hi'n haws ei deall.

Yn yr erthygl hon rydym yn gweithredu rhaglen Q llawn ac efallai y byddwch am roi cynnig arni. I wneud hyn, bydd angen Q ei hun arnoch. Gallwch lawrlwytho'r fersiwn 32-bit am ddim ar wefan y cwmni kx - www.kx.com. Yno, os oes gennych ddiddordeb, fe welwch wybodaeth gyfeirio ar Q, y llyfr Q Am Farwolion ac amryw erthyglau ar y pwnc hwn.

Datganiad o'r broblem

Mae yna ffynhonnell sy'n anfon tabl gyda data bob 25 milieiliad. Gan fod KDB+ yn cael ei ddefnyddio'n bennaf mewn cyllid, byddwn yn cymryd mai tabl o drafodion (masnachau) yw hwn, sydd â'r colofnau canlynol: amser (amser mewn milieiliadau), sym (dynodiad cwmni ar y gyfnewidfa stoc - IBM, AAPL,…), pris (y pris y prynwyd y cyfranddaliadau amdano), maint (maint y trafodiad). Mae'r cyfwng 25 milieiliad yn fympwyol, heb fod yn rhy fach ac nid yn rhy hir. Mae ei bresenoldeb yn golygu bod y data yn dod i'r gwasanaeth sydd eisoes wedi'i glustogi. Byddai'n hawdd gweithredu byffro ar ochr y gwasanaeth, gan gynnwys byffro deinamig yn dibynnu ar y llwyth presennol, ond er mwyn symlrwydd, byddwn yn canolbwyntio ar gyfwng sefydlog.

Rhaid i'r gwasanaeth gyfrif bob munud ar gyfer pob symbol sy'n dod i mewn o'r golofn sym, set o swyddogaethau agregu - pris uchaf, pris cyfartalog, maint swm, ac ati. gwybodaeth ddefnyddiol. Er mwyn symlrwydd, byddwn yn cymryd yn ganiataol y gellir cyfrifo pob swyddogaeth yn gynyddrannol, h.y. i gael gwerth newydd, mae'n ddigon gwybod dau rif - yr hen a'r gwerthoedd sy'n dod i mewn. Er enghraifft, mae gan y swyddogaethau uchafswm, cyfartaledd, swm yr eiddo hwn, ond nid oes gan y swyddogaeth ganolrifol.

Byddwn hefyd yn cymryd yn ganiataol bod y ffrwd data sy'n dod i mewn yn ôl trefn amser. Bydd hyn yn rhoi cyfle i ni weithio gyda'r funud olaf yn unig. Yn ymarferol, mae'n ddigon gallu gweithio gyda'r cofnodion cyfredol a blaenorol rhag ofn y bydd rhai diweddariadau yn hwyr. Er mwyn symlrwydd, ni fyddwn yn ystyried yr achos hwn.

Swyddogaethau agregu

Rhestrir y swyddogaethau agregu gofynnol isod. Cymerais gynifer ohonynt â phosibl i gynyddu'r llwyth ar y gwasanaeth:

  • uchel – pris uchaf – pris uchaf y funud.
  • isel – pris isaf – isafswm pris y funud.
  • pris cyntaf – pris cyntaf – pris cyntaf y funud.
  • Pris olaf - pris olaf - pris olaf y funud.
  • Maint cyntaf - maint cyntaf - maint masnach cyntaf y funud.
  • lastSize - maint olaf - maint masnach olaf mewn munud.
  • numTrades – cyfrif i – nifer y crefftau y funud.
  • cyfaint - swm maint - swm y meintiau masnach y funud.
  • pvolume - swm pris - swm y prisiau y funud, sy'n ofynnol ar gyfer avgPrice.
  • – pris trosiant swm* maint – cyfanswm nifer y trafodion y funud.
  • avgPrice – pvolume%numTrades – pris cyfartalog y funud.
  • avgSize – cyfaint%numTrades – maint masnach cyfartalog y funud.
  • vwap – trosiant% cyfaint – pris cyfartalog y funud wedi'i bwysoli yn ôl maint y trafodiad.
  • cumVolume – cyfaint swm – maint cronedig trafodion dros yr amser cyfan.

Gadewch i ni drafod yn syth un pwynt nad yw'n amlwg - sut i gychwyn y colofnau hyn am y tro cyntaf ac am bob munud dilynol. Rhaid cychwyn rhai colofnau o'r math FirstPrice i null bob tro; nid yw eu gwerth wedi'i ddiffinio. Rhaid gosod mathau eraill o gyfrol bob amser i 0. Mae yna hefyd golofnau sy'n gofyn am ddull cyfunol - er enghraifft, rhaid copïo cumVolume o'r funud flaenorol, ac ar gyfer yr un cyntaf gosod i 0. Gadewch i ni osod yr holl baramedrau hyn gan ddefnyddio data'r geiriadur math (yn cyfateb i gofnod):

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

Ychwanegais sym ac amser at y geiriadur er hwylustod, yn awr mae initWith yn llinell barod o'r tabl cyfansymiol terfynol, lle mae'n aros i osod y sym a'r amser cywir. Gallwch ei ddefnyddio i ychwanegu rhesi newydd at dabl.

Bydd angen aggCols arnom wrth greu swyddogaeth agregu. Rhaid gwrthdroi'r rhestr oherwydd y drefn y mae mynegiadau yn Q yn cael eu gwerthuso (o'r dde i'r chwith). Y nod yw sicrhau bod y cyfrifiad yn mynd o uchel i cumVolume, gan fod rhai colofnau yn dibynnu ar y rhai blaenorol.

Colofnau y mae angen eu copïo i funud newydd o'r un blaenorol, ychwanegir y golofn sym er hwylustod:

rollColumns:`sym`cumVolume;

Nawr, gadewch i ni rannu'r colofnau yn grwpiau yn ôl sut y dylid eu diweddaru. Gellir gwahaniaethu rhwng tri math:

  1. Cronaduron (cyfaint, trosiant,..) – rhaid inni ychwanegu'r gwerth sy'n dod i mewn at yr un blaenorol.
  2. Gyda phwynt arbennig (uchel, isel, ..) - mae'r gwerth cyntaf yn y funud yn cael ei gymryd o'r data sy'n dod i mewn, mae'r gweddill yn cael eu cyfrifo gan ddefnyddio'r swyddogaeth.
  3. Gorffwys. Cyfrifir bob amser gan ddefnyddio ffwythiant.

Gadewch i ni ddiffinio newidynnau ar gyfer y dosbarthiadau hyn:

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

Gorchymyn cyfrifo

Byddwn yn diweddaru'r tabl cyfanredol mewn dau gam. Ar gyfer effeithlonrwydd, yn gyntaf rydym yn crebachu'r tabl sy'n dod i mewn fel mai dim ond un rhes sydd ar gyfer pob cymeriad a munud. Mae'r ffaith bod ein holl swyddogaethau yn gynyddrannol a chysylltiadol yn gwarantu na fydd canlyniad y cam ychwanegol hwn yn newid. Gallech chi grebachu'r tabl gan ddefnyddio dewis:

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

Mae gan y dull hwn anfantais - mae'r set o golofnau wedi'u cyfrifo wedi'u diffinio ymlaen llaw. Yn ffodus, yn Q, mae dewis hefyd yn cael ei weithredu fel swyddogaeth lle gallwch chi amnewid dadleuon a grëwyd yn ddeinamig:

?[table;whereClause;byClause;selectClause]

Ni fyddaf yn disgrifio fformat y dadleuon yn fanwl; yn ein hachos ni, dim ond trwy a dethol ymadroddion fydd yn ddibwys a dylent fod yn eiriaduron o fynegiadau colofnau! Felly, gellir diffinio'r swyddogaeth crebachu fel a ganlyn:

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

Er eglurder, defnyddiais y swyddogaeth dosrannu, sy'n troi llinyn â mynegiant Q yn werth y gellir ei drosglwyddo i'r swyddogaeth eval ac sy'n ofynnol yn y swyddogaeth dewis. Sylwch hefyd fod rhagbroses yn cael ei ddiffinio fel tafluniad (h.y., swyddogaeth â dadleuon wedi'u diffinio'n rhannol) o'r swyddogaeth ddethol, mae un ddadl (y tabl) ar goll. Os byddwn yn cymhwyso rhagbroses i fwrdd, byddwn yn cael bwrdd cywasgedig.

Yr ail gam yw diweddaru'r tabl cyfanredol. Yn gyntaf, gadewch i ni ysgrifennu'r algorithm mewn ffuggod:

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, mae'n gyffredin defnyddio ffwythiannau map/lleihau yn lle dolenni. Ond gan fod Q yn iaith fector a gallwn yn hawdd gymhwyso pob gweithrediad i bob symbol ar unwaith, yna i frasamcan cyntaf y gallwn ei wneud heb ddolen o gwbl, gan berfformio gweithrediadau ar bob symbol ar unwaith:

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

Ond gallwn fynd ymhellach, mae gan Q weithredwr unigryw a hynod bwerus - y gweithredwr aseiniadau cyffredinol. Mae'n caniatáu ichi newid set o werthoedd mewn strwythur data cymhleth gan ddefnyddio rhestr o fynegeion, swyddogaethau a dadleuon. Yn ein hachos ni mae'n edrych fel hyn:

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

Yn anffodus, i aseinio i dabl mae angen rhestr o resi, nid colofnau, a rhaid i chi drawsosod y matrics (rhestr o golofnau i restr o resi) gan ddefnyddio'r ffwythiant troi. Mae hyn yn ddrud ar gyfer tabl mawr, felly yn lle hynny rydym yn cymhwyso aseiniad cyffredinol i bob colofn ar wahân, gan ddefnyddio'r ffwythiant map (sy'n edrych fel collnod):

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

Rydym eto'n defnyddio rhagamcaniad swyddogaeth. Sylwch hefyd, yn Q, bod creu rhestr hefyd yn swyddogaeth a gallwn ei alw gan ddefnyddio'r swyddogaeth pob (map) i gael rhestr o restrau.

Er mwyn sicrhau nad yw'r set o golofnau wedi'u cyfrifo yn sefydlog, byddwn yn creu'r mynegiant uchod yn ddeinamig. Yn gyntaf, gadewch i ni ddiffinio swyddogaethau i gyfrifo pob colofn, gan ddefnyddio'r newidynnau rhes ac inp i gyfeirio at y data cyfanredol a mewnbwn:

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

Mae rhai colofnau yn arbennig; ni ddylai eu gwerth cyntaf gael ei gyfrifo gan y ffwythiant. Gallwn benderfynu mai dyma'r gyntaf wrth y golofn rhes [`numTrades] - os yw'n cynnwys 0, yna'r gwerth sydd gyntaf. Mae gan Q swyddogaeth ddethol - ?[rhestr Boole; rhestr1; rhestr2] - sy'n dewis gwerth o restr 1 neu 2 yn dibynnu ar yr amod yn y ddadl gyntaf:

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

Yma gelwais aseiniad cyffredinol gyda fy swyddogaeth (mynegiant mewn braces cyrliog). Mae'n derbyn y gwerth cyfredol (y ddadl gyntaf) a dadl ychwanegol, yr wyf yn ei phasio yn y 4ydd paramedr.

Gadewch i ni ychwanegu siaradwyr batri ar wahân, gan fod y swyddogaeth yr un peth ar eu cyfer:

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

Mae hwn yn aseiniad arferol yn ôl safonau Q, ond rwy'n aseinio rhestr o werthoedd ar unwaith. Yn olaf, gadewch i ni greu'r prif swyddogaeth:

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

Gyda'r mynegiant hwn, rydw i'n creu ffwythiant o linyn sy'n cynnwys y mynegiant a roddais uchod yn ddeinamig. Bydd y canlyniad yn edrych fel hyn:

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

Mae trefn werthuso'r golofn wedi'i gwrthdroi oherwydd yn Q mae'r gorchymyn gwerthuso o'r dde i'r chwith.

Nawr mae gennym ni ddwy brif swyddogaeth sy'n angenrheidiol ar gyfer cyfrifiadau, does ond angen i ni ychwanegu ychydig o seilwaith ac mae'r gwasanaeth yn barod.

Camau terfynol

Mae gennym swyddogaethau preprocess a updateAgg sy'n gwneud yr holl waith. Ond mae'n dal yn angenrheidiol sicrhau'r trosglwyddiad cywir trwy funudau a chyfrifo mynegeion ar gyfer agregu. Yn gyntaf oll, gadewch i ni ddiffinio'r swyddogaeth 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
 }

Byddwn hefyd yn diffinio swyddogaeth y gofrestr, a fydd yn newid y funud gyfredol:

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

Bydd angen swyddogaeth i ychwanegu nodau newydd:

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

Ac yn olaf, y swyddogaeth upd (yr enw traddodiadol ar gyfer y swyddogaeth hon ar gyfer gwasanaethau Q), a elwir gan y cleient i ychwanegu 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]; // обновим агрегированную таблицу. Функция ? ищет индекс элементов списка справа в списке слева.
 };

Dyna i gyd. Dyma god cyflawn ein gwasanaeth, fel yr addawyd, dim ond ychydig linellau:

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

Profi

Gadewch i ni wirio perfformiad y gwasanaeth. I wneud hyn, gadewch i ni ei redeg mewn proses ar wahân (rhowch y cod yn y ffeil service.q) a ffoniwch y swyddogaeth init:

q service.q –p 5566

q)init[]

Mewn consol arall, dechreuwch yr ail broses Q a chysylltwch â'r cyntaf:

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

Yn gyntaf, gadewch i ni greu rhestr o symbolau - 10000 o ddarnau ac ychwanegu swyddogaeth i greu tabl ar hap. Yn yr ail gonsol:

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

Ychwanegais dri symbol go iawn at y rhestr i'w gwneud hi'n haws chwilio amdanynt yn y tabl. Mae'r ffwythiant rnd yn creu tabl ar hap gyda n rhesi, lle mae'r amser yn amrywio o t i t+25 milieiliad.

Nawr gallwch geisio anfon data i'r gwasanaeth (ychwanegwch y deg awr gyntaf):

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

Gallwch wirio yn y gwasanaeth bod y tabl wedi'i ddiweddaru:

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

Canlyniad:

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

Gadewch i ni nawr gynnal profion llwyth i ddarganfod faint o ddata y gall y gwasanaeth ei brosesu fesul munud. Gadewch imi eich atgoffa ein bod wedi gosod yr egwyl diweddaru i 25 milieiliad. Yn unol â hynny, rhaid i'r gwasanaeth (ar gyfartaledd) ffitio i mewn i o leiaf 20 milieiliad y diweddariad i roi amser i ddefnyddwyr ofyn am ddata. Rhowch y canlynol yn yr ail broses:

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

Mae 4800 yn ddau funud. Gallwch geisio rhedeg yn gyntaf am 1000 o resi bob 25 milieiliad:

start 1000

Yn fy achos i, mae'r canlyniad tua cwpl o filieiliadau fesul diweddariad. Felly byddaf yn cynyddu nifer y rhesi ar unwaith i 10.000:

start 10000

Canlyniad:

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

Unwaith eto, dim byd arbennig, ond mae hyn yn 24 miliwn o linellau y funud, 400 mil yr eiliad. Am fwy na 25 milieiliad, arafodd y diweddariad 5 gwaith yn unig, mae'n debyg pan newidiodd y funud. Gadewch i ni gynyddu i 100.000:

start 100000

Canlyniad:

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

Fel y gwelwch, prin y gall y gwasanaeth ymdopi, ond serch hynny mae'n llwyddo i aros i fynd. Mae cyfaint o ddata o'r fath (240 miliwn o resi y funud) yn hynod o fawr; mewn achosion o'r fath, mae'n gyffredin lansio sawl clon (neu hyd yn oed dwsinau o glonau) o'r gwasanaeth, gyda phob un ohonynt yn prosesu rhan o'r cymeriadau yn unig. Eto i gyd, mae'r canlyniad yn drawiadol ar gyfer iaith ddehongli sy'n canolbwyntio'n bennaf ar storio data.

Efallai y bydd y cwestiwn yn codi pam mae amser yn tyfu'n aflinol gyda maint pob diweddariad. Y rheswm yw bod y swyddogaeth crebachu mewn gwirionedd yn swyddogaeth C, sy'n llawer mwy effeithlon na updateAgg. Gan ddechrau o faint diweddaru penodol (tua 10.000), mae updateAgg yn cyrraedd ei nenfwd ac yna nid yw ei amser gweithredu yn dibynnu ar faint y diweddariad. Oherwydd y cam rhagarweiniol Q mae'r gwasanaeth yn gallu treulio'r fath symiau o ddata. Mae hyn yn amlygu pa mor bwysig yw hi i ddewis yr algorithm cywir wrth weithio gyda data mawr. Pwynt arall yw storio data yn gywir yn y cof. Pe na bai'r data'n cael ei storio'n golofnog neu heb ei archebu yn ôl amser, yna byddem yn dod yn gyfarwydd â'r fath beth â cholli storfa TLB - absenoldeb cyfeiriad tudalen cof yn storfa cyfeiriad y prosesydd. Mae chwilio am gyfeiriad yn cymryd tua 30 gwaith yn hirach os yw'n aflwyddiannus, ac os yw'r data'n wasgaredig, gall arafu'r gwasanaeth sawl gwaith.

Casgliad

Yn yr erthygl hon, dangosais fod y gronfa ddata KDB+ a Q yn addas nid yn unig ar gyfer storio data mawr a'i gyrchu'n hawdd trwy ddethol, ond hefyd ar gyfer creu gwasanaethau prosesu data sy'n gallu treulio cannoedd o filiynau o resi / gigabeit o ddata hyd yn oed mewn un broses Q sengl. Mae'r iaith Q ei hun yn caniatáu ar gyfer gweithredu algorithmau sy'n ymwneud â phrosesu data yn hynod gryno ac effeithlon oherwydd ei natur fector, cyfieithydd tafodiaith SQL adeiledig a set lwyddiannus iawn o swyddogaethau llyfrgell.

Sylwaf mai dim ond rhan o'r hyn y gall Q ei wneud yw'r uchod, mae ganddo nodweddion unigryw eraill hefyd. Er enghraifft, protocol IPC hynod o syml sy'n dileu'r ffin rhwng prosesau Q unigol ac yn caniatáu ichi gyfuno cannoedd o'r prosesau hyn yn un rhwydwaith, y gellir eu lleoli ar ddwsinau o weinyddion mewn gwahanol rannau o'r byd.

Ffynhonnell: hab.com

Ychwanegu sylw