حقيقي وقت جي خدمت جو مثال استعمال ڪندي Q ۽ KDB + ٻولي جا خاصيتون

توهان پڙهي سگهو ٿا ته KDB+ بنيادي، Q پروگرامنگ ٻولي ڇا آهي، انهن جون قوتون ۽ ڪمزوريون ڪهڙيون آهن منهنجي پوئين ۾ مضمون ۽ مختصر تعارف ۾. آرٽيڪل ۾، اسان Q تي هڪ خدمت لاڳو ڪنداسين جيڪا ايندڙ ڊيٽا جي وهڪري کي پروسيس ڪندي ۽ "ريئل ٽائيم" موڊ ۾ هر منٽ ۾ مختلف مجموعي ڪمن جو حساب ڪندي (يعني، ڊيٽا جي ايندڙ حصي کان اڳ هر شيء کي ڳڻڻ جو وقت هوندو). Q جي مکيه خصوصيت اها آهي ته اها هڪ ویکٹر ٻولي آهي جيڪا توهان کي هڪ واحد شين سان هلائڻ جي اجازت ڏئي ٿي، پر انهن جي صفن، arrays جي صفن ۽ ٻين پيچيده شين سان. ٻوليون جهڙوڪ ق ۽ ان جا مائٽ K، J، APL پنهنجي اختصار لاءِ مشهور آهن. گهڻو ڪري، هڪ پروگرام جيڪو هڪ واقف ٻولي ۾ ڪوڊ جي ڪيترن ئي اسڪرين کي وٺي ٿو جهڙوڪ جاوا انهن تي ڪجهه لائينن ۾ لکي سگهجي ٿو. اھو اھو آھي جيڪو مان ھن مضمون ۾ ڏيکارڻ چاھيان ٿو.

حقيقي وقت جي خدمت جو مثال استعمال ڪندي Q ۽ KDB + ٻولي جا خاصيتون

تعارف

KDB+ هڪ ڪالمن ڊيٽابيس آهي جيڪو تمام وڏي مقدار ۾ ڊيٽا تي مرکوز آهي، هڪ مخصوص طريقي سان ترتيب ڏنو ويو آهي (بنيادي طور تي وقت جي لحاظ کان). اهو بنيادي طور تي مالي ادارن ۾ استعمال ٿيندو آهي - بئنڪ، سيڙپڪاري فنڊ، انشورنس ڪمپنيون. Q ٻولي KDB+ جي اندروني ٻولي آهي جيڪا توهان کي هن ڊيٽا سان مؤثر طريقي سان ڪم ڪرڻ جي اجازت ڏئي ٿي. ق نظريو اختصار ۽ ڪارڪردگي آهي، جڏهن ته وضاحت قربان ڪئي وئي آهي. اهو ان حقيقت سان ثابت ٿئي ٿو ته ویکٹر ٻولي ڪنهن به صورت ۾ سمجهڻ ڏکيو ٿيندو، ۽ رڪارڊنگ جي مختصر ۽ اميري توهان کي هڪ اسڪرين تي پروگرام جو تمام وڏو حصو ڏسڻ جي اجازت ڏئي ٿي، جيڪا آخرڪار سمجهڻ آسان بڻائي ٿي.

هن آرٽيڪل ۾ اسان ق ۾ هڪ مڪمل پروگرام لاڳو ڪيو ۽ توهان شايد ان کي ڪوشش ڪرڻ چاهيندا. ائين ڪرڻ لاءِ، توهان کي اصل سوال جي ضرورت پوندي. توهان kx ڪمپني جي ويب سائيٽ تي مفت 32-bit ورجن ڊائون لوڊ ڪري سگهو ٿا. www.kx.com. اتي، جيڪڏھن توھان دلچسپي وٺندا آھيو، توھان کي ڪتاب، ق تي حوالن جي معلومات ملندي Q For Mortals ۽ هن موضوع تي مختلف مضمون.

مسئلو جي ترتيب

اتي ھڪڙو ذريعو آھي جيڪو ڊيٽا سان گڏ ٽيبل موڪلي ٿو ھر 25 مليسيڪنڊ. جيئن ته KDB+ بنيادي طور تي فنانس ۾ استعمال ٿئي ٿو، اسان سمجهون ٿا ته هي ٽرانزيڪشن (واپار) جو هڪ جدول آهي، جنهن ۾ هيٺيان ڪالمن آهن: وقت (ملي سيڪنڊن ۾ وقت)، سم (اسٽاڪ ايڪسچينج تي ڪمپني نامزدگي - IBM, AAPL,…)، قيمت (قيمت جنهن تي شيئر خريد ڪيا ويا)، سائيز (ٽرانزيڪشن جو سائز). 25 مليسيڪنڊ جو وقفو پاڻمرادو آهي، نه تمام ننڍو ۽ نه تمام ڊگهو. ان جي موجودگي جو مطلب آهي ته ڊيٽا اڳ ۾ ئي بفر ٿيل خدمت ۾ اچي ٿو. موجوده لوڊ جي لحاظ کان متحرڪ بفرنگ سميت، سروس جي پاسي تي بفرنگ کي لاڳو ڪرڻ آسان ٿيندو، پر سادگي لاءِ، اسان هڪ مقرر وقفي تي ڌيان ڏينداسين.

خدمت کي لازمي طور تي ڳڻڻ گهرجي ته هر ايندڙ نشاني لاءِ هر منٽ سم ڪالمن مان مجموعي ڪمن جو هڪ سيٽ - وڌ ۾ وڌ قيمت، سراسري قيمت، رقم جي ماپ، وغيره. مفيد معلومات. سادگيءَ لاءِ، اسان فرض ڪنداسين ته سڀ افعال حساب ڪري سگھجن ٿا، يعني. نئين قيمت حاصل ڪرڻ لاء، اهو ڪافي آهي ته ٻه نمبر ڄاڻڻ لاء - پراڻي ۽ ايندڙ قدر. مثال طور، فنڪشن max، اوسط، sum ۾ هي ملڪيت آهي، پر وچين فنڪشن نه آهي.

اسان اهو پڻ فرض ڪنداسين ته ايندڙ ڊيٽا وهڪرو وقت جو حڪم ڏنو ويو آهي. اهو اسان کي صرف آخري منٽ سان ڪم ڪرڻ جو موقعو ڏيندو. عملي طور تي، اهو ڪافي آهي ته موجوده ۽ پوئين منٽن سان ڪم ڪرڻ جي قابل ٿيڻ جي صورت ۾ ڪجهه تازه ڪاريون دير سان آهن. سادگي لاء، اسان هن معاملي تي غور نه ڪنداسين.

گڏ ڪرڻ جا ڪم

گهربل مجموعا افعال ھيٺ ڏنل آھن. مون انهن مان جيترو ٿي سگهي ورتو سروس تي لوڊ وڌائڻ لاءِ:

  • اعلي - وڌ ۾ وڌ قيمت - وڌ ۾ وڌ قيمت في منٽ.
  • گھٽ - منٽ قيمت - گھٽ ۾ گھٽ قيمت في منٽ.
  • پهرين قيمت - پهرين قيمت - پهرين قيمت في منٽ.
  • آخري قيمت - آخري قيمت - آخري قيمت في منٽ.
  • FirstSize - پهرين سائيز - پهرين واپاري سائيز في منٽ.
  • آخري سائيز - آخري ماپ - آخري واپاري سائيز هڪ منٽ ۾.
  • numTrades - شمار i - واپار جو تعداد في منٽ.
  • حجم - رقم جي ماپ - واپار جي ماپ جو مجموعو في منٽ.
  • pvolume - رقم جي قيمت - قيمتن جو مجموعو في منٽ، avgPrice لاءِ گهربل.
  • - مجموعي واپار جي قيمت * سائيز - ٽرانزيڪشن جو ڪل مقدار في منٽ.
  • avgPrice – pvolume%numTrades – سراسري قيمت في منٽ.
  • avgSize - حجم٪ numTrades - سراسري واپاري سائيز في منٽ.
  • vwap - ٽران اوور٪ حجم - اوسط قيمت في منٽ ٽرانزيڪشن سائيز جي لحاظ کان وزن.
  • cumVolume - مجموعو حجم - سڄي وقت ۾ ٽرانزيڪشن جي جمع ٿيل سائيز.

اچو ته فوري طور تي هڪ غير واضح نقطي تي بحث ڪريون - انهن ڪالمن کي پهريون ڀيرو ۽ هر ايندڙ منٽ لاءِ ڪيئن شروع ڪجي. پهرين قيمت جي قسم جا ڪجهه ڪالمن هر دفعي null ڪرڻ لاءِ شروع ڪيا وڃن؛ انهن جي قيمت اڻڄاتل آهي. ٻين حجمن جي قسمن کي هميشه 0 تي مقرر ڪيو وڃي. اتي پڻ ڪالم آھن جن کي ھڪڙي گڏيل طريقي جي ضرورت آھي - مثال طور، cumVolume کي پوئين منٽ کان نقل ڪيو وڃي، ۽ پھريون ھڪڙي لاء 0 تي سيٽ ڪيو وڃي. اچو ته ڊڪشنري ڊيٽا استعمال ڪندي اھي سڀئي پيرا ميٽر سيٽ ڪريون. قسم (ريڪارڊ جي مطابق):

// list ! list – создать словарь, 0n – float null, 0N – long null, `sym – тип символ, `sym1`sym2 – список символов
initWith:`sym`time`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover`avgPrice`avgSize`vwap`cumVolume!(`;00:00;0n;0n;0n;0n;0N;0N;0;0;0.0;0.0;0n;0n;0n;0);
aggCols:reverse key[initWith] except `sym`time; // список всех вычисляемых колонок, reverse объяснен ниже

مون سهولت لاءِ ڊڪشنري ۾ sym ۽ time شامل ڪيو، ھاڻي initWith آخري مجموعي جدول مان تيار ٿيل لائن آھي، جتي صحيح سم ۽ وقت مقرر ڪرڻ لاءِ باقي رھيو آھي. توھان ان کي استعمال ڪري سگھوٿا ھڪڙي ٽيبل تي نيون قطارون شامل ڪرڻ لاءِ.

اسان کي aggCols جي ضرورت پوندي جڏهن هڪ مجموعي فنڪشن ٺاهي. لسٽ لازمي طور تي ان ترتيب جي ڪري ڦيرايو وڃي جنهن ۾ Q ۾ اظهار جو جائزو ورتو ويو آهي (ساڄي کان کاٻي طرف). مقصد اهو يقيني بڻائڻ آهي ته حساب ڪتاب اعلي کان گهٽ حجم تائين وڃي ٿو، ڇاڪاڻ ته ڪجهه ڪالمن پوئين تي منحصر آهن.

ڪالم جيڪي پوئين منٽ کان نئين منٽ ۾ ڪاپي ڪرڻ جي ضرورت آهي، سم ڪالمن کي سهولت لاء شامل ڪيو ويو آهي:

rollColumns:`sym`cumVolume;

ھاڻي اچو ته ڪالمن کي گروپن ۾ ورهايون ان مطابق انھن کي ڪيئن اپڊيٽ ڪيو وڃي. ٽن قسمن ۾ فرق ڪري سگهجي ٿو:

  1. جمع ڪندڙ (حجم، ٽرن اوور، ..) - اسان کي لازمي طور تي ايندڙ قيمت کي شامل ڪرڻ گهرجي.
  2. هڪ خاص نقطي سان (هاء، گهٽ، ..) - منٽ ۾ پهريون قدر ايندڙ ڊيٽا مان ورتو وڃي ٿو، باقي ڪم کي استعمال ڪندي حساب ڪيو ويو آهي.
  3. آرام. هميشه هڪ فنڪشن استعمال ڪندي حساب ڪيو.

اچو ته انهن طبقن لاء متغير جي وضاحت ڪريون:

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

حساب ڪتاب

اسان ٻن مرحلن ۾ مجموعي جدول کي اپڊيٽ ڪنداسين. ڪارڪردگيءَ لاءِ، اسان سڀ کان پهرين ايندڙ ٽيبل کي ڇڪيندا آهيون ته جيئن هر ڪردار ۽ منٽ لاءِ فقط هڪ قطار هجي. حقيقت اها آهي ته اسان جا سڀئي افعال واڌارو ۽ گڏيل ضمانت آهن ته هن اضافي قدم جو نتيجو تبديل نه ٿيندو. توھان ٽيبل کي ڇڪي سگھوٿا استعمال ڪندي چونڊيو:

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

ھن طريقي ۾ ھڪڙو نقصان آھي - حساب ڪيل ڪالمن جو سيٽ اڳ ۾ بيان ڪيل آھي. خوشقسمتيء سان، ق ۾، چونڊيو پڻ هڪ فنڪشن جي طور تي لاڳو ڪيو ويو آهي جتي توهان متحرڪ طور تي ٺاهيل دليلن کي متبادل ڪري سگهو ٿا:

?[table;whereClause;byClause;selectClause]

مان تفصيل سان دليلن جي فارميٽ کي بيان نه ڪندس؛ اسان جي صورت ۾، صرف ۽ صرف چونڊيل اظهار غير معمولي هوندا ۽ اهي فارم ڪالمن جي ڊڪشنري هجڻ گهرجن! ان ڪري، ڇڪڻ واري فنڪشن کي هيٺين ريت بيان ڪري سگهجي ٿو:

selExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover!parse each ("max price";"min price";"first price";"last price";"first size";"last size";"count i";"sum size";"sum price";"sum price*size"); // each это функция map в Q для одного списка
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];

وضاحت لاءِ، مون پارس فنڪشن استعمال ڪيو، جيڪو Q ايڪسپريشن سان هڪ اسٽرنگ کي هڪ قدر ۾ تبديل ڪري ٿو جيڪو ايول فنڪشن ڏانهن منتقل ٿي سگهي ٿو ۽ جيڪو فنڪشن جي چونڊ ۾ گهربل آهي. اهو پڻ نوٽ ڪريو ته اڳوڻو عمل هڪ پروجئشن جي طور تي بيان ڪيو ويو آهي (يعني، جزوي طور تي بيان ڪيل دليلن سان هڪ فنڪشن) چونڊيو فنڪشن جي، هڪ دليل (ٽيبل) غائب آهي. جيڪڏهن اسان هڪ ٽيبل تي اڳڀرائي لاڳو ڪنداسين، اسان کي هڪ ٺهيل ٽيبل حاصل ڪنداسين.

ٻيو مرحلو مجموعي جدول کي اپڊيٽ ڪرڻ آهي. اچو ته پهرين الگورٿم کي 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];
  …

ق ۾، لوپ جي بدران نقشي/گهٽائي ڪمن کي استعمال ڪرڻ عام آهي. پر جيئن ته Q هڪ ویکٹر ٻولي آهي ۽ اسان آساني سان سڀني عملن کي سڀني علامتن تي هڪ ئي وقت لاڳو ڪري سگهون ٿا، پوءِ پهرين تقريبن تائين اسان بغير ڪنهن لوپ جي ڪري سگهون ٿا، هڪ ئي وقت سڀني علامتن تي آپريشن ڪري سگھون ٿا:

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

پر اسان اڳتي وڌي سگھون ٿا، Q وٽ ھڪڙو منفرد ۽ انتهائي طاقتور آپريٽر آھي - عام اسائنمينٽ آپريٽر. اهو توهان کي هڪ پيچيده ڊيٽا جي جوڙجڪ ۾ قدر جو هڪ سيٽ تبديل ڪرڻ جي اجازت ڏئي ٿو انڊيڪس، افعال ۽ دليلن جي فهرست استعمال ڪندي. اسان جي حالت ۾، اهو هن طرح نظر اچي ٿو:

idx:calcIdx inputTable;
rows:aggTable idx;
// .[target;(idx0;idx1;..);function;argument] ~ target[idx 0;idx 1;…]: function[target[idx 0;idx 1;…];argument], в нашем случае функция – это присваивание
.[aggTable;(idx;aggCols);:;flip (row[`high] | inputTable`high;row[`volume] + inputTable`volume;…)];

بدقسمتي سان، ڪنهن ٽيبل کي تفويض ڪرڻ لاءِ توهان کي قطارن جي فهرست جي ضرورت آهي، نه ڪالمن جي، ۽ توهان کي فلپ فنڪشن استعمال ڪندي ميٽرڪس (ڪالمن جي فهرست کي قطارن جي فهرست) منتقل ڪرڻو پوندو. اهو هڪ وڏي ٽيبل لاءِ مهانگو آهي، تنهن ڪري ان جي بدران اسان نقشي جي فنڪشن کي استعمال ڪندي، هر ڪالمن تي الڳ الڳ هڪ عام ڪيل تفويض لاڳو ڪريون ٿا (جيڪو apostrophe وانگر نظر اچي ٿو):

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

اسان ٻيهر استعمال ڪريو فنڪشن پروجئشن. اهو پڻ نوٽ ڪريو ته Q ۾، هڪ فهرست ٺاهڻ پڻ هڪ فنڪشن آهي ۽ اسان فهرستن جي فهرست حاصل ڪرڻ لاء هر (نقشي) فنڪشن استعمال ڪندي ان کي سڏي سگهون ٿا.

انهي کي يقيني بڻائڻ لاءِ ته حساب ڪيل ڪالمن جو سيٽ مقرر نه ڪيو ويو آهي، اسان مٿين اظهار کي متحرڪ طور تي ٺاهينداسين. اچو ته سڀ کان پهريان ھر ڪالمن کي ڳڻڻ لاءِ افعال جي وضاحت ڪريون، قطار ۽ انپ متغير استعمال ڪندي مجموعي ۽ ان پٽ ڊيٽا جو حوالو ڏيو:

aggExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`avgPrice`avgSize`vwap`cumVolume!
 ("row[`high]|inp`high";"row[`low]&inp`low";"row`firstPrice";"inp`lastPrice";"row`firstSize";"inp`lastSize";"pvolume%numTrades";"volume%numTrades";"turnover%volume";"row[`cumVolume]+inp`volume");

ڪجھ ڪالمن خاص آھن؛ انھن جي پھرين قدر کي فنڪشن جي حساب سان نه ڳڻيو وڃي. اسان اهو اندازو لڳائي سگهون ٿا ته اها قطار [`numTrades] ڪالمن جي لحاظ کان پهرين آهي - جيڪڏهن ان ۾ 0 آهي، ته پوءِ قدر پهرين آهي. Q ۾ هڪ منتخب فنڪشن آهي - ?[Boolan list;list1;list2] - جيڪو فهرست 1 يا 2 مان هڪ قدر چونڊيندو آهي ان تي منحصر ڪري ٿو پهرين دليل ۾ شرط:

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

هتي مون پنهنجي فنڪشن سان گڏ هڪ عام اسائنمينٽ کي سڏيو آهي (هڪ اظهار گھمڻ واري ڪنگڻ ۾). اهو موجوده قدر حاصل ڪري ٿو (پهريون دليل) ۽ هڪ اضافي دليل، جيڪو آئون 4th پيٽرولر ۾ گذري ٿو.

اچو ته بيٽري اسپيڪر کي الڳ الڳ شامل ڪريون، ڇاڪاڻ ته فنڪشن انهن لاء ساڳيو آهي:

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

هي Q معيارن جي لحاظ کان هڪ عام تفويض آهي، پر مان هڪ ئي وقت ۾ قدرن جي هڪ فهرست تفويض ڪري رهيو آهيان. آخرڪار، اچو ته مکيه فنڪشن ٺاهيو:

// ":",/:aggExprs ~ map[{":",x};aggExpr] => ":row[`high]|inp`high" присвоим вычисленное значение переменной, потому что некоторые колонки зависят от уже вычисленных значений
// string[cols],'exprs ~ map[,;string[cols];exprs] => "high:row[`high]|inp`high" завершим создание присваивания. ,’ расшифровывается как map[concat]
// ";" sv exprs – String from Vector (sv), соединяет список строк вставляя “;” посредине
updateAgg:value "{[aggTable;idx;inp] row:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols;(",(";"sv string[aggCols],'":",/:aggExpression aggCols),")]}";

هن ايڪسپريشن سان، مان متحرڪ طور هڪ اسٽرنگ مان هڪ فنڪشن ٺاهيان ٿو جنهن ۾ اهو اظهار شامل آهي جيڪو مون مٿي ڏنو آهي. نتيجو هن طرح نظر ايندو:

{[aggTable;idx;inp] rows:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols ;(cumVolume:row[`cumVolume]+inp`cumVolume;… ; high:?[isFirst;inp`high;row[`high]|inp`high])]}

ڪالمن جي تشخيص جو آرڊر انٽوريٽ ڪيو ويو آهي ڇاڪاڻ ته Q ۾ تشخيص آرڊر ساڄي کان کاٻي طرف آهي.

ھاڻي اسان وٽ ٻه مکيه ڪم آھن جيڪي حساب ڪتاب لاءِ ضروري آھن، اسان کي رڳو ٿورڙو انفراسٽرڪچر شامل ڪرڻو آھي ۽ خدمت تيار آھي.

آخري مرحلو

اسان وٽ اڳڀرائي ۽ تازه ڪاري Agg فنڪشن آهي جيڪي سڀ ڪم ڪن ٿا. پر اهو اڃا تائين ضروري آهي ته منٽن ذريعي صحيح منتقلي کي يقيني بڻائڻ ۽ مجموعي لاء انڊيڪس جي حساب سان. سڀ کان پهريان، اچو ته init فنڪشن جي وضاحت ڪريون:

init:{
  tradeAgg:: 0#enlist[initWith]; // создаем пустую типизированную таблицу, enlist превращает словарь в таблицу, а 0# означает взять 0 элементов из нее
  currTime::00:00; // начнем с 0, :: означает, что присваивание в глобальную переменную
  currSyms::`u#`symbol$(); // `u# - превращает список в дерево, для ускорения поиска элементов
  offset::0; // индекс в tradeAgg, где начинается текущая минута 
  rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg; // кэш для последних значений roll колонок, таблица с ключом sym
 }

اسان رول فنڪشن کي پڻ بيان ڪنداسين، جيڪو موجوده منٽ کي تبديل ڪندو:

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

اسان کي نون اکرن کي شامل ڪرڻ لاءِ فنڪشن جي ضرورت پوندي:

addSyms:{[syms]
  currSyms,::syms; // добавим в список известных
  // добавим в таблицу sym, time и rollColumns воспользовавшись обобщенным присваиванием.
  // Функция ^ подставляет значения по умолчанию для roll колонок, если символа нет в кэше. value flip table возвращает список колонок в таблице.
  `tradeAgg upsert @[count[syms]#enlist initWith;`sym`time,cols rc;:;(syms;currTime), (initWith cols rc)^value flip rc:rollCache ([] sym: syms)];
 }

۽ آخرڪار، اپڊيٽ فنڪشن (ق سروسز لاءِ هن فنڪشن جو روايتي نالو)، جنهن کي ڪلائنٽ طرفان ڊيٽا شامل ڪرڻ لاءِ سڏيو ويندو آهي:

upd:{[tblName;data] // tblName нам не нужно, но обычно сервис обрабатывает несколько таблиц 
  tm:exec distinct time from data:() xkey preprocess data; // preprocess & calc time
  updMinute[data] each tm; // добавим данные для каждой минуты
};
updMinute:{[data;tm]
  if[tm<>currTime; roll tm; currTime::tm]; // поменяем минуту, если необходимо
  data:select from data where time=tm; // фильтрация
  if[count msyms:syms where not (syms:data`sym)in currSyms; addSyms msyms]; // новые символы
  updateAgg[`tradeAgg;offset+currSyms?syms;data]; // обновим агрегированную таблицу. Функция ? ищет индекс элементов списка справа в списке слева.
 };

اهو ئي سڀ ڪجهه آهي. هتي اسان جي خدمت جو مڪمل ڪوڊ آهي، جيئن واعدو ڪيو ويو آهي، صرف چند لائينون:

initWith:`sym`time`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover`avgPrice`avgSize`vwap`cumVolume!(`;00:00;0n;0n;0n;0n;0N;0N;0;0;0.0;0.0;0n;0n;0n;0);
aggCols:reverse key[initWith] except `sym`time;
rollColumns:`sym`cumVolume;

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

selExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`numTrades`volume`pvolume`turnover!parse each ("max price";"min price";"first price";"last price";"first size";"last size";"count i";"sum size";"sum price";"sum price*size");
preprocess:?[;();`sym`time!`sym`time.minute;selExpression];

aggExpression:`high`low`firstPrice`lastPrice`firstSize`lastSize`avgPrice`avgSize`vwap`cumVolume!("row[`high]|inp`high";"row[`low]&inp`low";"row`firstPrice";"inp`lastPrice";"row`firstSize";"inp`lastSize";"pvolume%numTrades";"volume%numTrades";"turnover%volume";"row[`cumVolume]+inp`volume");
@[`aggExpression;specialCols;{"?[isFirst;inp`",y,";",x,"]"};string specialCols];
aggExpression[accumulatorCols]:{"row[`",x,"]+inp`",x } each string accumulatorCols;
updateAgg:value "{[aggTable;idx;inp] row:aggTable idx; isFirst_0=row`numTrades; .[aggTable;;:;]'[(idx;)each aggCols;(",(";"sv string[aggCols],'":",/:aggExpression aggCols),")]}"; / '

init:{
  tradeAgg::0#enlist[initWith];
  currTime::00:00;
  currSyms::`u#`symbol$();
  offset::0;
  rollCache:: `sym xkey update `u#sym from rollColumns#tradeAgg;
 };
roll:{[tm]
  if[currTime>tm; :init[]];
  rollCache,::offset _ rollColumns#tradeAgg;
  offset::count tradeAgg;
  currSyms::`u#`$();
 };
addSyms:{[syms]
  currSyms,::syms;
  `tradeAgg upsert @[count[syms]#enlist initWith;`sym`time,cols rc;:;(syms;currTime),(initWith cols rc)^value flip rc:rollCache ([] sym: syms)];
 };

upd:{[tblName;data] updMinute[data] each exec distinct time from data:() xkey preprocess data};
updMinute:{[data;tm]
  if[tm<>currTime; roll tm; currTime::tm];
  data:select from data where time=tm;
  if[count msyms:syms where not (syms:data`sym)in currSyms; addSyms msyms];
  updateAgg[`tradeAgg;offset+currSyms?syms;data];
 };

جاچ

اچو ته خدمت جي ڪارڪردگي جي جانچ ڪريو. هن کي ڪرڻ لاء، اچو ته ان کي هڪ الڳ عمل ۾ هلون (ڪوڊ کي service.q فائل ۾ رکون) ۽ init فنڪشن کي ڪال ڪريو:

q service.q –p 5566

q)init[]

ٻئي ڪنسول ۾، ٻيو ق عمل شروع ڪريو ۽ پهرين سان ڳنڍيو:

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

پهرين، اچو ته علامتن جي هڪ فهرست ٺاهيو - 10000 ٽڪر ۽ هڪ فنڪشن شامل ڪريو بي ترتيب ٽيبل ٺاهڻ لاءِ. ٻئي ڪنسول ۾:

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

مون لسٽ ۾ ٽي حقيقي علامتون شامل ڪيون آھن انھن کي ٽيبل ۾ ڳولڻ آسان بڻائڻ لاءِ. rnd فنڪشن n قطارن سان هڪ بي ترتيب ٽيبل ٺاهي ٿو، جتي وقت t کان t + 25 ملي سيڪنڊن ۾ مختلف ٿئي ٿو.

هاڻي توهان ڪوشش ڪري سگهو ٿا ڊيٽا موڪلڻ جي خدمت ڏانهن (پهرين ڏهه ڪلاڪ شامل ڪريو):

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

توهان خدمت ۾ چيڪ ڪري سگهو ٿا ته ٽيبل اپڊيٽ ڪيو ويو آهي:

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

نتيجو

sym|time|high|low|firstPrice|lastPrice|firstSize|lastSize|numTrades|volume|pvolume|turnover|avgPrice|avgSize|vwap|cumVolume
--|--|--|--|--|--------------------------------
AAPL|09:27|9.258904|9.258904|9.258904|9.258904|8|8|1|8|9.258904|74.07123|9.258904|8|9.258904|2888
AAPL|09:28|9.068162|9.068162|9.068162|9.068162|7|7|1|7|9.068162|63.47713|9.068162|7|9.068162|2895
AAPL|09:31|4.680449|0.2011121|1.620827|0.2011121|1|5|4|14|9.569556|36.84342|2.392389|3.5|2.631673|2909
AAPL|09:33|2.812535|2.812535|2.812535|2.812535|6|6|1|6|2.812535|16.87521|2.812535|6|2.812535|2915
AAPL|09:34|5.099025|5.099025|5.099025|5.099025|4|4|1|4|5.099025|20.3961|5.099025|4|5.099025|2919

اچو ته ھاڻي لوڊ جانچ ڪريون اھو معلوم ڪرڻ لاءِ ته خدمت ڪيترو ڊيٽا في منٽ تي عمل ڪري سگھي ٿي. مون کي توهان کي ياد ڏيارڻ ڏيو ته اسان اپڊيٽ جي وقفي کي 25 ملي سيڪنڊن تي مقرر ڪيو. ان جي مطابق، خدمت لازمي طور تي (اوسط طور تي) گھٽ ۾ گھٽ 20 مليسيڪنڊ في اپڊيٽ ۾ فٽ ٿيڻ گهرجي ته جيئن صارفين کي ڊيٽا جي درخواست ڪرڻ جو وقت ڏيو. ٻئي عمل ۾ ھيٺيون داخل ڪريو:

tm:10:00:00.000
stressTest:{[n] 1 string[tm]," "; times,::h ({st:.z.T; upd[`trade;x]; .z.T-st};rnd[n;tm]); tm+:25}
start:{[n] times::(); do[4800;stressTest[n]]; -1 " "; `min`avg`med`max!(min times;avg times;med times;max times)}

4800 ٻه منٽ آهي. توھان ڪوشش ڪري سگھوٿا پھريائين ھلڻ جي 1000 قطارن لاءِ ھر 25 ملي سيڪنڊن ۾:

start 1000

منهنجي حالت ۾، نتيجو لڳ ڀڳ ٻه ملي سيڪنڊ في اپڊيٽ آهي. تنهن ڪري آئون فوري طور تي قطارن جو تعداد وڌائي 10.000 تائين ڪندس:

start 10000

نتيجو

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

ٻيهر، ڪجهه خاص ناهي، پر اهو آهي 24 ملين لائين في منٽ، 400 هزار في سيڪنڊ. 25 مليسيڪنڊن کان وڌيڪ، اپڊيٽ صرف 5 ڀيرا سست ٿي، ظاهري طور تي جڏهن منٽ تبديل ٿي ويو. اچو ته وڌائي 100.000 تائين:

start 100000

نتيجو

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

جئين توهان ڏسي سگهو ٿا، خدمت مشڪل سان مقابلو ڪري سگهي ٿي، پر ان جي باوجود اهو برقرار رکڻ جو انتظام ڪري ٿو. ڊيٽا جو اهڙو مقدار (240 ملين قطار في منٽ) تمام وڏو آهي؛ اهڙين حالتن ۾، سروس جي ڪيترن ئي کلون (يا اڃا به درجن جي ڪلون) کي لانچ ڪرڻ عام آهي، جن مان هر هڪ صرف ڪردارن جي حصي تي عمل ڪري ٿو. اڃا تائين، نتيجو هڪ تعبير ٿيل ٻولي لاءِ متاثر ڪندڙ آهي جيڪو بنيادي طور تي ڊيٽا اسٽوريج تي ڌيان ڏئي ٿو.

سوال ٿي سگهي ٿو ته ڇو وقت هر اپڊيٽ جي سائيز سان غير لڪير سان وڌي ٿو. ان جو سبب اهو آهي ته ڇڪڻ فنڪشن اصل ۾ هڪ سي فنڪشن آهي، جيڪو اپڊيٽ ايگ کان گهڻو وڌيڪ موثر آهي. هڪ خاص تازه ڪاري سائيز (تقريبن 10.000) کان شروع ڪندي، اپڊيٽ ايگ پنهنجي حد تائين پهچي ٿو ۽ پوء ان جي عمل جو وقت اپڊيٽ جي سائيز تي منحصر ناهي. اهو ابتدائي قدم Q جي ڪري آهي ته خدمت ڊيٽا جي اهڙي مقدار کي هضم ڪرڻ جي قابل آهي. اهو نمايان ڪري ٿو ته اهو ڪيترو اهم آهي صحيح الورورٿم چونڊڻ جڏهن وڏي ڊيٽا سان ڪم ڪندي. ٻيو نقطو ميموري ۾ ڊيٽا جي صحيح اسٽوريج آهي. جيڪڏهن ڊيٽا ڪالمن ۾ محفوظ نه ڪئي وئي هئي يا وقت جي ترتيب سان ترتيب نه ڏني وئي هئي، ته پوء اسان اهڙي شيء کان واقف ٿينداسين جيئن هڪ TLB ڪيش مس - پروسيسر ايڊريس ڪيش ۾ ميموري صفحي جي پتي جي غير موجودگي. پتو ڳولڻ ۾ لڳ ڀڳ 30 ڀيرا وڌيڪ لڳندو آهي جيڪڏهن ناڪام ٿيو، ۽ جيڪڏهن ڊيٽا پکڙيل آهي، اهو سروس کي ڪيترائي ڀيرا سست ڪري سگهي ٿو.

ٿڪل

هن آرٽيڪل ۾، مون ڏيکاريو ته KDB+ ۽ Q ڊيٽابيس نه رڳو وڏي ڊيٽا کي محفوظ ڪرڻ ۽ ان کي چونڊ ذريعي آسانيءَ سان رسائي حاصل ڪرڻ لاءِ موزون آهن، پر ڊيٽا پروسيسنگ سروسز ٺاهڻ لاءِ پڻ آهن جيڪي سوين لکن قطارون/گيگا بائيٽ ڊيٽا هضم ڪرڻ جي قابل آهن. هڪ واحد Q عمل. Q ٻولي بذات خود اجازت ڏئي ٿي ڊيٽا پروسيسنگ سان لاڳاپيل الگورتھم جي انتهائي جامع ۽ موثر عمل درآمد جي لاءِ ان جي ویکٹر نوعيت جي ڪري، بلٽ ان SQL ڊائلڪٽ مترجم ۽ لائبريري ڪمن جو هڪ تمام ڪامياب سيٽ.

مان نوٽ ڪندس ته مٿيان صرف هڪ حصو آهي جيڪو Q ڪري سگهي ٿو، ان ۾ ٻيون منفرد خاصيتون پڻ آهن. مثال طور، هڪ انتهائي سادو IPC پروٽوڪول جيڪو انفرادي Q پروسيس جي وچ ۾ حد کي ختم ڪري ٿو ۽ توهان کي انهن عملن مان سوين کي هڪ واحد نيٽ ورڪ ۾ گڏ ڪرڻ جي اجازت ڏئي ٿو، جيڪو دنيا جي مختلف حصن ۾ درجن جي سرورن تي واقع ٿي سگهي ٿو.

جو ذريعو: www.habr.com

تبصرو شامل ڪريو