Бодит цагийн үйлчилгээний жишээг ашиглан Q ба KDB+ хэлний онцлог

Та KDB+ суурь, Q програмчлалын хэл гэж юу болох, тэдгээрийн давуу болон сул талууд юу болохыг миний өмнөх нийтлэлээс уншиж болно. нийтлэл болон оршил хэсэгт товчхон. Өгүүлэлд бид Q дээр ирж буй өгөгдлийн урсгалыг боловсруулж, янз бүрийн нэгтгэх функцийг минут тутамд "бодит цагийн" горимд тооцоолох үйлчилгээг хэрэгжүүлэх болно (өөрөөр хэлбэл, өгөгдлийн дараагийн хэсгээс өмнө бүх зүйлийг тооцоолох цаг байх болно). Q хэлний гол онцлог нь дан объекттой биш тэдгээрийн массив, массив массив болон бусад нарийн төвөгтэй объектуудтай ажиллах боломжийг олгодог вектор хэл юм. Q болон түүний төрөл төрөгсөд болох K, J, APL гэх мэт хэлүүд нь товчхон байдгаараа алдартай. Ихэнхдээ Java гэх мэт танил хэл дээрх хэд хэдэн дэлгэцийн кодыг багтаасан програмыг хэдэн мөрөнд бичиж болно. Үүнийг би энэ нийтлэлд харуулахыг хүсч байна.

Бодит цагийн үйлчилгээний жишээг ашиглан Q ба KDB+ хэлний онцлог

Танилцуулга

KDB+ нь маш их хэмжээний өгөгдөлд төвлөрсөн, тодорхой аргаар (үндсэндээ цаг хугацаагаар) эрэмбэлэгдсэн багана хэлбэрийн мэдээллийн сан юм. Энэ нь үндсэндээ санхүүгийн байгууллагууд - банк, хөрөнгө оруулалтын сан, даатгалын компаниудад ашиглагддаг. Q хэл нь KDB+-ийн дотоод хэл бөгөөд энэ өгөгдөлтэй үр дүнтэй ажиллах боломжийг олгодог. Q үзэл баримтлал нь товч бөгөөд үр ашигтай, харин тодорхой байдлыг золиослодог. Энэ нь вектор хэлийг ямар ч тохиолдолд ойлгоход хэцүү байх бөгөөд бичлэгийн товч бөгөөд баялаг нь програмын илүү том хэсгийг нэг дэлгэц дээр үзэх боломжийг олгодог бөгөөд энэ нь эцэстээ ойлгоход хялбар болгодог.

Энэ нийтлэлд бид Q-д бүрэн хэмжээний програмыг хэрэгжүүлдэг бөгөөд та үүнийг туршиж үзэхийг хүсч магадгүй юм. Үүнийг хийхийн тулд танд бодит Q хэрэгтэй болно. Та kx компанийн вэбсайтаас 32 битийн үнэгүй хувилбарыг татаж авах боломжтой - www.kx.com. Тэнд, хэрэв та сонирхож байгаа бол Q, номын талаархи лавлах мэдээллийг олж авах болно Мөнх бус хүмүүст зориулсан Q мөн энэ сэдвээр янз бүрийн нийтлэлүүд.

Асуудлын тодорхойлолт

25 миллисекунд тутамд өгөгдөл бүхий хүснэгт илгээдэг эх сурвалж байдаг. KDB+ нь санхүүгийн салбарт голчлон ашиглагддаг тул бид үүнийг дараах баганатай гүйлгээний (арилжааны) хүснэгт гэж үзэх болно: цаг (миллисекунд дахь хугацаа), sym (хөрөнгийн бирж дээрх компанийн нэршил - IBM, AAPL,...), үнэ (хувьцааг худалдаж авсан үнэ), хэмжээ (гүйлгээний хэмжээ). 25 миллисекундын интервал нь дур зоргоороо, хэтэрхий жижиг биш, хэтэрхий урт биш юм. Түүний байгаа нь өгөгдөл нь аль хэдийн буферлэгдсэн үйлчилгээнд ирдэг гэсэн үг юм. Одоогийн ачааллаас хамааран динамик буферийг багтаасан үйлчилгээний тал дээр буферлэлтийг хэрэгжүүлэхэд хялбар байх боловч хялбар болгох үүднээс бид тогтмол интервал дээр анхаарлаа хандуулах болно.

Үйлчилгээ нь хамгийн дээд үнэ, дундаж үнэ, нийлбэрийн хэмжээ гэх мэтийг нэгтгэх функцуудын багцыг сим баганаас ирж буй тэмдэг бүрийг минут тутамд тоолох ёстой. хэрэгтэй мэдээлэл. Энгийн байхын тулд бид бүх функцийг аажмаар тооцоолох боломжтой гэж үзэх болно, i.e. шинэ утгыг олж авахын тулд хуучин болон ирж буй утгууд гэсэн хоёр тоог мэдэхэд хангалттай. Жишээлбэл, max, дундаж, sum функцууд ийм шинж чанартай байдаг бол медиан функцэд байхгүй.

Мөн бид ирж буй өгөгдлийн урсгалыг цаг хугацааны дараалалтай гэж үзэх болно. Энэ нь эцсийн мөчид л ажиллах боломжийг бидэнд олгоно. Практикт зарим шинэчлэлтүүд хожимдсон тохиолдолд одоогийн болон өмнөх минутуудтай ажиллахад хангалттай. Энгийн байхын тулд бид энэ хэргийг авч үзэхгүй.

Нэгтгэх функцууд

Шаардлагатай нэгтгэх функцуудыг доор жагсаав. Үйлчилгээний ачааллыг нэмэгдүүлэхийн тулд би аль болох олон зүйлийг авсан.

  • өндөр – дээд үнэ – минутын дээд үнэ.
  • бага – хамгийн бага үнэ – минутын хамгийн бага үнэ.
  • FirstPrice – анхны үнэ – минутын эхний үнэ.
  • lastPrice – сүүлийн үнэ – минутын сүүлийн үнэ.
  • firstSize – эхний хэмжээ – минутын эхний арилжааны хэмжээ.
  • lastSize - сүүлчийн хэмжээ - нэг минутын доторх сүүлийн арилжааны хэмжээ.
  • numTrades – count 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 объяснен ниже

Би толь бичигт хялбар болгох үүднээс сим, цаг нэмсэн, одоо initWith нь эцсийн нэгтгэсэн хүснэгтээс бэлэн мөр болж байгаа бөгөөд энэ нь зөв сим, цагийг тохируулах хэвээр байна. Та хүснэгтэд шинэ мөр нэмэхийн тулд үүнийг ашиглаж болно.

Бид нэгтгэх функцийг үүсгэх үед aggCols хэрэгтэй болно. Q дахь илэрхийллүүдийг (баруунаас зүүн тийш) үнэлэх дарааллаас шалтгаалан жагсаалтыг эргүүлэх ёстой. Зорилго нь зарим багана нь өмнөх баганауудаас хамаардаг тул тооцоолол өндөрөөс cumVolume руу шилжихийг баталгаажуулах явдал юм.

Өмнөх баганаас шинэ минут руу хуулах шаардлагатай багануудыг тав тухтай байлгах үүднээс сим баганыг нэмсэн:

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

Ойлгомжтой болгохын тулд би Q илэрхийлэл бүхий мөрийг үнэлэх функц руу дамжуулж болох, функц сонгоход шаардлагатай утга болгон хувиргадаг parse функцийг ашигласан. Мөн урьдчилсан процесс нь сонгох функцийн төсөөлөл (өөрөөр хэлбэл хэсэгчлэн тодорхойлогдсон аргументтай функц) гэж тодорхойлогддог бөгөөд нэг аргумент (хүснэгт) дутуу байгааг анхаарна уу. Хэрэв бид хүснэгтэд урьдчилсан боловсруулалт хийвэл бид шахсан ширээ авах болно.

Хоёр дахь шат нь нэгтгэсэн хүснэгтийг шинэчлэх явдал юм. Эхлээд алгоритмаа псевдокодоор бичье:

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-д гогцооны оронд map/reduce функцийг ашиглах нь түгээмэл байдаг. Гэхдээ 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-д жагсаалт үүсгэх нь мөн функц бөгөөд бид үүнийг every(map) функцээр дуудаж, жагсаалтын жагсаалтыг авах боломжтой гэдгийг анхаарна уу.

Тооцоолсон баганын багцыг тогтворгүй байлгахын тулд бид дээрх илэрхийллийг динамикаар үүсгэх болно. Эхлээд нэгтгэсэн болон оролтын өгөгдөлд хандахын тулд мөр болон 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 нь сонгох функцтэй - ?[Боолийн жагсаалт;жагсаалт1;жагсаалт2] - эхний аргумент дахь нөхцлөөс хамааран жагсаалт 1 эсвэл 2-оос утгыг сонгоно:

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

Энд би өөрийн функцтэй ерөнхий даалгавар (буржгар хаалтанд хийсэн илэрхийлэл) гэж нэрлэв. Энэ нь одоогийн утга (эхний аргумент) болон нэмэлт аргументыг хүлээн авдаг бөгөөд би үүнийг 4-р параметрт дамжуулдаг.

Батерейны чанга яригчийг тусад нь нэмье, учир нь функц нь тэдний хувьд ижил байна.

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

Эцэст нь, үйлчлүүлэгчээс өгөгдөл нэмэхийн тулд дууддаг upd функц (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:.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 сая мөр) маш том бөгөөд ийм тохиолдолд үйлчилгээний хэд хэдэн клон (эсвэл бүр хэдэн арван клон) ажиллуулах нь элбэг байдаг бөгөөд тус бүр нь тэмдэгтүүдийн зөвхөн нэг хэсгийг боловсруулдаг. Гэсэн хэдий ч үр дүн нь өгөгдөл хадгалахад голчлон анхаардаг орчуулгын хэлний хувьд гайхалтай юм.

Шинэчлэлт бүрийн хэмжээгээр цаг хугацаа яагаад шугаман бус өсдөг вэ гэсэн асуулт гарч ирж магадгүй юм. Шалтгаан нь shrink функц нь үнэндээ C функц бөгөөд updateAgg-ээс хамаагүй илүү үр дүнтэй байдаг. Тодорхой шинэчлэлтийн хэмжээнээс (ойролцоогоор 10.000) эхлэн updateAgg дээд хязгаартаа хүрч, дараа нь гүйцэтгэлийн хугацаа нь шинэчлэлтийн хэмжээнээс хамаардаггүй. Үйлчилгээ нь ийм хэмжээний өгөгдлийг шингээх чадвартай болсон нь Q-ийн урьдчилсан алхамын ачаар юм. Энэ нь том өгөгдөлтэй ажиллахдаа зөв алгоритмыг сонгох нь хэр чухал болохыг онцолж байна. Өөр нэг зүйл бол санах ойд өгөгдлийг зөв хадгалах явдал юм. Хэрэв өгөгдөл багана хэлбэрээр хадгалагдаагүй эсвэл цаг хугацааны хувьд захиалга өгөөгүй бол бид TLB кэш алдаа гэх мэт зүйлийг мэддэг болсон - процессорын хаягийн кэш дэх санах ойн хуудасны хаяг байхгүй. Хаяг хайх нь амжилтгүй болвол ойролцоогоор 30 дахин урт хугацаа шаардагдах бөгөөд хэрэв өгөгдөл тараагдсан бол энэ нь үйлчилгээг хэд хэдэн удаа удаашруулж болзошгүй юм.

дүгнэлт

Энэ нийтлэлд би KDB+ болон Q мэдээллийн сан нь том хэмжээний өгөгдлийг хадгалах, сонгох замаар хялбархан хандахад тохиромжтой төдийгүй, хэдэн зуун сая мөр/гигабайт өгөгдлийг шингээх чадвартай өгөгдөл боловсруулах үйлчилгээг бий болгоход тохиромжтой гэдгийг харуулсан. нэг Q процесс. Q хэл нь вектор шинж чанар, суурилуулсан SQL аялгуу орчуулагч, номын сангийн функцүүдийн маш амжилттай багц зэргээс шалтгаалан өгөгдөл боловсруулахтай холбоотой алгоритмуудыг маш товч бөгөөд үр дүнтэй хэрэгжүүлэх боломжийг олгодог.

Дээрх нь Q-ийн хийж чадах зүйлийн зөвхөн нэг хэсэг бөгөөд бусад өвөрмөц онцлогтой гэдгийг би тэмдэглэх болно. Жишээлбэл, Q процессуудын хоорондын хил хязгаарыг арилгадаг маш энгийн IPC протокол бөгөөд эдгээр хэдэн зуун процессыг дэлхийн өнцөг булан бүрт байрлах олон арван сервер дээр байрлах нэг сүлжээнд нэгтгэх боломжийг олгодог.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх