Real vaqt xizmati misolida Q va KDB+ tilining xususiyatlari

KDB+ bazasi, Q dasturlash tili nima ekanligini, ularning kuchli va zaif tomonlari haqida mening oldingi maqolamda o'qishingiz mumkin. maqola va qisqacha kirish qismida. Maqolada biz Q da kiruvchi ma'lumotlar oqimini qayta ishlovchi va "real vaqt" rejimida har daqiqada turli yig'ish funktsiyalarini hisoblab chiqadigan xizmatni amalga oshiramiz (ya'ni, ma'lumotlarning keyingi qismidan oldin hamma narsani hisoblash uchun vaqt bo'ladi). Q ning asosiy xususiyati shundaki, u vektor tili bo'lib, u bitta ob'ektlar bilan emas, balki ularning massivlari, massivlar massivlari va boshqa murakkab ob'ektlar bilan ishlash imkonini beradi. Q va uning qarindoshlari K, J, APL kabi tillar qisqaligi bilan mashhur. Ko'pincha Java kabi tanish tilda bir nechta ekran kodlarini egallagan dastur ularga bir necha qatorda yozilishi mumkin. Bu men ushbu maqolada ko'rsatmoqchi bo'lgan narsadir.

Real vaqt xizmati misolida Q va KDB+ tilining xususiyatlari

kirish

KDB+ - bu ma'lum bir tarzda (birinchi navbatda vaqt bo'yicha) tartiblangan juda katta hajmdagi ma'lumotlarga qaratilgan ustunli ma'lumotlar bazasi. U birinchi navbatda moliya institutlarida - banklarda, investitsiya fondlarida, sug'urta kompaniyalarida qo'llaniladi. Q tili KDB+ ning ichki tili bo'lib, ushbu ma'lumotlar bilan samarali ishlash imkonini beradi. Q mafkurasi qisqalik va samaradorlikdir, aniqlik esa qurbon qilinadi. Bu vektor tilini har qanday holatda tushunish qiyin bo'lishi bilan oqlanadi va yozuvning qisqaligi va boyligi dasturning ancha katta qismini bitta ekranda ko'rish imkonini beradi, bu esa oxir-oqibat tushunishni osonlashtiradi.

Ushbu maqolada biz Q-da to'liq huquqli dasturni amalga oshiramiz va siz uni sinab ko'rishingiz mumkin. Buning uchun sizga haqiqiy Q kerak bo'ladi. Siz kx kompaniyasining veb-saytidan 32 bitli bepul versiyani yuklab olishingiz mumkin - www.kx.com. U erda, agar sizni qiziqtirsa, siz Q, kitob bo'yicha ma'lumotnoma ma'lumotlarini topasiz O'lim uchun Q va bu mavzu bo'yicha turli maqolalar.

Muammoni shakllantirish

Har 25 millisekundda ma'lumotlar bilan jadval yuboradigan manba mavjud. KDB+ birinchi navbatda moliya sohasida qo'llanilganligi sababli, biz bu quyidagi ustunlarga ega bo'lgan operatsiyalar (savdolar) jadvali deb taxmin qilamiz: vaqt (millisekundlarda vaqt), sym (birjadagi kompaniya belgisi - IBM, AAPL,…), narx (aktsiyalar sotib olingan narx), hajmi (bitim hajmi). 25 millisekund oralig'i o'zboshimchalik bilan, juda kichik va juda uzoq emas. Uning mavjudligi ma'lumotlar allaqachon buferlangan xizmatga kelishini anglatadi. Joriy yukga qarab dinamik buferlashni o'z ichiga olgan xizmat tomonida buferlashni amalga oshirish oson bo'lar edi, ammo soddaligi uchun biz belgilangan intervalga e'tibor qaratamiz.

Xizmat sim ustunidan har bir kiruvchi belgi uchun har bir daqiqani birlashtiruvchi funktsiyalar to'plamini hisoblashi kerak - maksimal narx, o'rtacha narx, yig'indi hajmi va boshqalar. foydali ma'lumotlar. Oddiylik uchun biz barcha funktsiyalarni bosqichma-bosqich hisoblash mumkinligini taxmin qilamiz, ya'ni. yangi qiymatni olish uchun ikkita raqamni bilish kifoya - eski va kiruvchi qiymatlar. Masalan, max, o'rtacha, sum funktsiyalari bunday xususiyatga ega, ammo mediana funksiyasi yo'q.

Bundan tashqari, kiruvchi ma'lumotlar oqimining vaqti buyurtma qilingan deb taxmin qilamiz. Bu bizga faqat oxirgi daqiqalar bilan ishlash imkoniyatini beradi. Amalda, ba'zi yangilanishlar kechikib qolgan taqdirda, joriy va oldingi daqiqalar bilan ishlay olish kifoya. Oddiylik uchun biz bu ishni ko'rib chiqmaymiz.

Birlashtirish funktsiyalari

Kerakli yig'ish funktsiyalari quyida keltirilgan. Xizmatga yukni oshirish uchun men ulardan iloji boricha ko'proq narsani oldim:

  • yuqori - maksimal narx - daqiqada maksimal narx.
  • past – min narx – minutiga minimal narx.
  • Birinchi narx - birinchi narx - daqiqada birinchi narx.
  • lastPrice – oxirgi narx – daqiqasiga oxirgi narx.
  • firstSize - birinchi o'lcham - daqiqada birinchi savdo hajmi.
  • lastSize - oxirgi o'lcham - bir daqiqada oxirgi savdo hajmi.
  • numTrades – count i – daqiqadagi savdolar soni.
  • hajm - summa hajmi - daqiqada savdo hajmi yig'indisi.
  • pvolume – summa narxi – o‘rtacha narx uchun zarur bo‘lgan daqiqada narxlar yig‘indisi.
  • – aylanma summasi narxi*hajmi – bir daqiqadagi tranzaksiyalarning umumiy hajmi.
  • avgPrice – pvolume%numTrades – daqiqasiga o‘rtacha narx.
  • avgSize – hajmi%numTrades – daqiqada o‘rtacha savdo hajmi.
  • vwap - aylanma% hajmi - tranzaksiya hajmi bo'yicha o'rtacha daqiqada narx.
  • cumVolume - yig'indisi hajmi - butun vaqt davomida tranzaktsiyalarning to'plangan hajmi.

Keling, darhol bir noaniq nuqtani muhokama qilaylik - bu ustunlarni birinchi marta va har bir keyingi daqiqada qanday boshlash kerak. FirstPrice turidagi ba'zi ustunlar har safar null bo'lishi uchun ishga tushirilishi kerak; ularning qiymati aniqlanmagan. Boshqa tovush turlari har doim 0 ga o‘rnatilishi kerak. Birlashtirilgan yondashuvni talab qiladigan ustunlar ham bor - masalan, cumVolume oldingi daqiqadan nusxa ko‘chirilishi kerak va birinchisi uchun 0 ga o‘rnatiladi. Keling, ushbu parametrlarning barchasini lug‘at ma’lumotlaridan foydalanib o‘rnatamiz. turi (yozuvga o'xshash):

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

Qulaylik uchun lug'atga sim va vaqt qo'shdim, endi initWith yakuniy yig'ilgan jadvaldan tayyor chiziq bo'lib, u erda to'g'ri sim va vaqtni o'rnatish qoladi. Jadvalga yangi qatorlar qo'shish uchun undan foydalanishingiz mumkin.

Birlashtirish funksiyasini yaratishda bizga aggCols kerak bo'ladi. Q dagi ifodalarni baholash tartibi (o'ngdan chapga) tufayli ro'yxat teskari bo'lishi kerak. Maqsad, hisoblashning yuqoridan cumVolumegacha borishini ta'minlashdir, chunki ba'zi ustunlar avvalgilariga bog'liq.

Avvalgisidan yangi daqiqaga ko'chirilishi kerak bo'lgan ustunlar, qulaylik uchun sim ustuni qo'shiladi:

rollColumns:`sym`cumVolume;

Endi ustunlarni qanday yangilash kerakligiga qarab guruhlarga ajratamiz. Uchta turni ajratish mumkin:

  1. Akkumulyatorlar (hajm, aylanma, ..) - biz oldingi qiymatga kiruvchi qiymatni qo'shishimiz kerak.
  2. Maxsus nuqta bilan (yuqori, past, ..) - daqiqada birinchi qiymat kiruvchi ma'lumotlardan olinadi, qolganlari funksiya yordamida hisoblanadi.
  3. Dam olish. Har doim funktsiya yordamida hisoblangan.

Keling, ushbu sinflar uchun o'zgaruvchilarni aniqlaymiz:

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

Hisoblash tartibi

Ikki bosqichda jamlangan jadvalni yangilaymiz. Samaradorlik uchun biz birinchi navbatda kiruvchi jadvalni har bir belgi va daqiqa uchun faqat bitta satr bo'lishi uchun qisqartiramiz. Bizning barcha funktsiyalarimiz qo'shimcha va assotsiativ bo'lishi ushbu qo'shimcha qadamning natijasi o'zgarmasligini kafolatlaydi. Select yordamida jadvalni qisqartirishingiz mumkin:

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

Ushbu usulning kamchiligi bor - hisoblangan ustunlar to'plami oldindan belgilangan. Yaxshiyamki, Q da, tanlash dinamik ravishda yaratilgan argumentlarni almashtirishingiz mumkin bo'lgan funksiya sifatida ham amalga oshiriladi:

?[table;whereClause;byClause;selectClause]

Men argumentlar formatini batafsil tasvirlab bermayman, bizda faqat by va tanlangan iboralar ahamiyatsiz bo'ladi va ular ustunlar! ifodalar lug'ati bo'lishi kerak. Shunday qilib, qisqarish funktsiyasini quyidagicha aniqlash mumkin:

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

Aniqlik uchun men Q ifodasi bo'lgan satrni baholash funktsiyasiga o'tkazilishi mumkin bo'lgan va funktsiyani tanlashda talab qilinadigan qiymatga aylantiradigan parse funktsiyasidan foydalandim. Shuni ham yodda tutingki, preprocess tanlangan funksiyaning proektsiyasi (ya'ni, qisman aniqlangan argumentlarga ega funksiya) sifatida belgilanadi, bitta argument (jadval) etishmayapti. Agar biz jadvalga oldindan ishlov berishni qo'llasak, biz siqilgan jadvalga ega bo'lamiz.

Ikkinchi bosqich - jamlangan jadvalni yangilash. Avval algoritmni psevdokodda yozamiz:

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-da ko'chadan o'rniga xaritalash/kamaytirish funksiyalaridan foydalanish odatiy holdir. Ammo Q vektor tili bo'lgani uchun va biz barcha amallarni bir vaqtning o'zida barcha belgilarga osonlik bilan qo'llashimiz mumkin bo'lganligi sababli, birinchi yaqinlashishda biz tsiklsiz, bir vaqtning o'zida barcha belgilar ustida amallarni bajarishimiz mumkin:

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

Ammo biz oldinga borishimiz mumkin, Q noyob va o'ta kuchli operatorga ega - umumlashtirilgan tayinlash operatori. Bu indekslar, funktsiyalar va argumentlar ro'yxatidan foydalangan holda murakkab ma'lumotlar strukturasidagi qiymatlar to'plamini o'zgartirishga imkon beradi. Bizning holatlarimizda u quyidagicha ko'rinadi:

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

Afsuski, jadvalni belgilash uchun sizga ustunlar emas, satrlar roʻyxati kerak boʻladi va flip funksiyasidan foydalanib matritsani (ustunlar roʻyxatini satrlar roʻyxatiga) koʻchirishingiz kerak. Bu katta jadval uchun qimmat, shuning oʻrniga xarita funksiyasidan (apostrofga oʻxshaydi) foydalanib, har bir ustunga alohida umumlashtirilgan topshiriqni qoʻllaymiz:

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

Biz yana funksiya proyeksiyasidan foydalanamiz. Shuni ham yodda tutingki, Q da ro'yxat yaratish ham funktsiyadir va biz ro'yxatlar ro'yxatini olish uchun uni every(map) funktsiyasidan foydalanib chaqirishimiz mumkin.

Hisoblangan ustunlar to'plami o'zgarmasligini ta'minlash uchun biz yuqoridagi ifodani dinamik ravishda yaratamiz. Keling, birinchi navbatda har bir ustunni hisoblash uchun funktsiyalarni aniqlaymiz, bunda qator va inp o'zgaruvchilari yig'ilgan va kiritilgan ma'lumotlarga murojaat qiladi:

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

Ba'zi ustunlar maxsus; ularning birinchi qiymati funktsiya tomonidan hisoblanmasligi kerak. Bu satr [`numTrades] ustuni boʻyicha birinchi ekanligini aniqlashimiz mumkin - agar unda 0 boʻlsa, qiymat birinchi boʻladi. Q tanlov funksiyasiga ega - ?[Mantiqiy ro'yxat;list1;list2] - bu birinchi argumentdagi shartga qarab 1 yoki 2-ro'yxatdagi qiymatni tanlaydi:

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

Bu erda men o'z funksiyam bilan umumlashtirilgan topshiriqni chaqirdim (jingalak qavslardagi ifoda). U joriy qiymatni (birinchi argument) va qo'shimcha argumentni oladi, men uni 4-parametrda o'tkazaman.

Batareya dinamiklarini alohida qo'shamiz, chunki funktsiya ular uchun bir xil:

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

Bu Q standartlari bo'yicha oddiy topshiriq, lekin men bir vaqtning o'zida qiymatlar ro'yxatini tayinlayman. Va nihoyat, asosiy funktsiyani yaratamiz:

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

Ushbu ifoda yordamida men yuqorida bergan iborani o'z ichiga olgan satrdan funktsiyani dinamik ravishda yarataman. Natija quyidagicha ko'rinadi:

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

Ustunni baholash tartibi teskari bo'ladi, chunki Q da baholash tartibi o'ngdan chapga.

Endi bizda hisob-kitoblar uchun zarur bo'lgan ikkita asosiy funktsiya mavjud, biz ozgina infratuzilmani qo'shishimiz kerak va xizmat tayyor.

Yakuniy qadamlar

Bizda barcha ishlarni bajaradigan preprocess va updateAgg funksiyalari mavjud. Ammo daqiqalar orqali to'g'ri o'tishni ta'minlash va yig'ish uchun indekslarni hisoblash hali ham zarur. Avvalo, init funksiyasini aniqlaymiz:

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
 }

Shuningdek, biz joriy daqiqani o'zgartiradigan rulon funktsiyasini aniqlaymiz:

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

Yangi belgilar qo'shish uchun bizga funksiya kerak bo'ladi:

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

Va nihoyat, mijoz tomonidan ma'lumotlarni qo'shish uchun chaqiriladigan yangilash funktsiyasi (Q xizmatlari uchun ushbu funktsiyaning an'anaviy nomi):

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

Ana xolos. Mana bizning xizmatimizning to'liq kodi, va'da qilinganidek, bir necha satr:

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

Viktorina

Keling, xizmatning ishlashini tekshiramiz. Buning uchun uni alohida jarayonda ishga tushiramiz (kodni service.q fayliga qo'ying) va init funksiyasini chaqiramiz:

q service.q –p 5566

q)init[]

Boshqa konsolda ikkinchi Q jarayonini boshlang va birinchisiga ulaning:

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

Birinchidan, belgilar ro'yxatini tuzamiz - 10000 XNUMX dona va tasodifiy jadval yaratish uchun funktsiyani qo'shamiz. Ikkinchi konsolda:

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

Jadvalda ularni qidirishni osonlashtirish uchun ro'yxatga uchta haqiqiy belgi qo'shdim. rnd funksiyasi n qatorli tasodifiy jadval yaratadi, bu erda vaqt t dan t+25 millisekundgacha o'zgaradi.

Endi siz xizmatga ma'lumotlarni yuborishga urinib ko'rishingiz mumkin (birinchi o'n soatni qo'shing):

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

Xizmatda jadval yangilanganligini tekshirishingiz mumkin:

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

natija:

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

Keling, xizmat daqiqada qancha ma'lumotni qayta ishlashi mumkinligini aniqlash uchun yuk sinovini o'tkazamiz. Eslatib o'taman, biz yangilash oralig'ini 25 millisekundga o'rnatdik. Shunga ko'ra, foydalanuvchilarga ma'lumotlarni so'rash uchun vaqt berish uchun xizmat (o'rtacha) har bir yangilanish uchun kamida 20 millisekundga to'g'ri kelishi kerak. Ikkinchi jarayonda quyidagilarni kiriting:

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 - ikki daqiqa. Avval har 1000 millisekundda 25 ta qatorni ishga tushirishga harakat qilishingiz mumkin:

start 1000

Mening holatimda, natija har bir yangilanish uchun bir necha millisekund atrofida bo'ladi. Shunday qilib, men darhol qatorlar sonini 10.000 XNUMX ga oshiraman:

start 10000

natija:

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

Shunga qaramay, hech qanday maxsus narsa yo'q, lekin bu daqiqada 24 million satr, soniyada 400 ming. 25 millisekunddan ko'proq vaqt davomida yangilanish faqat 5 marta sekinlashdi, ehtimol daqiqa o'zgarganda. Keling, 100.000 ga oshiramiz:

start 100000

natija:

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

Ko'rib turganingizdek, xizmat zo'rg'a bardosh bera oladi, ammo shunga qaramay u suvda qolishga muvaffaq bo'ladi. Bunday ma'lumotlar hajmi (daqiqada 240 million qator) juda katta, bunday hollarda har biri belgilarning faqat bir qismini qayta ishlovchi xizmatning bir nechta klonlarini (hatto o'nlab klonlarni) ishga tushirish odatiy holdir. Shunga qaramay, natija asosan ma'lumotlarni saqlashga qaratilgan talqin qilingan til uchun ta'sirli.

Nima uchun vaqt har bir yangilanish hajmi bilan chiziqli bo'lmagan o'sadi degan savol tug'ilishi mumkin. Buning sababi shundaki, qisqartirish funktsiyasi aslida C funktsiyasi bo'lib, updateAgg ga qaraganda ancha samarali. Muayyan yangilash hajmidan (taxminan 10.000 30) boshlab, updateAgg o'zining shiftiga etadi va keyin uning bajarilish vaqti yangilanish hajmiga bog'liq emas. Aynan Q-ning dastlabki bosqichi tufayli xizmat bunday hajmdagi ma'lumotlarni hazm qila oladi. Bu katta ma'lumotlar bilan ishlashda to'g'ri algoritmni tanlash qanchalik muhimligini ta'kidlaydi. Yana bir nuqta - ma'lumotlarni xotirada to'g'ri saqlash. Agar ma'lumotlar ustunli tarzda saqlanmagan bo'lsa yoki vaqt bo'yicha buyurtma berilmagan bo'lsa, biz TLB keshini o'tkazib yuborish kabi narsa bilan tanish bo'lardik - protsessor manzili keshida xotira sahifasi manzilining yo'qligi. Manzilni qidirish muvaffaqiyatsiz bo'lsa, taxminan XNUMX baravar ko'proq vaqt oladi va ma'lumotlar tarqalib ketgan bo'lsa, bu xizmatni bir necha marta sekinlashtirishi mumkin.

xulosa

Ushbu maqolada men KDB+ va Q ma'lumotlar bazasi nafaqat katta hajmdagi ma'lumotlarni saqlash va ularga tanlash orqali osongina kirish uchun, balki ma'lumotlarni qayta ishlash xizmatlarini yaratish uchun ham mos ekanligini ko'rsatdim, ular hatto yuz millionlab qatorlar/gigabaytlar ma'lumotlarni hazm qilish imkoniyatiga ega. bitta Q jarayoni. Q tilining o'zi vektor tabiati, o'rnatilgan SQL dialekt tarjimoni va kutubxona funktsiyalarining juda muvaffaqiyatli to'plami tufayli ma'lumotlarni qayta ishlash bilan bog'liq algoritmlarni juda ixcham va samarali amalga oshirish imkonini beradi.

Shuni ta'kidlab o'tamanki, yuqorida aytilganlar Q qila oladigan narsaning bir qismi, u boshqa noyob xususiyatlarga ham ega. Misol uchun, individual Q jarayonlari orasidagi chegarani o'chiradigan va dunyoning turli burchaklaridagi o'nlab serverlarda joylashishi mumkin bo'lgan yuzlab jarayonlarni yagona tarmoqqa birlashtirishga imkon beruvchi juda oddiy IPC protokoli.

Manba: www.habr.com

a Izoh qo'shish