Нақты уақыттағы қызмет мысалында Q және KDB+ тілінің мүмкіндіктері

KDB+ базасы, Q бағдарламалау тілі қандай, олардың күшті және әлсіз жақтары туралы менің алдыңғы мақаламнан оқи аласыз. мақала және қысқаша кіріспеде. Мақалада біз кіріс деректер ағынын өңдейтін және әр минут сайын «нақты уақыт» режимінде әртүрлі біріктіру функцияларын есептейтін Q қызметін іске асыратын боламыз (яғни, деректердің келесі бөлігіне дейін барлығын есептеуге уақыт болады). Q тілінің негізгі ерекшелігі - бұл жеке объектілермен емес, олардың массивтерімен, массивтер массивтерімен және басқа да күрделі объектілермен жұмыс істеуге мүмкіндік беретін векторлық тіл. Q және оның туыстары K, J, APL сияқты тілдер қысқалығымен танымал. Көбінесе Java сияқты таныс тілдегі кодтың бірнеше экранын алатын бағдарлама оларға бірнеше жолға жазылуы мүмкін. Міне, мен осы мақалада көрсеткім келеді.

Нақты уақыттағы қызмет мысалында Q және KDB+ тілінің мүмкіндіктері

Кіріспе

KDB+ - бұл белгілі бір жолмен реттелген (ең алдымен уақыт бойынша) деректердің өте үлкен көлеміне бағытталған бағаналы дерекқор. Ол ең алдымен қаржы институттарында – банктерде, инвестициялық қорларда, сақтандыру компанияларында қолданылады. Q тілі – бұл деректермен тиімді жұмыс істеуге мүмкіндік беретін KDB+ ішкі тілі. Q идеологиясы қысқалық пен тиімділік, ал анықтық құрбандыққа шалынған. Бұл векторлық тілдің кез келген жағдайда түсіну қиын болатындығымен ақталады, ал жазбаның қысқалығы мен байлығы бір экранда бағдарламаның әлдеқайда үлкен бөлігін көруге мүмкіндік береді, бұл сайып келгенде түсінуді жеңілдетеді.

Бұл мақалада біз Q тілінде толыққанды бағдарламаны жүзеге асырамыз және сіз оны қолданып көргіңіз келуі мүмкін. Ол үшін сізге нақты Q қажет. Сіз kx компаниясының веб-сайтынан тегін 32 биттік нұсқасын жүктей аласыз – www.kx.com. Онда, егер сізді қызықтырса, сіз Q, кітап туралы анықтамалық ақпаратты таба аласыз Q Өлімдерге арналған және осы тақырып бойынша әртүрлі мақалалар.

Мәселенің тұжырымы

Әр 25 миллисекунд сайын деректері бар кестені жіберетін көз бар. KDB+ негізінен қаржы саласында қолданылатындықтан, бұл келесі бағандары бар транзакциялар (сауда-саттықтар) кестесі деп есептейміз: уақыт (миллисекундтағы уақыт), sym (биржадағы компанияның белгіленуі - IBM, AAPL,…), баға (акциялар сатып алынған баға), мөлшері (мәміле өлшемі). 25 миллисекунд аралығы ерікті, тым кішкентай емес және тым ұзақ емес. Оның болуы деректердің буферленген қызметке келетінін білдіреді. Ағымдағы жүктемеге байланысты динамикалық буферлеуді қоса алғанда, қызмет жағында буферлеуді жүзеге асыру оңай болар еді, бірақ қарапайымдылық үшін біз бекітілген интервалға назар аударамыз.

Қызмет сим бағанынан әрбір кіріс символы үшін әр минутты біріктіретін функциялар жиынтығын санауы керек - максималды баға, орташа баға, сома өлшемі және т.б. пайдалы ақпарат. Қарапайымдылық үшін біз барлық функцияларды қадаммен есептеуге болады деп есептейміз, яғни. жаңа мәнді алу үшін екі санды - ескі және кіріс мәндерін білу жеткілікті. Мысалы, max, middle, sum функцияларында бұл қасиет бар, бірақ медианалық функцияда жоқ.

Біз сондай-ақ кіріс деректер ағыны уақыт бойынша реттелген деп есептейміз. Бұл бізге соңғы минутпен ғана жұмыс істеуге мүмкіндік береді. Іс жүзінде кейбір жаңартулар кешігіп қалған жағдайда ағымдағы және алдыңғы минуттармен жұмыс істей білу жеткілікті. Қарапайымдылық үшін біз бұл істі қарастырмаймыз.

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

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

  • жоғары – максималды баға – минутына максималды баға.
  • төмен – минимум баға – минутына ең төменгі баға.
  • FirstPrice – бірінші баға – минутына бірінші баға.
  • соңғы баға – соңғы баға – минутына соңғы баға.
  • firstSize – бірінші өлшем – минутына бірінші сауда мөлшері.
  • lastSize – соңғы өлшем – бір минуттағы соңғы сауда мөлшері.
  • numTrades – count i – минутына сауда-саттық саны.
  • көлем – сома өлшемі – минутына сауда өлшемдерінің сомасы.
  • pvolume – sum price – орташа бағаға қажетті минуттағы бағалар сомасы.
  • – айналым сомасының бағасы*мөлшері – минутына транзакциялардың жалпы көлемі.
  • 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 объяснен ниже

Ыңғайлы болу үшін сөздікке sym және уақыт қостым, енді 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)];
 }

Соңында, клиент деректерді қосу үшін шақыратын жаңарту функциясы (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 миллион жол) өте үлкен, мұндай жағдайларда әрқайсысы кейіпкерлердің бір бөлігін ғана өңдейтін қызметтің бірнеше клондарын (тіпті ондаған клондарды) іске қосу жиі кездеседі. Дегенмен, нәтиже негізінен деректерді сақтауға бағытталған интерпретацияланған тіл үшін әсерлі.

Әр жаңартудың өлшеміне қарай уақыт неге сызықты емес өседі деген сұрақ туындауы мүмкін. Себебі, кішірейту функциясы шын мәнінде updateAgg қарағанда әлдеқайда тиімді болып табылатын C функциясы болып табылады. Белгілі бір жаңарту өлшемінен бастап (шамамен 10.000 30), updateAgg өзінің шегіне жетеді, содан кейін оның орындалу уақыты жаңарту өлшеміне байланысты емес. Бұл алдын ала Q қадамының арқасында қызмет деректердің осындай көлемін қорыта алады. Бұл үлкен деректермен жұмыс істеу кезінде дұрыс алгоритмді таңдаудың қаншалықты маңызды екенін көрсетеді. Тағы бір мәселе жадта деректерді дұрыс сақтау. Егер деректер бағаналық түрде сақталмаса немесе уақыт бойынша реттелмесе, онда біз TLB кэшті жіберіп алу сияқты нәрсемен таныс болар едік - процессордың мекенжай кэшінде жад бетінің мекенжайының болмауы. Мекенжайды іздеу сәтсіз болса, шамамен XNUMX есе ұзағырақ уақыт алады, ал деректер шашыраңқы болса, қызметті бірнеше рет баяулатуы мүмкін.

қорытынды

Бұл мақалада мен KDB+ және Q деректер базасы үлкен деректерді сақтауға және таңдау арқылы оларға оңай қол жеткізуге ғана емес, сонымен қатар жүздеген миллион жолдарды/гигабайттарды өңдеуге қабілетті деректерді өңдеу қызметтерін құруға жарамды екенін көрсеттім. бір Q процесі. Q тілінің өзі векторлық табиғатына, кірістірілген SQL диалект интерпретаторына және кітапхана функцияларының өте сәтті жиынтығына байланысты деректерді өңдеуге қатысты алгоритмдерді өте қысқа және тиімді жүзеге асыруға мүмкіндік береді.

Жоғарыда айтылғандар Q жасай алатын нәрсенің бір бөлігі ғана екенін атап өтейін, оның басқа да бірегей мүмкіндіктері бар. Мысалы, жеке Q процестері арасындағы шекараны өшіретін және әлемнің әртүрлі бөліктеріндегі ондаған серверлерде орналасуы мүмкін жүздеген осы процестерді бір желіге біріктіруге мүмкіндік беретін өте қарапайым IPC протоколы.

Ақпарат көзі: www.habr.com

пікір қалдыру