Sampeyan bisa maca babagan apa basis KDB +, basa pemrograman Q, apa kekuwatan lan kelemahane ing sadurunge.
Pambuka
KDB+ minangka basis data kolom sing fokus ing jumlah data sing akeh banget, diurutake kanthi cara tartamtu (utamane miturut wektu). Iki digunakake utamane ing institusi finansial - bank, dana investasi, perusahaan asuransi. Basa Q minangka basa internal KDB+ sing ngidini sampeyan nggarap data iki kanthi efektif. Ideologi Q minangka ringkesan lan efisiensi, dene kejelasan dikorbanake. Iki dibenerake kanthi kasunyatan manawa basa vektor bakal angel dingerteni ing kasus apa wae, lan ringkesan lan kasugihan rekaman ngidini sampeyan ndeleng bagean sing luwih gedhe saka program kasebut ing layar siji, sing pungkasane luwih gampang dingerteni.
Ing artikel iki kita ngleksanakake program lengkap ing Q lan sampeyan bisa uga pengin nyoba metu. Kanggo nindakake iki, sampeyan butuh Q sing nyata. Sampeyan bisa ngundhuh versi 32-bit gratis ing situs web perusahaan kx -
Formulasi masalah
Ana sumber sing ngirim tabel karo data saben 25 milliseconds. Wiwit KDB + digunakake utamane ing keuangan, kita bakal nganggep yen iki minangka tabel transaksi (dagang), sing nduweni kolom ing ngisor iki: wektu (wektu ing milliseconds), sym (sebutan perusahaan ing bursa saham - IBM, AAPL,β¦), rega (rega ing ngendi saham dituku), ukuran (ukuran transaksi). Interval 25 milidetik iku sembarang, ora cilik banget lan ora dawa banget. Anane tegese data teka menyang layanan sing wis buffered. Iku bakal gampang kanggo ngleksanakake buffering ing sisih layanan, kalebu buffering dinamis gumantung ing mbukak saiki, nanging kanggo gamblang, kita bakal fokus ing interval tetep.
Layanan kudu ngetung saben menit kanggo saben simbol sing mlebu saka kolom sym sakumpulan fungsi panggabungan - rega maksimal, rega rata-rata, ukuran jumlah, lsp. informasi migunani. Kanggo gamblang, kita bakal nganggep kabeh fungsi bisa diwilang incrementally, i.e. kanggo entuk nilai anyar, cukup ngerti rong nomer - nilai lawas lan mlebu. Contone, fungsi max, rata-rata, jumlah duwe properti iki, nanging fungsi median ora.
Kita uga bakal nganggep yen aliran data sing mlebu wis diurutake wektu. Iki bakal menehi kita kesempatan kanggo bisa mung karo menit pungkasan. Ing praktik, cukup kanggo bisa nggarap menit saiki lan sadurunge yen sawetara nganyari telat. Kanggo gamblang, kita ora bakal nimbang kasus iki.
Fungsi agregasi
Fungsi agregasi sing dibutuhake kapacak ing ngisor iki. Aku njupuk akeh sing bisa kanggo nambah beban ing layanan:
- dhuwur - rega max - rega maksimum saben menit.
- murah - rega min - rega minimal saben menit.
- firstPrice - rega pisanan - rega pisanan saben menit.
- lastPrice - rega pungkasan - rega pungkasan saben menit.
- firstSize - ukuran pisanan - ukuran perdagangan pisanan saben menit.
- lastSize - ukuran pungkasan - ukuran perdagangan pungkasan ing menit.
- numTrades - count i - jumlah dagang saben menit.
- volume - jumlah ukuran - jumlah saka ukuran perdagangan saben menit.
- pvolume - jumlah rega - jumlah rega saben menit, dibutuhake kanggo avgPrice.
- - jumlah rega turnover * ukuran - total volume transaksi saben menit.
- avgPrice β pvolume%numTrades β rega rata-rata saben menit.
- avgSize β volume%numTrades β ukuran perdagangan rata-rata saben menit.
- vwap - volume turnover - rega rata-rata saben menit sing ditimbang miturut ukuran transaksi.
- cumVolume - jumlah volume - ukuran akumulasi transaksi ing kabeh wektu.
Ayo langsung ngrembug siji titik sing ora jelas - carane miwiti kolom kasebut kanggo pisanan lan saben menit sabanjure. Sawetara kolom saka jinis FirstPrice kudu diinisialisasi dadi null saben wektu; regane ora ditemtokake. Jinis volume liyane kudu disetel dadi 0. Ana uga kolom sing mbutuhake pendekatan gabungan - contone, cumVolume kudu disalin saka menit sadurunge, lan kanggo sing pisanan disetel dadi 0. Ayo nyetel kabeh parameter kasebut nggunakake data kamus jinis (analog karo rekaman):
// 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 ΠΎΠ±ΡΡΡΠ½Π΅Π½ Π½ΠΈΠΆΠ΅
Aku nambahake sym lan wektu ing kamus kanggo penak, saiki initWith minangka baris sing wis siap saka tabel gabungan pungkasan, ing ngendi iku tetep nyetel sym lan wektu sing bener. Sampeyan bisa nggunakake kanggo nambah baris anyar menyang meja.
Kita butuh aggCols nalika nggawe fungsi agregasi. Dhaptar kasebut kudu diwalik amarga urutan ekspresi ing Q dievaluasi (saka tengen ngiwa). Tujuane kanggo mesthekake pitungan dadi saka dhuwur nganti cumVolume, amarga sawetara kolom gumantung saka sing sadurunge.
Kolom sing kudu disalin menyang menit anyar saka sing sadurunge, kolom sym ditambahake kanggo penak:
rollColumns:`sym`cumVolume;
Saiki ayo dibagi kolom dadi klompok miturut cara sing kudu dianyari. Telung jinis bisa dibedakake:
- Akumulator (volume, turnover, ..) - kita kudu nambah nilai sing mlebu menyang sing sadurunge.
- Kanthi titik khusus (dhuwur, kurang, ..) - nilai pisanan ing menit dijupuk saka data sing mlebu, liyane diwilang nggunakake fungsi kasebut.
- Ngaso. Tansah diwilang nggunakake fungsi.
Ayo nemtokake variabel kanggo kelas kasebut:
accumulatorCols:`numTrades`volume`pvolume`turnover;
specialCols:`high`low`firstPrice`firstSize;
Urutan pitungan
Kita bakal nganyari tabel agregat ing rong tahap. Kanggo efisiensi, kita nyilikake tabel mlebu supaya mung siji baris kanggo saben karakter lan menit. Kasunyatan manawa kabeh fungsi kita minangka tambahan lan asosiatif njamin yen asil langkah tambahan iki ora bakal owah. Sampeyan bisa nyilikake tabel nggunakake pilih:
select high:max price, low:min price β¦ by sym,time.minute from table
Cara iki duwe kerugian - set kolom sing wis ditemtokake wis ditemtokake. Untunge, ing Q, pilih uga dileksanakake minangka fungsi ing ngendi sampeyan bisa ngganti argumen sing digawe kanthi dinamis:
?[table;whereClause;byClause;selectClause]
Aku ora bakal njlèntrèhaké kanthi rinci babagan format argumen; ing kasus kita, mung dening lan pilih ekspresi sing ora pati penting lan kudu dadi kamus saka wangun kolom!ekspresi. Dadi, fungsi nyusut bisa ditetepake kaya ing ngisor iki:
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];
Kanggo gamblang, Aku digunakake fungsi parse, kang dadi senar karo expression Q menyang Nilai sing bisa liwati kanggo fungsi eval lan kang dibutuhake ing fungsi pilih. Wigati uga yen preprocess ditetepake minangka proyeksi (yaiku, fungsi kanthi argumen sing ditemtokake sebagian) saka fungsi pilih, siji argumen (tabel) ilang. Yen kita ngetrapake preprocess menyang tabel, kita bakal entuk tabel sing dikompres.
Tahap kapindho yaiku nganyari tabel agregat. Ayo pisanan nulis algoritma ing pseudocode:
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];
β¦
Ing Q, iku umum nggunakake map / ngurangi fungsi tinimbang puteran. Nanging amarga Q minangka basa vektor lan kita bisa kanthi gampang ngetrapake kabeh operasi menyang kabeh simbol bebarengan, mula kanggo perkiraan pisanan kita bisa nindakake tanpa loop, nindakake operasi ing kabeh simbol bebarengan:
idx:calcIdx inputTable;
row:aggTable idx;
aggTable[idx;`high]: row[`high] | inputTable`high;
aggTable[idx;`volume]: row[`volume] + inputTable`volume;
β¦
Nanging kita bisa pindhah luwih, Q wis operator unik lan banget kuat - operator assignment umum. Iki ngidini sampeyan ngganti sakumpulan nilai ing struktur data kompleks nggunakake dhaptar indeks, fungsi lan argumen. Ing kasus kita katon kaya iki:
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;β¦)];
Sayange, kanggo nemtokake tabel sampeyan kudu dhaptar baris, ora kolom, lan sampeyan kudu transpose matriks (dhaftar kolom kanggo dhaptar baris) nggunakake fungsi flip. Iki larang kanggo meja gedhe, mula kita ngetrapake tugas umum kanggo saben kolom kanthi kapisah, nggunakake fungsi peta (sing katon kaya apostrof):
.[aggTable;;:;]'[(idx;)each aggCols; (row[`high] | inputTable`high;row[`volume] + inputTable`volume;β¦)];
Kita maneh nggunakake proyeksi fungsi. Uga elinga yen ing Q, nggawe dhaptar uga fungsi lan kita bisa nyebataken nggunakake saben (peta) fungsi kanggo njaluk dhaptar dhaptar.
Kanggo mesthekake yen set kolom sing diwilang ora tetep, kita bakal nggawe ekspresi ing ndhuwur kanthi dinamis. Pisanan kita nemtokake fungsi kanggo ngetung saben kolom, nggunakake variabel baris lan inp kanggo ngrujuk data sing dikumpulake lan input:
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");
Sawetara kolom khusus; nilai sing sepisanan ora kudu diitung kanthi fungsi kasebut. Kita bisa nemtokake manawa iku sing pisanan kanthi kolom [`numTrades] - yen ngemot 0, mula nilai kasebut pisanan. Q nduweni fungsi pilih - ?[Boolean list;list1;list2] - sing milih nilai saka dhaptar 1 utawa 2 gumantung saka kondisi ing argumen pisanan:
// high -> ?[isFirst;inp`high;row[`high]|inp`high]
// @ - ΡΠΎΠΆΠ΅ ΠΎΠ±ΠΎΠ±ΡΠ΅Π½Π½ΠΎΠ΅ ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°Π½ΠΈΠ΅ Π΄Π»Ρ ΡΠ»ΡΡΠ°Ρ ΠΊΠΎΠ³Π΄Π° ΠΈΠ½Π΄Π΅ΠΊΡ Π½Π΅Π³Π»ΡΠ±ΠΎΠΊΠΈΠΉ
@[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols];
Ing kene aku nelpon tugas umum karo fungsiku (ekspresi ing kurung kriting). Nampa nilai saiki (argumen pisanan) lan argumen tambahan, sing dakliwati ing parameter 4.
Tambahake speaker baterei kanthi kapisah, amarga fungsine padha:
// volume -> row[`volume]+inp`volume
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
Iki minangka tugas normal miturut standar Q, nanging aku menehi dhaptar nilai sekaligus. Pungkasan, ayo nggawe fungsi utama:
// ":",/: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),")]}";
Kanthi ekspresi iki, aku nggawe fungsi kanthi dinamis saka senar sing ngemot ekspresi sing dakwenehake ing ndhuwur. Asil bakal katon kaya iki:
{[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])]}
Urutan evaluasi kolom diwalik amarga ing Q urutan evaluasi saka tengen ngiwa.
Saiki kita duwe rong fungsi utama sing dibutuhake kanggo petungan, kita mung kudu nambah infrastruktur sethithik lan layanan wis siyap.
Langkah pungkasan
Kita duwe fungsi preprocess lan updateAgg sing nindakake kabeh karya. Nanging isih perlu kanggo mesthekake transisi sing bener liwat menit lan ngetung indeks kanggo agregasi. Kaping pisanan, ayo nemtokake fungsi 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
}
Kita uga bakal nemtokake fungsi gulung, sing bakal ngganti menit saiki:
roll:{[tm]
if[currTime>tm; :init[]]; // Π΅ΡΠ»ΠΈ ΠΏΠ΅ΡΠ΅Π²Π°Π»ΠΈΠ»ΠΈ Π·Π° ΠΏΠΎΠ»Π½ΠΎΡΡ, ΡΠΎ ΠΏΡΠΎΡΡΠΎ Π²ΡΠ·ΠΎΠ²Π΅ΠΌ init
rollCache,::offset _ rollColumns#tradeAgg; // ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ ΠΊΡΡ β Π²Π·ΡΡΡ roll ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ ΠΈΠ· aggTable, ΠΎΠ±ΡΠ΅Π·Π°ΡΡ, Π²ΡΡΠ°Π²ΠΈΡΡ Π² rollCache
offset::count tradeAgg;
currSyms::`u#`$();
}
Kita butuh fungsi kanggo nambah karakter anyar:
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)];
}
Lan pungkasane, fungsi upd (jeneng tradisional kanggo fungsi iki kanggo layanan Q), sing diarani klien kanggo nambah data:
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]; // ΠΎΠ±Π½ΠΎΠ²ΠΈΠΌ Π°Π³ΡΠ΅Π³ΠΈΡΠΎΠ²Π°Π½Π½ΡΡ ΡΠ°Π±Π»ΠΈΡΡ. Π€ΡΠ½ΠΊΡΠΈΡ ? ΠΈΡΠ΅Ρ ΠΈΠ½Π΄Π΅ΠΊΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΠΏΠΈΡΠΊΠ° ΡΠΏΡΠ°Π²Π° Π² ΡΠΏΠΈΡΠΊΠ΅ ΡΠ»Π΅Π²Π°.
};
Mekaten. Iki kode lengkap layanan kita, kaya sing dijanjekake, mung sawetara baris:
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];
};
Tes
Ayo priksa kinerja layanan kasebut. Kanggo nindakake iki, ayo mbukak ing proses kapisah (sijine kode ing file service.q) lan nelpon fungsi init:
q service.q βp 5566
q)init[]
Ing console liyane, miwiti proses Q kapindho lan sambungake menyang pisanan:
h:hopen `:host:5566
h:hopen 5566 // Π΅ΡΠ»ΠΈ ΠΎΠ±Π° Π½Π° ΠΎΠ΄Π½ΠΎΠΌ Ρ
ΠΎΡΡΠ΅
Pisanan, ayo nggawe dhaptar simbol - 10000 potongan lan nambah fungsi kanggo nggawe tabel acak. Ing konsol kapindho:
syms:`IBM`AAPL`GOOG,-9997?`8
rnd:{[n;t] ([] sym:n?syms; time:t+asc n#til 25; price:n?10f; size:n?10)}
Aku ditambahakΓ© telung simbol nyata kanggo dhaftar make iku luwih gampang kanggo katon ing meja. Fungsi rnd nggawe tabel acak karo n larik, ngendi wektu beda-beda gumantung saka t kanggo t + 25 milliseconds.
Saiki sampeyan bisa nyoba ngirim data menyang layanan (nambah sepuluh jam pisanan):
{h (`upd;`trade;rnd[10000;x])} each `time$00:00 + til 60*10
Sampeyan bisa mriksa ing layanan sing tabel wis dianyari:
c 25 200
select from tradeAgg where sym=`AAPL
-20#select from tradeAgg where sym=`AAPL
Asil:
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
Saiki ayo nindakake tes beban kanggo ngerteni jumlah data sing bisa diproses saben menit. Ayo kula ngelingake yen kita nyetel interval nganyari dadi 25 milidetik. Patut, layanan kasebut kudu (rata-rata) pas karo paling sethithik 20 milidetik saben nganyari kanggo menehi wektu pangguna kanggo njaluk data. Ketik ing ngisor iki ing proses kapindho:
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 iku rong menit. Sampeyan bisa nyoba mlaku dhisik kanggo 1000 baris saben 25 milidetik:
start 1000
Ing kasusku, asile watara saperangan milliseconds saben nganyari. Dadi aku bakal langsung nambah jumlah baris dadi 10.000:
start 10000
Asil:
min| 00:00:00.004
avg| 9.191458
med| 9f
max| 00:00:00.030
Maneh, ora ana sing khusus, nanging iki 24 yuta garis saben menit, 400 ewu per detik. Kanggo luwih saka 25 milliseconds, nganyari mung kalem 5 kaping, ketoke nalika menit diganti. Ayo tambah dadi 100.000:
start 100000
Asil:
min| 00:00:00.013
avg| 25.11083
med| 24f
max| 00:00:00.108
q)sum times
00:02:00.532
Kaya sing sampeyan ngerteni, layanan kasebut meh ora bisa ditindakake, nanging tetep bisa tetep. Volume data kasebut (240 yuta larik saben menit) gedhe banget; ing kasus kaya mengkono, umume ngluncurake sawetara klon (utawa malah puluhan klon) layanan kasebut, sing saben-saben mung ngolah bagean saka karakter. Isih, asil nyengsemaken kanggo basa sing diinterpretasikake sing fokus utamane ing panyimpenan data.
Pitakonan bisa uga muncul kenapa wektu tuwuh non-linear kanthi ukuran saben nganyari. Alesane yaiku fungsi shrink iku sejatine fungsi C, sing luwih efisien tinimbang updateAgg. Miwiti saka ukuran nganyari tartamtu (sekitar 10.000), updateAgg tekan langit-langit lan wektu eksekusi ora gumantung saka ukuran nganyari. Amarga langkah awal Q, layanan kasebut bisa nyerna volume data kasebut. Iki nyorot pentinge milih algoritma sing bener nalika nggarap data gedhe. Titik liyane yaiku panyimpenan data sing bener ing memori. Yen data ora disimpen columnarly utawa padha ora dhawuh dening wektu, kita bakal dadi menowo karo bab kaya TLB cache miss - ananΓ© alamat kaca memori ing cache alamat prosesor. Nelusuri alamat njupuk kira-kira 30 kaping maneh yen ora kasil, lan yen data kasebar, iku bisa alon mudhun layanan kaping pirang-pirang.
kesimpulan
Ing artikel iki, aku nuduhake yen database KDB + lan Q cocok ora mung kanggo nyimpen data gedhe lan gampang diakses liwat pilih, nanging uga kanggo nggawe layanan pangolahan data sing bisa nyerna atusan yuta larik / gigabyte data malah ing siji proses Q. Basa Q dhewe ngidini implementasine algoritma sing ringkes lan efisien sing ana gandhengane karo pangolahan data amarga sifat vektor, interpreter dialek SQL sing dibangun lan fungsi perpustakaan sing sukses banget.
Aku bakal Wigati sing ndhuwur mung bagean saka apa Q bisa nindakake, iku uga fitur unik liyane. Contone, protokol IPC sing gampang banget sing mbusak wates antarane proses Q individu lan ngidini sampeyan nggabungake atusan proses kasebut dadi jaringan siji, sing bisa ditemokake ing puluhan server ing macem-macem bagean ing saindenging jagad.
Source: www.habr.com