Dir kënnt liesen iwwer wat d'KDB + Basis ass, d'Q Programmiersprache, wat hir Stäerkten a Schwächten a mengem fréiere sinn a kuerz an der Aféierung. Am Artikel wäerte mir e Service op Q ëmsetzen, deen den erakommende Datestroum veraarbecht a verschidde Aggregatiounsfunktiounen all Minutt am "Echtzäit" Modus berechent (dh, et wäert Zäit hunn alles virum nächsten Deel vun Daten ze berechnen). D'Haaptfunktioun vu Q ass datt et eng Vektorsprooch ass déi Iech erlaabt net mat eenzelen Objeten ze bedreiwen, awer mat hiren Arrays, Arrays vun Arrays an aner komplex Objeten. Sprooche wéi Q a seng Famill K, J, APL si berühmt fir hir Kuerzegkeet. Dacks kann e Programm, deen e puer Schiirme vu Code an enger vertrauter Sprooch wéi Java ophëlt, op e puer Zeilen geschriwwe ginn. Dëst ass wat ech an dësem Artikel wëll weisen.

Aféierung
KDB+ ass eng spalten Datebank konzentréiert op ganz grouss Quantitéiten un Daten, op eng spezifesch Manéier bestallt (haaptsächlech no Zäit). Et gëtt haaptsächlech an Finanzinstituter benotzt - Banken, Investitiounsfongen, Versécherungsgesellschaften. D'Q Sprooch ass déi intern Sprooch vu KDB+ déi Iech erlaabt effektiv mat dësen Donnéeën ze schaffen. D'Q Ideologie ass Kuerzegkeet an Effizienz, wärend Kloerheet geaffert gëtt. Dëst ass gerechtfäerdegt duerch d'Tatsaach datt d'Vektorsprooch op alle Fall schwéier ze verstoen ass, an d'Kürze a Räichtum vun der Opnam erlaabt Iech e vill méi groussen Deel vum Programm op engem Écran ze gesinn, wat et schlussendlech méi einfach ze verstoen mécht.
An dësem Artikel implementéiere mir e vollwäertege Programm am Q an Dir wëllt et vläicht ausprobéieren. Fir dëst ze maachen, braucht Dir déi aktuell Q. Dir kënnt déi gratis 32-Bit Versioun op der kx Firma Websäit eroflueden - . Do, wann Dir interesséiert sidd, fannt Dir Referenzinformatiounen iwwer Q, d'Buch a verschidden Artikelen iwwer dëst Thema.
Problemerklärung
Et gëtt eng Quell déi all 25 Millisekonnen en Dësch mat Daten schéckt. Zënter KDB+ haaptsächlech a Finanzen benotzt gëtt, wäerte mir ugeholl datt dëst eng Transaktiounstabell (Händler) ass, déi déi folgend Kolonnen huet: Zäit (Zäit a Millisekonnen), sym (Bezeechnung vun der Firma op der Bourse - IBM, AAPL,...), Präis (Präis zu deem Aktien kaaft goufen), Gréisst (Transaktiounsgréisst). De 25 Millisekonnen Intervall ass arbiträr, net ze kleng an net ze laang. Seng Präsenz bedeit datt d'Donnéeën an de Service kommen, déi scho gebuffert sinn. Et wier einfach Puffer op der Service Säit ëmzesetzen, dorënner dynamesch Puffer ofhängeg vun der aktueller Belaaschtung, awer fir Simplicitéit konzentréiere mir eis op e fixen Intervall.
De Service muss all Minutt fir all erakommen Symbol aus der Sym Kolonn eng Rei vun aggregéiert Funktiounen zielen - Max Präis, Avg Präis, Zomm Gréisst, etc. nëtzlech Informatiounen. Fir Simplicitéit wäerte mir dovun ausgoen datt all Funktiounen inkrementell berechent kënne ginn, d.h. fir en neie Wäert ze kréien, ass et genuch zwou Zuelen ze wëssen - déi al an déi erakommen Wäerter. Zum Beispill, d'Funktiounen Max, Moyenne, Zomm hunn dës Propriétéit, mä de Median Funktioun net.
Mir wäerten och unhuelen datt den erakommende Datestroum Zäit bestallt ass. Dëst gëtt eis d'Méiglechkeet nëmme mat der leschter Minutt ze schaffen. An der Praxis ass et genuch fir mat den aktuellen a fréiere Minutten ze schaffen am Fall wou e puer Updates spéit sinn. Fir Einfachheet wäerte mir dëse Fall net berücksichtegen.
Aggregatioun Funktiounen
Déi erfuerderlech Aggregatiounsfunktiounen sinn hei ënnendrënner opgezielt. Ech hunn esou vill wéi méiglech vun hinnen geholl fir d'Laascht op de Service ze erhéijen:
- héich - maximal Präis - maximal Präis pro Minutt.
- niddereg - min Präis - Minimum Präis pro Minutt.
- firstPrice - éischte Präis - éischte Präis pro Minutt.
- lastPrice - leschte Präis - leschte Präis pro Minutt.
- firstSize - éischt Gréisst - éischt Handelsgréisst pro Minutt.
- lastSize - lescht Gréisst - lescht Handelsgréisst an enger Minutt.
- numTrades - zielen i - Zuel vun den Handel pro Minutt.
- Volume - Zomm Gréisst - Zomm vun Handelsgréissten pro Minutt.
- pvolume - Zomm Präis - Zomm vun Präisser pro Minutt, néideg fir avgPrice.
- - Zomm Ëmsaz Präis * Gréisst - Gesamtvolumen vun Transaktiounen pro Minutt.
- avgPrice - pvolume% numTrades - Duerchschnëttspräis pro Minutt.
- avgSize - Volume% numTrades - Duerchschnëtt Handelsgréisst pro Minutt.
- vwap - Ëmsaz% Volumen - Duerchschnëttspräis pro Minutt gewiicht duerch Transaktiounsgréisst.
- cumVolume - Zommvolumen - akkumuléiert Gréisst vun Transaktiounen iwwer déi ganz Zäit.
Loosst eis direkt en net offensichtleche Punkt diskutéieren - wéi Dir dës Kolonnen fir d'éischt Kéier a fir all nächst Minutt initialiséiert. E puer Kolonnen vum FirstPrice-Typ mussen all Kéier initialiséiert ginn op null, hire Wäert ass ondefinéiert. Aner Volumentypen mussen ëmmer op 0 gesat ginn. Et ginn och Sailen, déi eng kombinéiert Approche erfuerderen - zum Beispill, cumVolume muss aus der viregter Minutt kopéiert ginn, a fir déi éischt op 0 gesat. Loosst eis all dës Parameteren mat der Wierderbuchdaten setzen Typ (analog zu engem Rekord):
// 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 объяснен ниже
Ech hunn d'Sym an d'Zäit fir d'Bequemlechkeet bäigefüügt, elo ass initWith eng fäerdeg Linn aus der leschter aggregéierter Tabell, wou et bleift fir de richtege Sym an d'Zäit ze setzen. Dir kënnt et benotze fir nei Zeilen an en Dësch ze addéieren.
Mir brauchen aggCols wann Dir eng Aggregatiounsfunktioun erstellt. D'Lëscht muss ëmgedréint ginn wéinst der Uerdnung an där Ausdréck am Q evaluéiert ginn (vu riets op lénks). D'Zil ass sécherzestellen datt d'Berechnung vun héich op cumVolume geet, well e puer Kolonnen op déi virdrun hänken.
Kolonnen déi op eng nei Minutt vun der viregter kopéiert musse ginn, gëtt d'Sym Kolonn fir d'Bequemlechkeet bäigefüügt:
rollColumns:`sym`cumVolume;
Loosst eis elo d'Kolonn a Gruppen opdeelen no wéi se solle aktualiséiert ginn. Dräi Aarte kënnen ënnerscheeden:
- Akkumulatoren (Volumen, Ëmsaz, ..) - mir mussen den erakommende Wäert op dee virdrun addéieren.
- Mat engem spezielle Punkt (héich, niddereg, ..) - den éischte Wäert an der Minutt gëtt vun den erakommen Daten geholl, de Rescht gëtt mat der Funktioun berechent.
- Rescht. Ëmmer berechent mat enger Funktioun.
Loosst eis Variablen fir dës Klassen definéieren:
accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;
Berechnung Uerdnung
Mir aktualiséieren den aggregéierten Dësch an zwou Etappen. Fir Effizienz, schrumpfen mir éischt den Entréeën Dësch sou datt et nëmmen eng Zeil fir all Charakter a Minutt ass. D'Tatsaach datt all eis Funktiounen inkrementell an assoziativ sinn garantéiert datt d'Resultat vun dësem zousätzleche Schrëtt net ännert. Dir kënnt den Dësch schrumpfen andeems Dir wielt:
select high:max price, low:min price … by sym,time.minute from table
Dës Method huet en Nodeel - de Set vu berechent Sailen ass virdefinéiert. Glécklecherweis gëtt am Q Select och als Funktioun implementéiert wou Dir dynamesch erstallt Argumenter ersetzen kënnt:
?[table;whereClause;byClause;selectClause]
Ech wäert net am Detail d'Format vun den Argumenter beschreiwen an eisem Fall, nëmmen duerch a wielt Ausdréck wäert net-trivial sinn a si soll Dictionnairen vun der Form Kolonnen! Also kann d'Schrumpfungsfunktioun wéi follegt definéiert ginn:
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];
Fir Kloerheet hunn ech d'Parse Funktioun benotzt, déi e String mat engem Q Ausdrock an e Wäert verwandelt, deen un d'Eval Funktioun weidergeleet ka ginn an deen an der Funktioun Auswiel erfuerderlech ass. Notéiert och datt de Preprocess als Projektioun definéiert ass (dh eng Funktioun mat deelweis definéierten Argumenter) vun der Auswielfunktioun, een Argument (den Dësch) fehlt. Wa mir preprocess op en Dësch gëllen, wäerte mir e kompriméierten Dësch kréien.
Déi zweet Stuf ass d'Aktualiséierung vun der aggregéierter Tabell. Loosst eis als éischt den Algorithmus am Pseudocode schreiwen:
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];
…
Am Q ass et üblech fir Kaart-/Reduktiounsfunktiounen ze benotzen anstatt Loops. Awer well Q eng Vektorsprooch ass a mir kënnen all Operatiounen op all Symboler gläichzäiteg applizéieren, da kënne mir zu enger éischter Approximatioun iwwerhaapt ouni Loop maachen, Operatiounen op all Symboler gläichzäiteg ausféieren:
idx:calcIdx inputTable;
row:aggTable idx;
aggTable[idx;`high]: row[`high] | inputTable`high;
aggTable[idx;`volume]: row[`volume] + inputTable`volume;
…
Mä mir kënne weider goen, Q huet eng eenzegaarteg an extrem mächteg Bedreiwer - der generaliséiert Aufgab Bedreiwer. Et erlaabt Iech eng Rei vu Wäerter an enger komplexer Datestruktur z'änneren mat enger Lëscht vun Indizes, Funktiounen an Argumenter. An eisem Fall gesäit et esou aus:
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;…)];
Leider, fir en Dësch ze ginn, brauch Dir eng Lëscht vu Reihen, net Kolonnen, an Dir musst d'Matrix transposéieren (Lëscht vu Kolonnen op d'Lëscht vun de Reihen) mat der Flip-Funktioun. Dëst ass deier fir e groussen Dësch, also amplaz gëlle mir eng generaliséiert Aufgab op all Kolonn getrennt, mat der Kaartfunktioun (déi ausgesäit wéi en Apostroph):
.[aggTable;;:;]'[(idx;)each aggCols; (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];
Mir benotzen erëm Fonktioun Projektioun. Notéiert och datt am Q eng Lëscht erstellen och eng Funktioun ass a mir kënnen et mat der jidderengem (Kaart) Funktioun nennen fir eng Lëscht vu Lëschte ze kréien.
Fir sécherzestellen datt de Set vu berechent Sailen net fixéiert ass, kreéiere mir den uewe genannten Ausdrock dynamesch. Loosst eis als éischt Funktiounen definéieren fir all Kolonn ze berechnen, andeems d'Zeilen an Inp Variablen benotzt fir op déi aggregéiert an Inputdaten ze referenzéieren:
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");
E puer Kolonnen sinn speziell hiren éischte Wäert soll net vun der Funktioun berechent ginn. Mir kënne feststellen datt et déi éischt ass duerch d'Zeil[`numTrades] Kolonn - wann et 0 enthält, dann ass de Wäert als éischt. Q huet eng Auswielfunktioun - ?[Boolean Lëscht; Lëscht1;Lëscht2] - déi e Wäert aus der Lëscht 1 oder 2 auswielt ofhängeg vun der Bedingung am éischten Argument:
// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - тоже обобщенное присваивание для случая когда индекс неглубокий
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];
Hei hunn ech eng generaliséiert Aufgab mat menger Funktioun genannt (en Ausdrock a Curly Klammeren). Et kritt den aktuelle Wäert (dat éischt Argument) an en zousätzlech Argument, deen ech am 4. Parameter passéieren.
Loosst eis Batterie Spriecher separat addéieren, well d'Funktioun fir si d'selwecht ass:
// volume -> row[`volume]+inp`volume
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
Dëst ass eng normal Uerderung no Q Standarden, awer ech ginn eng Lëscht vu Wäerter op eemol zou. Endlech, loosst eis d'Haaptfunktioun erstellen:
// ":",/: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),")]}";
Mat dësem Ausdrock erstellen ech dynamesch eng Funktioun aus engem String, deen den Ausdrock enthält, deen ech uewe ginn. D'Resultat wäert esou ausgesinn:
{[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])]}
D'Kolonnbewäertungsuerdnung gëtt ëmgedréint well am Q d'Evaluatiounsuerdnung vu riets op lénks ass.
Elo hu mir zwou Haaptfunktiounen déi néideg sinn fir Berechnungen, mir brauche just e bëssen Infrastruktur ze addéieren an de Service ass prett.
Finale Schrëtt
Mir hunn Preprocess an UpdateAgg Funktiounen déi all d'Aarbecht maachen. Awer et ass nach ëmmer néideg fir de korrekten Iwwergang duerch Minutten ze garantéieren an d'Indexe fir d'Aggregatioun ze berechnen. Als éischt, loosst eis d'Init Funktioun definéieren:
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
}
Mir definéieren och d'Rollfunktioun, déi déi aktuell Minutt ännert:
roll:{[tm]
if[currTime>tm; :init[]]; // если перевалили за полночь, то просто вызовем init
rollCache,::offset _ rollColumns#tradeAgg; // обновим кэш – взять roll колонки из aggTable, обрезать, вставить в rollCache
offset::count tradeAgg;
currSyms::`u#`$();
}
Mir brauchen eng Funktioun fir nei Zeechen ze addéieren:
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)];
}
A schliisslech ass d'Upd Funktioun (den traditionellen Numm fir dës Funktioun fir Q Servicer), déi vum Client opgeruff gëtt fir Daten ze addéieren:
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]; // обновим агрегированную таблицу. Функция ? ищет индекс элементов списка справа в списке слева.
};
Dat ass alles. Hei ass de komplette Code vun eisem Service, wéi versprach, just e puer Zeilen:
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];
};
Testen
Loosst eis d'Leeschtung vum Service iwwerpréiwen. Fir dëst ze maachen, loosst eis et an engem separaten Prozess lafen (de Code an der service.q Datei setzen) a rufft d'Init Funktioun:
q service.q –p 5566
q)init[]
An enger anerer Konsole start den zweeten Q Prozess a verbënnt mat den éischten:
h:hopen `:host:5566
h:hopen 5566 // если оба на одном хосте
Eischtens, loosst eis eng Lëscht vu Symboler erstellen - 10000 Stécker a füügt eng Funktioun fir eng zoufälleg Dësch ze kreéieren. An der zweeter Konsole:
syms:`IBM`AAPL`GOOG,-9997?`8
rnd:{[n;t] ([] sym:n?syms; time:t+asc n#til 25; price:n?10f; size:n?10)}
Ech hunn dräi richteg Symboler op d'Lëscht bäigefüügt fir et méi einfach ze maachen fir se an der Tabell ze sichen. D'rnd Funktioun erstellt eng zoufälleg Dësch mat n Zeile, wou d'Zäit variéiert vun t bis t + 25 Millisekonnen.
Elo kënnt Dir probéieren Daten an de Service ze schécken (derfüügt déi éischt zéng Stonnen):
{h (`upd;`trade;rnd[10000;x])} each `time$00:00 + til 60*10
Dir kënnt am Service kontrolléieren ob den Dësch aktualiséiert gouf:
c 25 200
select from tradeAgg where sym=`AAPL
-20#select from tradeAgg where sym=`AAPL
Resultat:
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|2919Loosst eis elo Lasttest ausféieren fir erauszefannen wéi vill Daten de Service pro Minutt ka veraarbecht. Loosst mech Iech drun erënneren datt mir den Update-Intervall op 25 Millisekonnen setzen. Deementspriechend muss de Service (am Duerchschnëtt) op d'mannst 20 Millisekonnen pro Update passen fir de Benotzer Zäit ze ginn fir Daten ze froen. Gitt déi folgend am zweete Prozess un:
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 ass zwou Minutten. Dir kënnt probéieren als éischt fir 1000 Zeilen all 25 Millisekonnen ze lafen:
start 1000
A mengem Fall ass d'Resultat ongeféier e puer Millisekonnen pro Update. Also ech erhéijen direkt d'Zuel vun de Reihen op 10.000:
start 10000
Resultat:
min| 00:00:00.004
avg| 9.191458
med| 9f
max| 00:00:00.030
Erëm, näischt Besonnesches, awer dëst ass 24 Millioune Linnen pro Minutt, 400 Tausend pro Sekonn. Fir méi wéi 25 Millisekonnen huet den Update nëmme 5 Mol verlangsamt, anscheinend wann d'Minutt geännert huet. Loosst eis op 100.000 eropgoen:
start 100000
Resultat:
min| 00:00:00.013
avg| 25.11083
med| 24f
max| 00:00:00.108
q)sum times
00:02:00.532
Wéi Dir gesitt, kann de Service et kaum ausgoen, awer trotzdem geléngt en iwwerschwemmt ze bleiwen. Esou e Volume vun Daten (240 Millioune Reihen pro Minutt) ass extrem grouss an esou Fäll, ass et üblech fir e puer Klonen (oder souguer Dosende vu Klonen) vum Service ze lancéieren, jidderee vun deenen nëmmen en Deel vun de Charaktere veraarbecht. Trotzdem ass d'Resultat beandrockend fir eng interpretéiert Sprooch déi sech haaptsächlech op Datelagerung konzentréiert.
D'Fro kann opstoen firwat d'Zäit net-linear mat der Gréisst vun all Update wiisst. De Grond ass datt d'Schrumpfung tatsächlech eng C Funktioun ass, déi vill méi effizient ass wéi UpdateAgg. Vun enger bestëmmter Updategréisst un (ongeféier 10.000), erreecht UpdateAgg seng Plafong an dann hänkt seng Ausféierungszäit net vun der Updategréisst of. Et ass wéinst dem virleefege Schrëtt Q datt de Service fäeg ass sou Datenvolumen ze verdauen. Dëst beliicht wéi wichteg et ass de richtege Algorithmus ze wielen wann Dir mat Big Data schafft. En anere Punkt ass déi richteg Späichere vun Daten an der Erënnerung. Wann d'Donnéeën net columnarly gespäichert goufen oder net vun Zäit bestallt goufen, da wäerte mir mat esou eppes wéi TLB Cache Miss - d'Feele vun enger Erënnerung Säit Adress am Prozessor Adress Cache Gewunnecht ginn. D'Sich no enger Adress dauert ongeféier 30 Mol méi laang wann et net erfollegräich ass, a wann d'Donnéeën verspreet sinn, kann et de Service e puer Mol verlangsamen.
Konklusioun
An dësem Artikel hunn ech gewisen datt d'KDB+ a Q Datebank gëeegent sinn net nëmme fir grouss Daten ze späicheren an einfach Zougang zu se duerch Auswiel ze kréien, awer och fir Datenveraarbechtungsservicer ze kreéieren déi fäeg sinn Honnerte vu Millioune Reihen / Gigabytes vun Daten ze verdauen och an een eenzege Q Prozess. D'Q Sprooch selwer erlaabt eng extrem präzis an effizient Ëmsetzung vun Algorithmen am Zesummenhang mat Datenveraarbechtung wéinst senger Vektornatur, agebaute SQL Dialektinterpreter an e ganz erfollegräiche Set vu Bibliothéiksfunktiounen.
Ech wäert feststellen, datt déi uewe just en Deel vun ass, wat Q do kann, et huet och aner eenzegaarteg Fonctiounen. Zum Beispill, en extrem einfache IPC Protokoll, deen d'Grenz tëscht eenzelne Q Prozesser läscht an erlaabt Iech Honnerte vun dëse Prozesser an engem eenzegen Netz ze kombinéieren, deen op Dosende vu Serveren a verschiddenen Deeler vun der Welt läit.
Source: will.com
