Real vaxt xidmətinin nümunəsindən istifadə edərək Q və KDB+ dilinin xüsusiyyətləri

KDB+ bazasının, Q proqramlaşdırma dilinin nədən ibarət olduğunu, onların güclü və zəif tərəflərinin nə olduğunu əvvəlki məqaləmdə oxuya bilərsiniz. məqalə və qısaca girişdə. Məqalədə biz Q-da daxil olan məlumat axınını emal edəcək və "real vaxt" rejimində hər dəqiqə müxtəlif toplama funksiyalarını hesablayacaq bir xidmət həyata keçirəcəyik (yəni, məlumatların növbəti hissəsinə qədər hər şeyi hesablamağa vaxt tapacaq). Q dilinin əsas xüsusiyyəti ondan ibarətdir ki, o, tək obyektlərlə deyil, onların massivləri, massivlər massivləri və digər mürəkkəb obyektlərlə işləməyə imkan verən vektor dilidir. Q və onun qohumları K, J, APL kimi dillər qısalığı ilə məşhurdur. Çox vaxt Java kimi tanış dildə bir neçə ekran kod tutan proqram onların üzərinə bir neçə sətirdə yazıla bilər. Bu məqalədə nümayiş etdirmək istədiyim budur.

Real vaxt xidmətinin nümunəsindən istifadə edərək Q və KDB+ dilinin xüsusiyyətləri

Giriş

KDB+, müəyyən bir şəkildə (əsasən zamana görə) sifariş edilmiş çox böyük həcmdə məlumatlara yönəlmiş sütunlu verilənlər bazasıdır. O, ilk növbədə maliyyə institutlarında - banklarda, investisiya fondlarında, sığorta şirkətlərində istifadə olunur. Q dili bu verilənlərlə effektiv işləməyə imkan verən KDB+ proqramının daxili dilidir. Q ideologiyası qısalıq və səmərəlilikdir, aydınlıq isə qurban verilir. Bu onunla əsaslandırılır ki, vektor dili istənilən halda çətin başa düşüləcək və qeydin qısalığı və zənginliyi proqramın daha böyük hissəsini bir ekranda görməyə imkan verir ki, bu da son nəticədə başa düşməyi asanlaşdırır.

Bu yazıda biz Q-da tam hüquqlu proqramı həyata keçiririk və siz onu sınamaq istəyə bilərsiniz. Bunun üçün sizə faktiki Q lazımdır. Siz kx şirkətinin saytında pulsuz 32 bitlik versiyanı yükləyə bilərsiniz - www.kx.com. Əgər maraqlanırsınızsa, orada Q, kitab haqqında məlumat tapa bilərsiniz Q Ölümlülər üçün və bu mövzuda müxtəlif məqalələr.

Problem problemi

Hər 25 millisaniyədən bir verilənlərlə cədvəl göndərən mənbə var. KDB+ ilk növbədə maliyyədə istifadə edildiyindən, bunun aşağıdakı sütunları olan əməliyyatlar (ticarətlər) cədvəli olduğunu güman edəcəyik: vaxt (milisaniyələrdə vaxt), sym (birjada şirkətin təyinatı - IBM, AAPL,…), qiymət (səhmlərin alındığı qiymət), ölçüsü (sövdələşmənin ölçüsü). 25 millisaniyəlik interval ixtiyaridir, çox kiçik və çox uzun deyil. Onun mövcudluğu o deməkdir ki, məlumat artıq tamponlanmış xidmətə gəlir. Cari yükdən asılı olaraq dinamik buferləşdirmə də daxil olmaqla xidmət tərəfində buferləşdirməni həyata keçirmək asan olardı, lakin sadəlik üçün biz sabit intervala diqqət yetirəcəyik.

Xidmət sim sütunundan gələn hər bir simvol üçün hər dəqiqə saymalıdır, bir sıra toplayıcı funksiyalar - maksimum qiymət, orta qiymət, cəmin ölçüsü və s. faydalı məlumat. Sadəlik üçün, bütün funksiyaların artımla hesablana biləcəyini güman edəcəyik, yəni. yeni bir dəyər əldə etmək üçün iki rəqəmi bilmək kifayətdir - köhnə və gələn dəyərlər. Məsələn, max, orta, sum funksiyaları bu xüsusiyyətə malikdir, lakin median funksiyası yoxdur.

Biz həmçinin güman edəcəyik ki, daxil olan məlumat axını vaxt sifarişlidir. Bu, bizə yalnız son dəqiqə ilə işləmək imkanı verəcək. Təcrübədə bəzi yeniləmələrin gecikdiyi halda cari və əvvəlki dəqiqələrlə işləyə bilmək kifayətdir. Sadəlik üçün bu işi nəzərdən keçirməyəcəyik.

Aqreqasiya funksiyaları

Tələb olunan toplama funksiyaları aşağıda verilmişdir. Xidmətə yükü artırmaq üçün mümkün qədər çoxunu götürdüm:

  • yüksək – maksimum qiymət – dəqiqədə maksimum qiymət.
  • aşağı – minimum qiymət – dəqiqədə minimum qiymət.
  • FirstPrice – ilk qiymət – dəqiqədə ilk qiymət.
  • son qiymət – son qiymət – dəqiqədə son qiymət.
  • firstSize – birinci ölçü – dəqiqədə ilk ticarət ölçüsü.
  • lastSize – son ölçü – bir dəqiqə ərzində son ticarət ölçüsü.
  • numTrades – count i – dəqiqədə hərracların sayı.
  • həcm – məbləğ ölçüsü – dəqiqədə ticarət ölçülərinin cəmi.
  • pvolume – cəmi qiymət – dəqiqədə qiymətlərin cəmi, avgPrice üçün tələb olunur.
  • – cəmi dövriyyə qiyməti*ölçüsü – dəqiqədə əməliyyatların ümumi həcmi.
  • avgPrice – phəcm%numTrades – dəqiqədə orta qiymət.
  • avgSize – həcm%numTrades – dəqiqədə orta ticarət ölçüsü.
  • vwap – dövriyyənin%həcmi – əməliyyatın ölçüsünə görə çəkilmiş dəqiqədə orta qiymət.
  • cumVolume – cəmi həcmi – bütün vaxt ərzində əməliyyatların yığılmış ölçüsü.

Gəlin dərhal bir qeyri-aşkar məqamı müzakirə edək - bu sütunları ilk dəfə və hər sonrakı dəqiqə üçün necə işə salmaq olar. FirstPrice tipli bəzi sütunlar hər dəfə null olaraq işə salınmalıdır; onların dəyəri müəyyən edilmir. Digər həcm növləri həmişə 0-a təyin edilməlidir. Kombinə yanaşma tələb edən sütunlar da var - məsələn, cumVolume əvvəlki dəqiqədən kopyalanmalı, birincisi üçün isə 0. Gəlin bütün bu parametrləri lüğət məlumatlarından istifadə edərək təyin edək. növü (bir qeydin analoqu):

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

Rahatlıq üçün lüğətə sim və vaxt əlavə etdim, indi initWith yekun ümumiləşdirilmiş cədvəldən hazır bir xəttdir, burada düzgün sim və vaxtı təyin etmək qalır. Siz cədvələ yeni sətirlər əlavə etmək üçün istifadə edə bilərsiniz.

Toplama funksiyası yaradarkən bizə aggCols lazım olacaq. Q-dakı ifadələrin qiymətləndirilir (sağdan sola) sırasına görə siyahı tərsinə çevrilməlidir. Məqsəd hesablamanın yüksəkdən cumVolume səviyyəsinə keçməsini təmin etməkdir, çünki bəzi sütunlar əvvəlkilərdən asılıdır.

Əvvəlkidən yeni bir dəqiqəyə kopyalanması lazım olan sütunlar, rahatlıq üçün sim sütunu əlavə olunur:

rollColumns:`sym`cumVolume;

İndi sütunları necə yenilənməli olduğuna görə qruplara ayıraq. Üç növü ayırd etmək olar:

  1. Akkumulyatorlar (həcmi, dövriyyəsi,..) – daxil olan dəyəri əvvəlki birinə əlavə etməliyik.
  2. Xüsusi bir nöqtə ilə (yüksək, aşağı, ..) - dəqiqədə ilk dəyər daxil olan məlumatlardan alınır, qalanları funksiyadan istifadə edərək hesablanır.
  3. İstirahət. Həmişə bir funksiyadan istifadə edərək hesablanır.

Bu siniflər üçün dəyişənləri təyin edək:

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

Hesablama qaydası

Birləşdirilmiş cədvəli iki mərhələdə yeniləyəcəyik. Səmərəlilik üçün əvvəlcə daxil olan cədvəli kiçildrik ki, hər simvol və dəqiqə üçün yalnız bir sıra olsun. Bütün funksiyalarımızın artımlı və assosiativ olması bu əlavə addımın nəticəsinin dəyişməyəcəyinə zəmanət verir. Seçimdən istifadə edərək cədvəli kiçilədə bilərsiniz:

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

Bu metodun bir dezavantajı var - hesablanmış sütunlar dəsti əvvəlcədən müəyyən edilmişdir. Xoşbəxtlikdən, Q-da select funksiyası dinamik olaraq yaradılmış arqumentləri əvəz edə biləcəyiniz funksiya kimi də həyata keçirilir:

?[table;whereClause;byClause;selectClause]

Arqumentlərin formatını təfərrüatlı təsvir etməyəcəyəm, bizim vəziyyətimizdə yalnız və seçilmiş ifadələr qeyri-trivial olacaq və onlar forma sütunlarının lüğəti olmalıdır!ifadələr. Beləliklə, daralma funksiyası aşağıdakı kimi müəyyən edilə bilər:

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

Aydınlıq üçün mən Q ifadəsi olan sətri qiymətləndirmə funksiyasına ötürülə bilən və seçim funksiyasında tələb olunan dəyərə çevirən parse funksiyasından istifadə etdim. Həm də nəzərə alın ki, preproses seçim funksiyasının proyeksiyası (yəni, qismən müəyyən edilmiş arqumentləri olan funksiya) kimi müəyyən edilir, bir arqument (cədvəl) yoxdur. Cədvəl üçün əvvəlcədən prosesi tətbiq etsək, sıxılmış bir cədvəl alacağıq.

İkinci mərhələ ümumiləşdirilmiş cədvəlin yenilənməsidir. Əvvəlcə alqoritmi psevdokodla yazaq:

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 döngələr əvəzinə xəritə/kiçil funksiyalarından istifadə etmək adi haldır. Lakin Q vektor dili olduğundan və biz bütün əməliyyatları birdən-birə bütün simvollara asanlıqla tətbiq edə bildiyimiz üçün, birinci yaxınlaşma üçün bütün simvollar üzərində eyni anda əməliyyatlar yerinə yetirməklə, ümumiyyətlə, dövrə olmadan edə bilərik:

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

Lakin biz daha da irəli gedə bilərik, Q unikal və son dərəcə güclü operatora malikdir - ümumiləşdirilmiş təyinat operatoru. Bu, indekslər, funksiyalar və arqumentlər siyahısından istifadə edərək mürəkkəb məlumat strukturunda bir sıra dəyərləri dəyişdirməyə imkan verir. Bizim vəziyyətimizdə bu belə görünür:

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

Təəssüf ki, cədvələ təyin etmək üçün sizə sütunlar deyil, sətirlər siyahısı lazımdır və çevirmə funksiyasından istifadə edərək matrisi (sütunların siyahısını sətirlərin siyahısına) köçürməlisiniz. Bu böyük cədvəl üçün baha başa gəlir, buna görə də biz xəritə funksiyasından istifadə edərək (apostrof kimi görünür) hər bir sütuna ayrıca ümumiləşdirilmiş tapşırıq tətbiq edirik:

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

Yenidən funksiya proyeksiyasından istifadə edirik. Onu da qeyd edək ki, Q-da siyahı yaratmaq da funksiyadır və biz siyahıların siyahısını əldə etmək üçün every(map) funksiyasından istifadə edərək ona zəng edə bilərik.

Hesablanmış sütunlar dəstinin sabit olmadığını təmin etmək üçün yuxarıdakı ifadəni dinamik şəkildə yaradacağıq. Gəlin əvvəlcə hər bir sütunu hesablamaq üçün sətir və inp dəyişənlərindən istifadə edərək ümumiləşdirilmiş və daxil edilmiş məlumatlara istinad edən funksiyaları müəyyən edək:

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

Bəzi sütunlar xüsusidir; onların ilk dəyəri funksiya ilə hesablanmamalıdır. Biz onun birinci olduğunu sətir[`numTrades] sütunu ilə müəyyən edə bilərik - əgər 0-dan ibarətdirsə, dəyər birincidir. Q seçim funksiyasına malikdir - ?[Boolean list;list1;list2] - bu, birinci arqumentdəki şərtdən asılı olaraq siyahı 1 və ya 2-dən qiymət seçir:

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

Burada öz funksiyamla ümumiləşdirilmiş tapşırığı çağırdım (qıvrımlı mötərizələrdə ifadə). O, cari dəyəri (birinci arqument) və 4-cü parametrdə keçən əlavə arqumenti alır.

Batareya dinamiklərini ayrıca əlavə edək, çünki funksiya onlar üçün eynidir:

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

Bu Q standartlarına görə normal bir tapşırıqdır, lakin mən bir anda dəyərlər siyahısını təyin edirəm. Nəhayət, əsas funksiyanı yaradaq:

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

Bu ifadə ilə yuxarıda verdiyim ifadəni ehtiva edən sətirdən dinamik olaraq funksiya yaradıram. Nəticə belə görünəcək:

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

Sütun qiymətləndirmə sırası tərsinə çevrilir, çünki Q-da qiymətləndirmə sırası sağdan soladır.

İndi hesablamalar üçün lazım olan iki əsas funksiyamız var, sadəcə bir az infrastruktur əlavə etməliyik və xidmət hazırdır.

Son addımlar

Bütün işləri görən preprocess və updateAgg funksiyalarımız var. Ancaq yenə də dəqiqələr arasında düzgün keçidi təmin etmək və toplama üçün indeksləri hesablamaq lazımdır. Əvvəlcə init funksiyasını təyin edək:

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
 }

Cari dəqiqəni dəyişdirəcək roll funksiyasını da təyin edəcəyik:

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

Yeni simvol əlavə etmək üçün bizə bir funksiya lazımdır:

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

Və nəhayət, məlumat əlavə etmək üçün müştəri tərəfindən çağırılan upd funksiyası (Q xidmətləri üçün bu funksiyanın ənənəvi adı):

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

Hamısı budur. Budur, xidmətimizin tam kodu, söz verildiyi kimi, cəmi bir neçə sətir:

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

Test

Xidmətin performansını yoxlayaq. Bunun üçün onu ayrıca prosesdə işə salaq (kodu service.q faylına qoyun) və init funksiyasını çağıraq:

q service.q –p 5566

q)init[]

Başqa bir konsolda ikinci Q prosesini başladın və birinciyə qoşulun:

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

Əvvəlcə simvolların siyahısını yaradaq - 10000 ədəd və təsadüfi cədvəl yaratmaq üçün bir funksiya əlavə edək. İkinci 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)}

Cədvəldə onları axtarmağı asanlaşdırmaq üçün siyahıya üç real simvol əlavə etdim. rnd funksiyası n sıradan ibarət təsadüfi cədvəl yaradır, burada vaxt t ilə t+25 millisaniyə arasında dəyişir.

İndi siz xidmətə məlumat göndərməyə cəhd edə bilərsiniz (ilk on saat əlavə edin):

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

Xidmətdə cədvəlin yeniləndiyini yoxlaya bilərsiniz:

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

Nəticə:

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

İndi xidmətin dəqiqədə nə qədər məlumat emal edə biləcəyini öyrənmək üçün yük testini keçirək. Nəzərinizə çatdırım ki, biz yeniləmə intervalını 25 millisaniyə təyin etmişik. Müvafiq olaraq, istifadəçilərə məlumat tələb etmək üçün vaxt vermək üçün xidmət (orta hesabla) hər yeniləmə üçün ən azı 20 millisaniyəyə uyğun olmalıdır. İkinci prosesdə aşağıdakıları daxil edin:

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 iki dəqiqədir. Siz hər 1000 millisaniyədən bir 25 cərgə üçün əvvəlcə qaçmağa cəhd edə bilərsiniz:

start 1000

Mənim vəziyyətimdə nəticə hər yeniləmə üçün bir neçə millisaniyədir. Beləliklə, dərhal cərgələrin sayını 10.000-ə çatdıracağam:

start 10000

Nəticə:

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

Yenə də xüsusi bir şey yoxdur, amma bu dəqiqədə 24 milyon sətir, saniyədə 400 mindir. 25 millisaniyədən çox müddət ərzində yeniləmə yalnız 5 dəfə yavaşladı, görünür dəqiqə dəyişdikdə. 100.000-ə qədər artıraq:

start 100000

Nəticə:

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

Gördüyünüz kimi, xidmət çətinliklə öhdəsindən gələ bilər, lakin buna baxmayaraq, suda qalmağı bacarır. Belə bir məlumat həcmi (dəqiqədə 240 milyon sıra) olduqca böyükdür; belə hallarda, hər biri simvolların yalnız bir hissəsini emal edən xidmətin bir neçə klonunun (və ya onlarla klonunun) işə salınması adi haldır. Yenə də nəticə əsasən məlumatların saxlanmasına diqqət yetirən tərcümə edilmiş dil üçün təsir edicidir.

Sual yarana bilər ki, niyə vaxt hər yeniləmənin ölçüsü ilə qeyri-xətti böyüyür. Bunun səbəbi kiçilmə funksiyasının əslində updateAgg-dən daha səmərəli olan C funksiyası olmasıdır. Müəyyən bir yeniləmə ölçüsündən (təxminən 10.000) başlayaraq, updateAgg öz tavanına çatır və sonra onun icra müddəti yeniləmə ölçüsündən asılı deyil. İlkin Q addımı sayəsində xidmət bu cür həcmdə məlumatı həzm edə bilir. Bu, böyük verilənlərlə işləyərkən düzgün alqoritmin seçilməsinin nə qədər vacib olduğunu vurğulayır. Başqa bir məqam isə məlumatların yaddaşda düzgün saxlanmasıdır. Məlumatlar sütun şəklində saxlanmasaydı və ya zamanla sifariş edilməsəydi, o zaman TLB önbelleğinin qaçırılması kimi bir şeylə tanış olardıq - prosessorun ünvan keşində yaddaş səhifəsi ünvanının olmaması. Ünvan axtarışı uğursuz olarsa, təqribən 30 dəfə uzun çəkir və məlumatlar səpələnirsə, bu, xidməti bir neçə dəfə ləngidə bilər.

Nəticə

Bu yazıda mən göstərdim ki, KDB+ və Q verilənlər bazası nəinki böyük məlumatların saxlanması və onlara seçim vasitəsilə asanlıqla daxil olmaq, həm də yüz milyonlarla cərgə/giqabayt verilənləri həzm edə bilən məlumat emalı xidmətləri yaratmaq üçün uyğundur. bir Q prosesi. Q dilinin özü vektor təbiəti, quraşdırılmış SQL dialekt tərcüməçisi və çox uğurlu kitabxana funksiyaları dəsti sayəsində verilənlərin emalı ilə bağlı alqoritmlərin son dərəcə qısa və səmərəli həyata keçirilməsinə imkan verir.

Qeyd edim ki, yuxarıda göstərilənlər Q-nun edə biləcəyi işlərin yalnız bir hissəsidir, onun digər unikal xüsusiyyətləri də var. Məsələn, fərdi Q prosesləri arasındakı sərhədi silən və dünyanın müxtəlif yerlərində onlarla serverdə yerləşə bilən yüzlərlə bu prosesi vahid şəbəkədə birləşdirməyə imkan verən son dərəcə sadə IPC protokolu.

Mənbə: www.habr.com

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