ميزات Q و KDB + على مثال خدمة في الوقت الحقيقي

ماهية KDB + ، لغة برمجة Q ، نقاط قوتها وضعفها ، يمكن العثور عليها في مقالي السابق وباختصار في المقدمة. في هذه المقالة ، نقوم بتنفيذ خدمة على Q ستقوم بمعالجة دفق البيانات الواردة وحساب وظائف التجميع المختلفة في الدقيقة في وضع "الوقت الفعلي" (أي ، ستتمكن من حساب كل شيء حتى الجزء التالي من البيانات). الميزة الرئيسية لـ Q هي أنها لغة متجهية تتيح لك العمل ليس مع كائنات مفردة ، ولكن مع صفائفها ومصفوفات المصفوفات والكائنات المعقدة الأخرى. تشتهر لغات مثل Q و K و J و APL ذات الصلة بإيجازها. غالبًا ما يمكن كتابة برنامج يمتد على عدة شاشات من الرموز بلغة مألوفة مثل Java عليها في عدة أسطر. هذا هو بالضبط ما أريد أن أوضح في هذه المقالة.



مقدمة


KDB + هي قاعدة بيانات عمود تركز على وحدات تخزين كبيرة جدًا من البيانات ، يتم فرزها بطريقة معينة (بشكل أساسي حسب الوقت). يتم استخدامه ، أولاً وقبل كل شيء ، في المؤسسات المالية - البنوك وصناديق الاستثمار وشركات التأمين. لغة Q هي لغة داخلية لـ KDB + تتيح لك العمل بفعالية مع هذه البيانات. أيديولوجية Q هي الإيجاز والكفاءة ، بينما يتم التضحية بالوضوح. هذا ما يبرره حقيقة أنه في أي حال سيكون من الصعب إدراك لغة المتجه ، ويسمح لك إيجاز التسجيل وثرائه برؤية جزء أكبر بكثير من البرنامج على شاشة واحدة ، مما يسهل في النهاية فهمه.

في هذه المقالة ، ننفذ برنامج Q متكامل ، وقد ترغب في تجربته. للقيام بذلك ، ستحتاج إلى Q نفسها ، ويمكنك تنزيل الإصدار 32 بت المجاني من موقع kx على الويب - www.kx.com . في نفس المكان ، إذا كنت مهتمًا ، ستجد معلومات مرجعية عن Q ، والكتاب Q For Mortals ومقالات متنوعة حول هذا الموضوع.

بيان المشكلة


يوجد مصدر يرسل جدول بيانات كل 25 مللي ثانية. نظرًا لأن KDB + يستخدم بشكل أساسي في التمويل ، فنحن نفترض أن هذا هو جدول الصفقات الذي توجد فيه الأعمدة التالية: الوقت (بالميلي ثانية) ، sym (اسم الشركة في البورصة - IBM ، AAPL ، ...) ، السعر (السعر التي تم شراؤها الأسهم) ، والحجم (حجم المعاملة). يتم اختيار فاصل زمني من 25 مللي ثانية بشكل تعسفي ، أنها ليست صغيرة جدا وليست كبيرة جدا. وجودها يعني أن البيانات التي تصل إلى الخدمة مخزنة بالفعل. سيكون من السهل تنفيذ التخزين المؤقت على جانب الخدمة ، بما في ذلك التخزين المؤقت الديناميكي ، اعتمادًا على الحمل الحالي ، ولكن من أجل البساطة نتحدث عن فاصل زمني ثابت.

يجب أن تحسب الخدمة كل دقيقة لكل حرف وارد من العمود sym مجموعة من الوظائف التجميعية - الحد الأقصى للسعر ، متوسط ​​السعر ، حجم المبلغ ، إلخ. معلومات مفيدة. من أجل البساطة ، نفترض أنه يمكن حساب جميع الوظائف بشكل تدريجي ، أي للحصول على قيمة جديدة ، يكفي معرفة رقمين - القيمة القديمة والقيمة الواردة. على سبيل المثال ، تحتوي الدالتان max و average و sum على هذه الخاصية ، لكن الدالة الوسيطة لا تحتوي على هذه الخاصية.

نحن نفترض أيضًا أن دفق البيانات الواردة مرتبة حسب الوقت. هذا سيتيح لنا الفرصة للعمل فقط في اللحظة الأخيرة. في الممارسة العملية ، يكفي أن تكون قادرًا على العمل مع الدقائق الحالية والسابقة في حالة تأخر أي تحديثات. للبساطة ، ونحن لن تنظر في هذه القضية.

الوظائف الكلية


المدرجة أدناه هي الوظائف الإجمالية المطلوبة. أخذتها إلى أقصى حد ممكن لزيادة الحمل على الخدمة:

  • السعر العالي - الحد الأقصى للسعر في الدقيقة.
  • السعر المنخفض - الحد الأدنى للسعر في الدقيقة.
  • firstPrice - السعر الأول - السعر الأول في الدقيقة.
  • lastPrice - آخر سعر - آخر سعر في الدقيقة.
  • firstSize - الحجم الأول - حجم الصفقة الأول في دقيقة واحدة.
  • lastSize - الحجم الأخير - حجم الصفقة الأخير في دقيقة واحدة.
  • numTrades - العد i - عدد المعاملات في الدقيقة.
  • حجم - حجم المبلغ - مجموع أحجام المعاملات في الدقيقة الواحدة.
  • pvolume - مبلغ السعر - مجموع الأسعار في الدقيقة ، وهو أمر ضروري لمتوسط ​​السعر.
  • حجم التداول - المبلغ الإجمالي * الحجم - إجمالي حجم المعاملات في الدقيقة.
  • avgPrice - pvolume٪ numTrades - متوسط ​​سعر الدقيقة.
  • avgSize - حجم٪ numTrades - متوسط ​​حجم الصفقة في الدقيقة.
  • vwap - حجم التداول٪ حجم - متوسط ​​السعر في الدقيقة الواحدة مرجحًا بحجم المعاملة.
  • cumVolume - حجم التداول - حجم المعاملة المتراكمة طوال الوقت.

ناقش على الفور نقطة واحدة غير واضحة - كيفية تهيئة هذه الأعمدة لأول مرة ولكل دقيقة تالية. يجب تهيئة بعض الأعمدة من نوع firstPrice بالقيمة الخالية في كل مرة ، ولا يتم تحديد قيمتها. يجب دائمًا تعيين أنواع أخرى من وحدة التخزين على 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 ووقته إلى القاموس للراحة ، الآن initWith عبارة عن سطر نهائي من الجدول المجمع النهائي ، حيث يبقى تعيين sym والوقت الصحيحين. يمكنك استخدامه لإضافة صفوف جديدة إلى الجدول.

aggCols التي نحتاجها عند إنشاء وظيفة تجميعية. يجب عكس القائمة نظرًا لخصائص الترتيب الذي يتم به حساب التعبيرات في Q (من اليمين إلى اليسار). الهدف هو توفير حساب في الاتجاه من الأعلى إلى cumVolume ، حيث أن بعض الأعمدة تعتمد على الأعمدة السابقة.

الأعمدة المراد نسخها إلى دقيقة جديدة من العمود السابق ، تم إضافة عمود sym للراحة:

 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 

تحتوي هذه الطريقة على ناقص - تم تحديد مجموعة الأعمدة المحتسبة مسبقًا. لحسن الحظ ، في Q ، يتم تطبيق التحديد أيضًا كدالة حيث يمكنك استبدال الوسائط التي تم إنشاؤها ديناميكيًا:

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

من أجل الوضوح ، استخدمت الدالة parse التي تحول السلسلة ذات تعبير Q إلى قيمة يمكن تمريرها إلى دالة eval والمطلوبة في تحديد الوظيفة. لاحظ أيضًا أنه يتم تعريف preprocess كإسقاط (بمعنى ، دالة ذات وسيطات محددة جزئيًا) للدالة select ، وسيطة واحدة (الجدول) مفقودة. إذا قمنا بتطبيق preprocess على جدول ، فسنحصل على جدول منكمش.

المرحلة الثانية هي تحديث الجدول المجمع. أولاً ، نكتب الخوارزمية في الكود الكاذب:

 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 ، بدلاً من الحلقات ، من المعتاد استخدام وظائف مخطط / تصغير. ولكن نظرًا لأن 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;…)]; 

لسوء الحظ ، لتخصيص جدول ، فأنت بحاجة إلى قائمة من الصفوف ، وليس الأعمدة ، وعليك تحويل المصفوفة (قائمة الأعمدة إلى قائمة من الصفوف) باستخدام وظيفة قلب. بالنسبة إلى جدول كبير ، هذا غير ضروري ، لذلك بدلاً من ذلك نطبق المهمة المعممة على كل عمود على حدة ، باستخدام وظيفة الخريطة (التي تبدو وكأنها علامة اقتباس أحادية):

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

نستخدم مرة أخرى وظيفة الإسقاط. لاحظ أيضًا أن إنشاء قائمة في Q هو أيضًا وظيفة ويمكننا تسميتها باستخدام دالة (خريطة) للحصول على قائمة بالقوائم.

حتى لا يتم إصلاح مجموعة الأعمدة المحتسبة ، قم بإنشاء التعبير أعلاه ديناميكيًا. أولاً ، نحدد وظائف حساب كل عمود ، وذلك باستخدام متغيرات الصف و inp للإشارة إلى البيانات المجمعة وإدخال البيانات:

 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 لديه وظيفة اختيار -؟ [قائمة منطقية ؛ list1 ؛ list2] - التي تحدد قيمة من القائمة 1 أو 2 حسب الشرط في الوسيطة الأولى:

 // high -> ?[isFirst;inp`high;row[`high]|inp`high] // @ -         @[`aggExpression;specialCols;{[x;y]"?[isFirst;inp`",y,";",x,"]"};string specialCols]; 

هنا دعوت مهمة عامة مع وظيفتي (التعبير في الأقواس المتعرجة). يتم تمرير القيمة الحالية (الوسيطة الأولى) ووسيطة إضافية ، والتي أقوم بتمريرها في المعلمة الرابعة ، إليها.

بشكل منفصل ، نضيف سماعات للبطارية ، لأن الوظيفة هي نفسها بالنسبة لهم:

 // 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 يكون ترتيب الحساب من اليمين إلى اليسار.

الآن لدينا وظيفتان رئيسيتان ضروريتان لإجراء العمليات الحسابية ، يبقى إضافة القليل من البنية التحتية والخدمة جاهزة.

الخطوات النهائية


لدينا وظائف preprocess و updateAgg التي تقوم بكل العمل. ولكن لا يزال من الضروري ضمان الانتقال الصحيح في دقائق وحساب المؤشرات للتجميع. أولاً نحدد وظيفة 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)]; } 

وأخيرًا ، وظيفة التحديث (الاسم التقليدي لهذه الوظيفة لخدمات Q) ، والتي يطلق عليها العميل ، لإضافة البيانات:

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

في وحدة تحكم أخرى ، ابدأ عملية Q الثانية واتصل بالأول:

 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:.zT; upd[`trade;x]; .zT-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 مرات فقط ، على ما يبدو عند تغيير الدقيقة. الزيادة إلى 100000:

 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 مليون سطر في الدقيقة) كبيرة للغاية ، وفي مثل هذه الحالات ، من المعتاد تشغيل العديد من الحيوانات المستنسخة (أو حتى العشرات من الحيوانات المستنسخة) من الخدمة ، كل منها يعالج جزءًا فقط من الأحرف. ومع ذلك ، فإن النتيجة مثيرة للإعجاب بالنسبة للغة المفسرة ، والتي تركز بشكل أساسي على تخزين البيانات.

قد ينشأ السؤال ، لماذا ينمو الوقت بشكل غير خطي مع حجم كل تحديث. السبب هو أن وظيفة الضغط هي في الواقع دالة C تعمل بكفاءة أكبر بكثير من updateAgg. بدءًا من حجم التحديث (حوالي 10.000) ، يصل updateAgg إلى السقف الخاص به ، ثم لا يتوقف وقت تنفيذه على حجم التحديث. يرجع السبب في الخطوة الأولية Q إلى أن الخدمة قادرة على هضم هذه الكميات من البيانات. هذا يؤكد على مدى أهميته عند العمل مع البيانات الضخمة لاختيار الخوارزمية المناسبة. نقطة أخرى هي التخزين الصحيح للبيانات في الذاكرة. إذا لم يتم تخزين البيانات في أعمدة أو لم يتم ترتيبها حسب الوقت ، فسنتعرف على شيء مثل ذاكرة التخزين المؤقت TLB - عدم وجود عنوان صفحة ذاكرة في ذاكرة التخزين المؤقت لعنوان المعالج. يستغرق العثور على العنوان حوالي 30 مرة أطول في حالة الفشل وفي حالة البيانات المتناثرة يمكن أن يبطئ الخدمة عدة مرات.

استنتاج


في هذه المقالة ، أظهرت أن قاعدة بيانات KDB + و Q مناسبة ليس فقط لتخزين البيانات الكبيرة وسهولة الوصول إليها عبر التحديد ، ولكن أيضًا لإنشاء خدمات معالجة البيانات التي يمكنها هضم مئات الملايين من صفوف / غيغا بايت من البيانات حتى في عملية Q واحدة . تتيح لغة Q نفسها تنفيذ الخوارزميات باختصار شديد وفعال فيما يتعلق بمعالجة البيانات نظرًا لطبيعتها المتجهية والمترجم المدمج لهجة SQL ومجموعة ناجحة جدًا من وظائف المكتبة.

سألاحظ أن ما ورد أعلاه هو فقط جزء من قدرات Q ، لديه ميزات فريدة أخرى. على سبيل المثال ، بروتوكول IPC بسيط للغاية يمحو الحدود بين عمليات Q المنفصلة ويسمح لك بدمج المئات من هذه العمليات في شبكة واحدة ، والتي يمكن أن توجد على عشرات الخوادم في أجزاء مختلفة من العالم.

Source: https://habr.com/ru/post/ar470596/


All Articles