Q- ja KDB+-kielen ominaisuudet reaaliaikaisen palvelun esimerkin avulla

Mitä ovat KDB+-kanta, Q-ohjelmointikieli, mitkä ovat niiden vahvuudet ja heikkoudet, voit lukea edellisestäni. статье ja lyhyesti johdannossa. Artikkelissa toteutamme Q:lle palvelun, joka käsittelee saapuvan tietovirran ja laskee erilaisia ​​aggregointifunktioita minuutin välein "reaaliaikaisessa" tilassa (eli sillä on aikaa laskea kaikki ennen seuraavaa dataosaa). Q:n pääominaisuus on, että se on vektorikieli, jonka avulla voit käyttää ei yksittäisiä objekteja, vaan niiden taulukoita, taulukoiden taulukoita ja muita monimutkaisia ​​objekteja. Kielet, kuten Q ja sen sukulaiset K, J, APL, ovat kuuluisia lyhyydestään. Usein ohjelma, joka vie useita koodinäyttöjä tutulla kielellä, kuten Java, voidaan kirjoittaa niille muutamalla rivillä. Tämän haluan osoittaa tässä artikkelissa.

Q- ja KDB+-kielen ominaisuudet reaaliaikaisen palvelun esimerkin avulla

Esittely

KDB+ on saraketietokanta, joka keskittyy erittäin suuriin tietomääriin, jotka on järjestetty tietyllä tavalla (pääasiassa ajan mukaan). Sitä käytetään ensisijaisesti rahoituslaitoksissa - pankeissa, sijoitusrahastoissa, vakuutusyhtiöissä. Q-kieli on KDB+:n sisäinen kieli, jonka avulla voit käsitellä näitä tietoja tehokkaasti. Q-ideologia on lyhyys ja tehokkuus, samalla kun selkeys uhrataan. Tämä on perusteltua sillä, että vektorikieli on joka tapauksessa vaikea ymmärtää, ja tallennuksen lyhyys ja rikkaus mahdollistavat paljon suuremman osan ohjelman näkemisen yhdellä näytöllä, mikä lopulta helpottaa ymmärtämistä.

Tässä artikkelissa toteutamme täyden ohjelman Q:ssa ja saatat haluta kokeilla sitä. Tätä varten tarvitset varsinaisen Q:n. Voit ladata ilmaisen 32-bittisen version kx-yrityksen verkkosivustolta – www.kx.com. Sieltä, jos olet kiinnostunut, löydät viitetietoja kirjasta Q Q Kuolevaisille ja erilaisia ​​artikkeleita tästä aiheesta.

Ongelma

On lähde, joka lähettää tiedot sisältävän taulukon 25 millisekunnin välein. Koska KDB+:a käytetään ensisijaisesti rahoituksessa, oletetaan, että tämä on transaktioiden (kauppojen) taulukko, jossa on seuraavat sarakkeet: aika (aika millisekunteina), sym (yrityksen nimi pörssissä - IBM, AAPL,…), hinta (hinta, jolla osakkeet ostettiin), koko (kaupan koko). 25 millisekunnin aikaväli on mielivaltainen, ei liian pieni eikä liian pitkä. Sen läsnäolo tarkoittaa, että tiedot tulevat palveluun jo puskuroituina. Palvelupuolella olisi helppo toteuttaa puskurointi, mukaan lukien dynaaminen puskurointi nykyisen kuormituksen mukaan, mutta yksinkertaisuuden vuoksi keskitymme kiinteään aikaväliin.

Palvelun on laskettava joka minuutti jokaista sym-sarakkeesta tulevaa symbolia kohden joukko aggregointifunktioita - maksimihinta, keskihinta, summan koko jne. hyödyllistä tietoa. Yksinkertaisuuden vuoksi oletetaan, että kaikki funktiot voidaan laskea inkrementaalisesti, ts. uuden arvon saamiseksi riittää tietää kaksi numeroa - vanha ja saapuvat arvot. Esimerkiksi funktioilla max, keskiarvo, summa on tämä ominaisuus, mutta mediaanifunktiolla ei.

Oletetaan myös, että saapuva tietovirta on aikajärjestetty. Tämä antaa meille mahdollisuuden työskennellä vain viime hetkellä. Käytännössä riittää, että pystyt työskentelemään nykyisten ja edellisten minuuttien kanssa, jos jotkin päivitykset myöhästyvät. Yksinkertaisuuden vuoksi emme käsittele tätä tapausta.

Aggregointifunktiot

Vaaditut aggregointifunktiot on lueteltu alla. Otin niistä mahdollisimman monta lisätäkseni palvelun kuormitusta:

  • korkea – maksimihinta – maksimihinta minuutilta.
  • alhainen – minimihinta – minimihinta minuutilta.
  • firstPrice – ensimmäinen hinta – ensimmäinen minuuttihinta.
  • lastPrice – viimeinen hinta – viimeinen minuuttihinta.
  • firstSize – ensimmäinen koko – ensimmäinen kauppakoko minuutissa.
  • lastSize – viimeinen koko – viimeinen kaupan koko minuutissa.
  • numTrades – count i – kauppojen määrä minuutissa.
  • volyymi – summakoko – kauppakokojen summa minuutissa.
  • pvolume – summahinta – hintojen summa minuutissa, vaaditaan avgPrice-arvoon.
  • – kokonaisliikevaihtohinta*koko – tapahtumien kokonaismäärä minuutissa.
  • avgPrice – pvolume%numTrades – keskimääräinen minuuttihinta.
  • avgSize – volyymi%numTrades – keskimääräinen kaupan koko minuutissa.
  • vwap – liikevaihto%volume – keskimääräinen minuuttihinta painotettuna tapahtuman koolla.
  • cumVolume – summavolyymi – tapahtumien kertynyt koko koko ajan.

Keskustellaan heti yhdestä ei-ilmeisestä kohdasta - kuinka nämä sarakkeet alustetaan ensimmäistä kertaa ja jokaista seuraavaa minuuttia varten. Jotkin firstPrice-tyypin sarakkeet on alustettava tyhjäksi joka kerta; niiden arvoa ei ole määritelty. Muut volyymityypit on aina asetettava arvoon 0. On myös sarakkeita, jotka vaativat yhdistettyä lähestymistapaa - esimerkiksi cumVolume on kopioitava edellisestä minuutista ja ensimmäiselle asetetaan 0. Asetetaan kaikki nämä parametrit sanakirjan tietojen avulla tyyppi (analogisesti tietueelle):

// 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 объяснен ниже

Lisäsin sanakirjaan sym:n ja ajan mukavuuden vuoksi, nyt initWith on valmis rivi lopullisesta aggregoidusta taulukosta, jossa on vielä asetettava oikea sym ja aika. Voit käyttää sitä uusien rivien lisäämiseen taulukkoon.

Tarvitsemme aggCols-funktioita luodessasi koontifunktiota. Lista on käännettävä Q:n lausekkeiden laskentajärjestyksen vuoksi (oikealta vasemmalle). Tavoitteena on varmistaa, että laskenta menee korkeasta cumVolumeen, koska jotkut sarakkeet ovat riippuvaisia ​​aiemmista.

Sarakkeet, jotka on kopioitava uuteen minuuttiin edellisestä, sym-sarake lisätään mukavuuden vuoksi:

rollColumns:`sym`cumVolume;

Jaetaan nyt sarakkeet ryhmiin sen mukaan, kuinka ne pitäisi päivittää. Kolme tyyppiä voidaan erottaa:

  1. Akut (volyymi, liikevaihto,...) – meidän on lisättävä saapuva arvo edelliseen.
  2. Erikoispisteellä (korkea, matala, ..) – minuutin ensimmäinen arvo otetaan saapuvista tiedoista, loput lasketaan funktion avulla.
  3. Levätä. Lasketaan aina funktiolla.

Määritellään muuttujat näille luokille:

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

Laskujärjestys

Päivitämme koostetaulukon kahdessa vaiheessa. Tehokkuuden vuoksi kutistamme ensin saapuvan taulukon siten, että jokaista merkkiä ja minuuttia kohti on vain yksi rivi. Se, että kaikki toimintomme ovat inkrementaalisia ja assosiatiivisia, takaa, että tämän lisävaiheen tulos ei muutu. Voit pienentää taulukkoa valitsemalla:

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

Tällä menetelmällä on haitta - laskettujen sarakkeiden joukko on ennalta määritetty. Onneksi Q:ssa select on toteutettu myös funktiona, jolla voit korvata dynaamisesti luotuja argumentteja:

?[table;whereClause;byClause;selectClause]

En kuvaile yksityiskohtaisesti argumenttien muotoa; meidän tapauksessamme vain by ja select -lausekkeet ovat ei-triviaaleja ja niiden tulisi olla lomakesarakkeiden!-lausekkeiden sanakirjoja. Näin ollen kutistusfunktio voidaan määritellä seuraavasti:

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

Selvyyden vuoksi käytin parse-funktiota, joka muuttaa Q-lausekkeella varustetun merkkijonon arvoksi, joka voidaan välittää eval-funktiolle ja joka vaaditaan funktion selectissä. Huomaa myös, että esikäsittely on määritelty valintafunktion projektioksi (eli funktioksi, jossa on osittain määritellyt argumentit), yksi argumentti (taulukko) puuttuu. Jos käytämme esikäsittelyä taulukkoon, saamme pakatun taulukon.

Toinen vaihe on aggregoidun taulukon päivittäminen. Kirjoitetaan ensin algoritmi pseudokoodilla:

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:ssa on yleistä käyttää map/reduce-funktioita silmukoiden sijasta. Mutta koska Q on vektorikieli ja voimme helposti soveltaa kaikkia operaatioita kaikkiin symboleihin kerralla, niin ensimmäisessä approksimaatiossa voimme tehdä ilman silmukkaa suorittamalla operaatioita kaikille symboleille kerralla:

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

Mutta voimme mennä pidemmälle, Q:lla on ainutlaatuinen ja erittäin tehokas operaattori - yleisten tehtävien operaattori. Sen avulla voit muuttaa arvojoukkoa monimutkaisessa tietorakenteessa indeksien, funktioiden ja argumenttien luettelon avulla. Meidän tapauksessamme se näyttää tältä:

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

Valitettavasti taulukon määrittämiseksi tarvitset luettelon riveistä, ei sarakkeista, ja sinun on transponoitava matriisi (sarakeluettelo riviluetteloksi) kääntötoiminnolla. Tämä on kallista suurelle taulukolle, joten sen sijaan käytämme yleistettyä tehtävää jokaiseen sarakkeeseen erikseen käyttämällä karttatoimintoa (joka näyttää heittomerkiltä):

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

Käytämme jälleen funktioprojektiota. Huomaa myös, että Q:ssa luettelon luominen on myös funktio ja voimme kutsua sitä käyttämällä every(map)-funktiota saadaksemme luettelon luetteloista.

Varmistaaksemme, että laskettujen sarakkeiden joukko ei ole kiinteä, luomme yllä olevan lausekkeen dynaamisesti. Määritetään ensin funktiot kunkin sarakkeen laskemiseksi käyttämällä rivi- ja inp-muuttujia viittaamaan aggregoituun ja syöttötietoon:

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");

Jotkut sarakkeet ovat erityisiä; funktion ei pitäisi laskea niiden ensimmäistä arvoa. Voimme määrittää, että se on ensimmäinen rivi [`numTrades] sarakkeesta - jos se sisältää 0, arvo on ensimmäinen. Q:ssa on valintafunktio - ?[Boolean list;list1;list2] - joka valitsee arvon listasta 1 tai 2 riippuen ensimmäisen argumentin ehdosta:

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

Tässä kutsuin yleistettyä tehtävää funktiollani (lauseke kiharoissa aaltosulkeissa). Se vastaanottaa nykyisen arvon (ensimmäisen argumentin) ja lisäargumentin, jonka välitän 4. parametrissa.

Lisätään paristokaiuttimet erikseen, koska toiminto on niillä sama:

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

Tämä on normaali Q-standardien määritys, mutta määritän arvoluettelon kerralla. Luodaan lopuksi pääfunktio:

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

Tällä lausekkeella luon dynaamisesti funktion merkkijonosta, joka sisältää yllä antamani lausekkeen. Tulos näyttää tältä:

{[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])]}

Sarakkeen arviointijärjestys on käänteinen, koska Q:ssa arviointijärjestys on oikealta vasemmalle.

Nyt meillä on kaksi päätoimintoa, joita tarvitaan laskelmiin, tarvitsemme vain lisätä vähän infrastruktuuria ja palvelu on valmis.

Viimeiset vaiheet

Meillä on esikäsittely- ja updateAgg-toiminnot, jotka tekevät kaiken työn. Mutta silti on tarpeen varmistaa oikea siirtyminen minuuttien läpi ja laskea indeksit aggregointia varten. Ensinnäkin määritellään init-funktio:

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
 }

Määrittelemme myös rullafunktion, joka muuttaa nykyisen minuutin:

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

Tarvitsemme funktion uusien merkkien lisäämiseen:

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

Ja lopuksi upd-funktio (tämän funktion perinteinen nimi Q-palveluille), jota asiakas kutsuu lisäämään tietoja:

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]; // обновим агрегированную таблицу. Функция ? ищет индекс элементов списка справа в списке слева.
 };

Siinä kaikki. Tässä on palvelumme täydellinen koodi, kuten luvattiin, vain muutaman rivin verran:

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

Testaus

Tarkastellaan palvelun toimivuutta. Tätä varten suoritetaan se erillisessä prosessissa (laita koodi service.q-tiedostoon) ja kutsutaan init-funktio:

q service.q –p 5566

q)init[]

Käynnistä toinen Q-prosessi toisessa konsolissa ja muodosta yhteys ensimmäiseen:

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

Ensin luodaan symbolien luettelo - 10000 XNUMX kappaletta ja lisätään funktio satunnaisen taulukon luomiseksi. Toisessa konsolissa:

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

Lisäsin luetteloon kolme oikeaa symbolia, jotta ne olisi helpompi etsiä taulukosta. Rnd-funktio luo satunnaisen taulukon, jossa on n riviä, jossa aika vaihtelee t:stä ​​t+25 millisekuntiin.

Nyt voit yrittää lähettää dataa palveluun (lisää ensimmäiset kymmenen tuntia):

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

Voit tarkistaa palvelusta, että taulukko on päivitetty:

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

Suoritetaan nyt kuormitustestaus selvittääksemme kuinka paljon dataa palvelu pystyy käsittelemään minuutissa. Haluan muistuttaa, että asetimme päivitysväliksi 25 millisekuntia. Näin ollen palvelun täytyy (keskimäärin) mahtua vähintään 20 millisekuntiin päivitystä kohti, jotta käyttäjille jää aikaa pyytää tietoja. Kirjoita seuraava toisessa prosessissa:

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 on kaksi minuuttia. Voit yrittää suorittaa ensin 1000 riviä 25 millisekunnin välein:

start 1000

Minun tapauksessani tulos on noin pari millisekuntia päivitystä kohti. Joten lisään rivien määrän välittömästi 10.000 XNUMX:een:

start 10000

Результат:

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

Jälleen, ei mitään erikoista, mutta tämä on 24 miljoonaa riviä minuutissa, 400 tuhatta sekunnissa. Yli 25 millisekunnin ajan päivitys hidastui vain 5 kertaa, ilmeisesti minuutin vaihtuessa. Kasvataan 100.000 XNUMX:een:

start 100000

Результат:

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

Kuten näette, palvelu tuskin selviää, mutta siitä huolimatta se onnistuu pysymään pinnalla. Tällainen datamäärä (240 miljoonaa riviä minuutissa) on äärimmäisen suuri, tällaisissa tapauksissa on yleistä käynnistää useita palvelun klooneja (tai jopa kymmeniä klooneja), joista jokainen käsittelee vain osan hahmoista. Tulos on kuitenkin vaikuttava tulkitulla kielellä, joka keskittyy ensisijaisesti tietojen tallentamiseen.

Voi herää kysymys, miksi aika kasvaa epälineaarisesti jokaisen päivityksen koon myötä. Syynä on se, että kutistusfunktio on itse asiassa C-funktio, joka on paljon tehokkaampi kuin updateAgg. Tietystä päivityskoosta (noin 10.000 30) alkaen updateAgg saavuttaa enimmäismääränsä, jolloin sen suoritusaika ei riipu päivityksen koosta. Esivaiheen Q ansiosta palvelu pystyy sulattamaan tällaisia ​​tietomääriä. Tämä korostaa, kuinka tärkeää on valita oikea algoritmi, kun työskentelet big datan kanssa. Toinen asia on tietojen oikea tallennus muistiin. Jos dataa ei tallennettaisi sarakemaisesti tai niitä ei järjestettäisi ajan mukaan, tulisimme tutuksi sellaiseen asiaan kuin TLB-välimuistin puuttuminen - muistisivuosoitteen puuttuminen prosessorin osoitevälimuistista. Osoitteen etsiminen kestää noin XNUMX kertaa kauemmin, jos se epäonnistuu, ja jos tiedot ovat hajallaan, se voi hidastaa palvelua useita kertoja.

Johtopäätös

Tässä artikkelissa osoitin, että KDB+- ja Q-tietokannat soveltuvat paitsi suuren datan tallentamiseen ja helpottamiseen valikon kautta, myös sellaisten tietojenkäsittelypalveluiden luomiseen, jotka pystyvät sulattamaan satoja miljoonia rivejä/gigatavuja dataa jopa yksi ainoa Q-prosessi. Q-kieli itsessään mahdollistaa tietojenkäsittelyyn liittyvien algoritmien erittäin tiiviin ja tehokkaan toteutuksen vektoriluonteensa, sisäänrakennetun SQL-murretulkin ja erittäin onnistuneen kirjastotoimintosarjansa ansiosta.

Huomaan, että yllä oleva on vain osa Q:n osaamista, sillä siinä on myös muita ainutlaatuisia ominaisuuksia. Esimerkiksi erittäin yksinkertainen IPC-protokolla, joka poistaa rajan yksittäisten Q-prosessien välillä ja mahdollistaa satojen näiden prosessien yhdistämisen yhdeksi verkkoksi, joka voi sijaita kymmenillä palvelimilla eri puolilla maailmaa.

Lähde: will.com

Lisää kommentti