Реалдуу убакыт кызматынын мисалында Q жана KDB+ тилинин өзгөчөлүктөрү

KDB+ базасы, Q программалоо тили эмне, алардын күчтүү жана алсыз жактары кандай экенин менин мурунку макаламдан окуй аласыз. макала жана кыскача кириш сөзүндө. Макалада биз Q кызматын ишке ашырабыз, ал келип түшкөн маалымат агымын иштетип, ар бир мүнөт сайын "реалдуу убакыт" режиминде ар кандай топтоо функцияларын эсептейт (б.а. маалыматтардын кийинки бөлүгүнө чейин баарын эсептеп чыгууга убакыт болот). Q тилинин негизги өзгөчөлүгү - бул жалгыз объекттер менен эмес, алардын массивдери, массивдердин массивдери жана башка татаал объекттер менен иштөөгө мүмкүндүк берген вектордук тил. Q жана анын туугандары K, J, APL сыяктуу тилдер кыскалыгы менен белгилүү. Көп учурда Java сыяктуу тааныш тилде коддун бир нече экранын ээлеген программа аларга бир нече сапта жазылышы мүмкүн. Бул макалада мен көрсөткүм келет.

Реалдуу убакыт кызматынын мисалында Q жана KDB+ тилинин өзгөчөлүктөрү

тааныштыруу

KDB+ – бул белгилүү бир жол менен иреттелген (биринчи кезекте убакыт боюнча) өтө чоң көлөмдөгү маалыматтарга багытталган мамычалык маалымат базасы. Ал биринчи кезекте каржы институттарында - банктарда, инвестициялык фонддордо, камсыздандыруу компанияларында колдонулат. Q тили бул маалыматтар менен эффективдүү иштөөгө мүмкүндүк берген KDB+тин ички тили. Q идеологиясы кыскалык жана натыйжалуулук, ал эми айкындык курмандыкка чалынган. Бул вектордук тилди кандай болгон күндө да түшүнүү кыйын боло тургандыгы менен негизделет, ал эми жаздыруунун кыскалыгы жана байлыгы программанын бир топ чоң бөлүгүн бир экрандан көрүүгө мүмкүндүк берет, бул акыры түшүнүүнү жеңилдетет.

Бул макалада биз Qда толук кандуу программаны ишке ашырабыз жана сиз аны сынап көргүңүз келет. Бул үчүн сизге чыныгы С керек болот. Сиз kx компаниясынын веб-сайтынан 32-биттик версиясын акысыз жүктөп алсаңыз болот – www.kx.com. Ал жерден, эгер сизди кызыктырсаңыз, анда Q, китеп боюнча маалымдама маалымат таба аласыз Q For Mortals жана бул тема боюнча ар кандай макалалар.

Тапшырманын коюлушу

Ар бир 25 миллисекундда маалыматтар менен таблица жөнөтүүчү булак бар. KDB+ негизинен каржы тармагында колдонулгандыктан, бул транзакциялардын (соодалардын) таблицасы деп ойлойбуз, анда төмөнкү тилкелер бар: убакыт (убакыт миллисекунд менен), сим (биржада компаниянын белгилениши - IBM, AAPL,...), баасы (акциялар сатылып алынган баа), өлчөмү (бүтүмдүн өлчөмү). 25 миллисекунддук интервал ыктыярдуу, өтө кичинекей жана өтө узун эмес. Анын болушу маалыматтар буга чейин буферленген кызматка келет дегенди билдирет. Учурдагы жүктөөгө жараша динамикалык буферлөөнү кошо алганда, тейлөө жагында буферлөө оңой болмок, бирок жөнөкөйлүк үчүн биз белгиленген интервалга көңүл бурабыз.

Кызмат ар бир мүнөттү сим мамычасынан келген ар бир символ үчүн бириктирүүчү функциялардын топтомун эсептеп чыгышы керек - максималдуу баа, орточо баа, сумманын өлчөмү ж.б. пайдалуу маалымат. Жөнөкөйлүк үчүн, биз бардык функцияларды кадам сайын эсептөөгө болот деп ойлойбуз, б.а. жаңы мааниге ээ болуу үчүн эки санды билүү жетиштүү - эски жана кирген маанилер. Мисалы, max, орточо, sum функцияларында мындай касиет бар, бирок медианалык функцияда жок.

Ошондой эле, биз кирген маалымат агымы убакыт тартиби деп ойлойбуз. Бул бизге акыркы мүнөттөр менен гана иштөөгө мүмкүнчүлүк берет. Иш жүзүндө, кээ бир жаңыртуулар кечиккен учурда учурдагы жана мурунку мүнөттөр менен иштей билүү жетиштүү. Жөнөкөйлүк үчүн биз бул ишти карабайбыз.

Агрегация функциялары

Керектүү бириктирүү функциялары төмөндө келтирилген. Кызматтын жүгүн жогорулатуу үчүн мен алардын мүмкүн болушунча көбүн алдым:

  • жогорку – максималдуу баа – мүнөтүнө максималдуу баа.
  • төмөн – мин баасы – мүнөтүнө минималдуу баа.
  • firstPrice – биринчи баа – мүнөтүнө биринчи баа.
  • lastPrice – акыркы баа – мүнөтүнө акыркы баа.
  • firstSize - биринчи өлчөмү - мүнөтүнө биринчи соода көлөмү.
  • lastSize - акыркы өлчөмү - бир мүнөттө акыркы соода көлөмү.
  • numTrades – count i – мүнөтүнө соодалардын саны.
  • көлөмү – сумма өлчөмү – мүнөтүнө соода өлчөмдөрүнүн суммасы.
  • pvolume – сумма баасы – орточо баа үчүн талап кылынган мүнөтүнө баалардын суммасы.
  • – жүгүртүүнүн суммасынын баасы*өлчөмү – мүнөтүнө транзакциялардын жалпы көлөмү.
  • avgPrice – pvolume%numTrades – мүнөтүнө орточо баа.
  • avgSize – көлөмү%numTrades – мүнөтүнө орточо соода көлөмү.
  • vwap – жүгүртүү% көлөмү – транзакциянын өлчөмү боюнча салмактанып алынган мүнөтүнө орточо баа.
  • cumVolume – сумма көлөмү – бүткүл убакыттын ичинде бүтүмдөрдүн топтолгон өлчөмү.

Келгиле, дароо бир ачык-айкын эмес жагдайды талкуулайлы - бул тилкелерди биринчи жолу жана ар бир кийинки мүнөт үчүн кантип инициализациялоо керек. FirstPrice түрүндөгү кээ бир тилкелер ар бир жолу нөлгө инициализацияланышы керек; алардын мааниси аныкталбайт. Көлөмдүн башка түрлөрү ар дайым 0гө коюлушу керек. Ошондой эле айкалыштырылган мамилени талап кылган мамычалар бар - мисалы, cumVolume мурунку мүнөттөн көчүрүлүшү керек, ал эми биринчиси үчүн 0 болушу керек. Келгиле, бул параметрлердин баарын сөздүк маалыматтарын колдонуп орнотуп алалы түрү (жазууга окшош):

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

Ыңгайлуу болуу үчүн мен сөздүккө сим жана убакыт коштум, эми initWith акыркы топтолгон таблицадан даяр сап болуп саналат, ал жерде туура сим жана убакытты коюу керек. Аны таблицага жаңы саптарды кошуу үчүн колдоно аласыз.

Агрегация функциясын түзүүдө бизге aggCols керек болот. Q тилиндеги туюнтмалар бааланган тартипте (оңдон солго) улам тизме тескери болушу керек. Максаты, кээ бир мамычалар мурункусунан көз каранды болгондуктан, эсептөөнүн жогоркудан cumVolumeге өтүшүн камсыз кылуу.

Мурункудан жаңы мүнөткө көчүрүлүшү керек болгон тилкелер, ыңгайлуулук үчүн сим мамычасы кошулат:

rollColumns:`sym`cumVolume;

Эми мамычаларды кантип жаңыртуу керектиги боюнча топторго бөлүп көрөлү. үч түрүн айырмалоого болот:

  1. Аккумуляторлор (көлөмү, жүгүртүүсү, ..) – биз мурункуга кирген маанини кошуубуз керек.
  2. Өзгөчө чекит менен (жогорку, төмөн, ..) – мүнөттүн биринчи мааниси келип түшкөн маалыматтардан алынат, калгандары функциянын жардамы менен эсептелет.
  3. Эс алуу. Ар дайым функциянын жардамы менен эсептелинет.

Бул класстар үчүн өзгөрмөлөрдү аныктайлы:

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

Эсептөө тартиби

Биз эки этапта топтолгон таблицаны жаңылайбыз. Натыйжалуулук үчүн, биз алгач келген таблицаны ар бир белги жана мүнөт үчүн бир гана сап болушу үчүн кичирейтебиз. Биздин бардык функциялар кошумча жана ассоциативдик болушу бул кошумча кадамдын натыйжасы өзгөрбөй тургандыгына кепилдик берет. Сиз тандоону колдонуп таблицаны кичирейте аласыз:

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

Бул ыкманын кемчилиги бар - эсептелген мамычалардын жыйындысы алдын ала аныкталган. Бактыга жараша, Qда тандоо динамикалык түрдө түзүлгөн аргументтерди алмаштыра турган функция катары ишке ашырылат:

?[table;whereClause;byClause;selectClause]

Мен аргументтердин форматын майда-чүйдөсүнө чейин сүрөттөбөй эле коёюн, биздин учурда, бир гана жана тандаган туюнтмалар маанилүү эмес болуп калат жана алар форма мамычалары! Ошентип, кичирейтүү функциясын төмөнкүчө аныктоого болот:

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

Түшүнүктүү болуу үчүн мен Q туюнтмасы бар сапты баалоо функциясына бериле турган жана функцияны тандоодо талап кылынган мааниге айландырган талдоо функциясын колдондум. Ошондой эле, препроцесс тандоо функциясынын проекциясы (б.а. жарым-жартылай аныкталган аргументтери бар функция) катары аныкталганын, бир аргумент (таблица) жок экенин эске алыңыз. Эгерде биз үстөлгө алдын ала процессти колдонсок, кысылган таблицага ээ болобуз.

Экинчи этап - топтолгон таблицаны жаңылоо. Алгач алгоритмди псевдокоддо жазалы:

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да циклдердин ордуна карта/кичирейтүү функцияларын колдонуу кеңири таралган. Бирок Q вектордук тил болгондуктан жана биз бир эле учурда бардык символдорго бардык операцияларды оңой эле колдоно алабыз, анда биринчи жакындатууда биз циклсиз эле жасай алабыз, бир эле учурда бардык символдор боюнча операцияларды аткарабыз:

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

Бирок биз андан ары кете алабыз, Q уникалдуу жана өтө күчтүү оператору бар - жалпыланган дайындоо оператору. Бул индекстердин, функциялардын жана аргументтердин тизмесин колдонуу менен татаал маалымат структурасындагы баалуулуктардын топтомун өзгөртүүгө мүмкүндүк берет. Биздин учурда бул мындай көрүнөт:

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

Тилекке каршы, таблицага дайындоо үчүн сизге мамычалар эмес, саптардын тизмеси керек жана флип функциясын колдонуп, матрицаны (тилкелердин тизмесин саптардын тизмесине) которуу керек. Бул чоң таблица үчүн кымбат, ошондуктан анын ордуна карта функциясын колдонуу менен ар бир мамычага өзүнчө жалпыланган тапшырманы колдонобуз (ал апостроф сыяктуу):

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

Биз кайрадан функция проекциясын колдонобуз. Ошондой эле Q-да тизме түзүү дагы функция экенин жана биз тизмелердин тизмесин алуу үчүн every(map) функциясын колдонуп чакырсак болорун эске алыңыз.

Эсептелген тилкелердин жыйындысы бекитилбеши үчүн, биз жогорудагы туюнтманы динамикалык түрдө түзөбүз. Келгиле, алгач ар бир мамычаны эсептөө үчүн функцияларды аныктайлы, сап жана inp өзгөрмөлөрүнүн жардамы менен топтолгон жана киргизилген маалыматтарга кайрылалы:

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

Кээ бир мамычалар өзгөчө; алардын биринчи мааниси функция тарабынан эсептелбеши керек. Биз сап [`numTrades] тилкеси боюнча биринчи экенин аныктай алабыз - эгерде анда 0 болсо, анда маани биринчи. Q тандоо функциясына ээ - ?[Логикалык тизме; тизме1; тизме2] - биринчи аргументтеги шартка жараша 1 же 2 тизмеден маани тандайт:

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

Бул жерде мен өз функциясым менен жалпыланган тапшырманы чакырдым (тармал кашаадагы туюнтма). Ал учурдагы маанини (биринчи аргумент) жана мен 4-параметрде өткөргөн кошумча аргументти алат.

Батареянын динамиктерин өзүнчө кошолу, анткени функция алар үчүн бирдей:

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

Бул Q стандарттары боюнча кадимки тапшырма, бирок мен бир эле учурда баалуулуктардын тизмесин тапшырып жатам. Акырында, негизги функцияны түзөлү:

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

Бул туюнтма менен мен динамикалык түрдө жогоруда берген туюнтманы камтыган саптан функция түзөм. Натыйжа мындай болот:

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

Мамычаны баалоо тартиби тескериленген, анткени Qда баалоо ирети оңдон солго.

Азыр бизде эсептөөлөр үчүн зарыл болгон эки негизги функция бар, болгону бир аз инфраструктураны кошуш керек жана сервис даяр.

Акыркы кадамдар

Бизде бардык ишти аткарган preprocess жана updateAgg функциялары бар. Бирок дагы эле мүнөттөр аркылуу туура өтүүнү камсыз кылуу жана топтоо үчүн индекстерди эсептөө зарыл. Биринчиден, 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
 }

Биз ошондой эле учурдагы мүнөттү өзгөртө турган ролл функциясын аныктайбыз:

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

Жаңы белгилерди кошуу үчүн бизге функция керек болот:

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

Акыр-аягы, маалымат кошуу үчүн кардар тарабынан чакырылган upd функциясы (Q кызматтары үчүн бул функциянын салттуу аталышы):

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

Баары болду. Бул жерде биздин кызматтын толук коду, убада кылынгандай, бир нече саптар:

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

тестирлөө

Кызматтын иштешин текшерип көрөлү. Бул үчүн, келгиле, аны өзүнчө процессте иштетели (кодду service.q файлына коюңуз) жана init функциясын чакырыңыз:

q service.q –p 5566

q)init[]

Башка консолдо экинчи Q процессин баштаңыз жана биринчисине туташыңыз:

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

Биринчиден, символдордун тизмесин түзөлү - 10000 XNUMX даана жана кокустук таблицаны түзүү функциясын кошолу. Экинчи консолдо:

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

Таблицадан издөөнү жеңилдетүү үчүн тизмеге үч чыныгы символду коштум. rnd функциясы n катардан турган туш келди таблицаны түзөт, мында убакыт tден t+25 миллисекундка чейин өзгөрөт.

Эми сиз кызматка дайындарды жөнөтүүгө аракет кылсаңыз болот (биринчи он саатты кошуңуз):

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

Сиз кызматта таблица жаңыртылганын текшере аласыз:

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

жыйынтыгы:

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

Кызмат мүнөтүнө канча маалымат иштете аларын билүү үчүн жүктөмдү текшерүүнү жүргүзөлү. Эске сала кетейин, биз жаңыртуу аралыгын 25 миллисекундга койгонбуз. Демек, колдонуучуларга маалыматтарды суроого убакыт берүү үчүн кызмат (орто эсеп менен) жаңыртуу үчүн кеминде 20 миллисекундга туура келиши керек. Экинчи процессте төмөнкүлөрдү киргизиңиз:

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 эки мүнөт. Ар бир 1000 миллисекундда биринчи 25 сапка чуркасаңыз болот:

start 1000

Менин учурда, натыйжа бир жаңыртуу үчүн бир нече миллисекундду түзөт. Ошентип, мен дароо катарлардын санын 10.000 XNUMXге чейин көбөйтөм:

start 10000

жыйынтыгы:

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

Дагы, өзгөчө эч нерсе жок, бирок бул мүнөтүнө 24 миллион сап, секундасына 400 миң. 25 миллисекунддан ашык убакытка жаңыртуу 5 жолу гана жайлады, сыягы, мүнөт өзгөргөндө. 100.000 XNUMXге көбөйтөлү:

start 100000

жыйынтыгы:

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

Көрүнүп тургандай, кызмат эптеп туруштук бере алат, бирок ошентсе да, сүзүп кете алат. Маалыматтын мындай көлөмү (мүнөтүнө 240 миллион сап) өтө чоң, мындай учурларда кызматтын бир нече клондорун (ал тургай ондогон клондорун) ишке киргизүү кеңири таралган, алардын ар бири каармандардын бир бөлүгүн гана иштетет. Ошентсе да, натыйжа биринчи кезекте маалыматтарды сактоого багытталган чечмеленген тил үчүн таасирдүү.

Ар бир жаңыртуунун өлчөмү менен убакыт эмне үчүн сызыктуу эмес өсөт деген суроо туулат. Себеби, кичирейтүү функциясы чындыгында C функциясы болуп саналат, ал updateAggге караганда алда канча натыйжалуу. Белгилүү бир жаңыртуу өлчөмүнөн баштап (болжол менен 10.000 30), updateAgg өзүнүн чегине жетет жана андан кийин анын аткарылуу убактысы жаңыртуу өлчөмүнөн көз каранды эмес. Алдын ала Q кадамынын аркасында кызмат мындай көлөмдөгү маалыматтарды сиңире алат. Бул чоң маалыматтар менен иштөөдө туура алгоритмди тандоо канчалык маанилүү экенин көрсөтүп турат. Дагы бир жагдай - эстутумда маалыматтарды туура сактоо. Эгерде маалыматтар мамычалык түрдө сакталбаса же убакыт боюнча иреттелбесе, анда биз TLB кэш сагыныч сыяктуу нерсе менен тааныш болуп калмакпыз - процессордун дарек кэшинде эстутум баракчасынын дареги жок. Даректи издөө ийгиликсиз болсо, болжол менен XNUMX эсе көп убакытты талап кылат, ал эми маалыматтар чачырап кетсе, кызматты бир нече жолу жайлатышы мүмкүн.

жыйынтыктоо

Бул макалада мен KDB+ жана Q маалыматтар базасы чоң маалыматтарды сактоо жана тандоо аркылуу оңой жетүү үчүн гана эмес, ошондой эле жүз миллиондогон саптарды/гигабайттарды сиңире алган маалыматтарды иштетүү кызматтарын түзүү үчүн ылайыктуу экенин көрсөттү. бир Q процесси. Q тилинин өзү вектордук табияты, орнотулган SQL диалекттик котормочусу жана китепкана функцияларынын абдан ийгиликтүү топтому аркасында маалыматтарды иштетүүгө байланыштуу алгоритмдерди өтө кыска жана натыйжалуу ишке ашырууга мүмкүндүк берет.

Жогоруда айтылгандар Q жасай ала турган нерселердин бир гана бөлүгү экенин белгилей кетейин, анын башка уникалдуу өзгөчөлүктөрү да бар. Мисалы, өтө жөнөкөй IPC протоколу, ал жеке Q процесстеринин ортосундагы чек араны жок кылат жана бул жүздөгөн процесстерди дүйнөнүн ар кайсы бөлүктөрүндөгү ондогон серверлерде жайгаштырылган бир тармакка бириктирүүгө мүмкүндүк берет.

Source: www.habr.com

Комментарий кошуу