Značajke jezika Q i KDB+ na primjeru usluge u stvarnom vremenu

O tome što su baza KDB+, programski jezik Q, koje su njihove prednosti i slabosti možete pročitati u mojim prethodnim članak i ukratko u uvodu. U članku ćemo implementirati uslugu na Q koja će obraditi dolazni tok podataka i izračunati razne funkcije agregacije svake minute u načinu rada "u stvarnom vremenu" (tj. imat će vremena sve izračunati prije sljedećeg dijela podataka). Glavna značajka Q-a je da je to vektorski jezik koji vam omogućuje da ne radite s pojedinačnim objektima, već s njihovim nizovima, nizovima nizova i drugim složenim objektima. Jezici kao što su Q i njegovi srodnici K, J, APL poznati su po svojoj kratkoći. Često se program koji zauzima nekoliko ekrana koda u poznatom jeziku poput Jave može napisati u nekoliko redaka. To je ono što želim pokazati u ovom članku.

Značajke jezika Q i KDB+ na primjeru usluge u stvarnom vremenu

Uvod

KDB+ je stupčasta baza podataka usmjerena na vrlo velike količine podataka, poredane na specifičan način (prvenstveno prema vremenu). Koristi se prvenstveno u financijskim institucijama – bankama, investicijskim fondovima, osiguravajućim društvima. Q jezik je interni jezik KDB+ koji vam omogućuje učinkovit rad s ovim podacima. Ideologija Q je kratkoća i učinkovitost, dok je jasnoća žrtvovana. To se opravdava činjenicom da će vektorski jezik u svakom slučaju biti teško razumljiv, a kratkoća i bogatstvo zapisa omogućuje da se puno veći dio programa vidi na jednom ekranu, što u konačnici olakšava razumijevanje.

U ovom članku implementiramo potpuni program u Q-u i možda biste ga željeli isprobati. Da biste to učinili, trebat će vam stvarni Q. Možete preuzeti besplatnu 32-bitnu verziju na web stranici kx tvrtke – www.kx.com. Tamo ćete, ako ste zainteresirani, pronaći referentne informacije o knjizi Q Q Za smrtnike i razne članke na ovu temu.

Formuliranje problema

Postoji izvor koji svakih 25 milisekundi šalje tablicu s podacima. Budući da se KDB+ koristi prvenstveno u financijama, pretpostavit ćemo da se radi o tablici transakcija (trgovina), koja ima sljedeće stupce: vrijeme (vrijeme u milisekundama), sym (oznaka tvrtke na burzi - IBM, AAPL,…), cijena (cijena po kojoj su dionice kupljene), veličina (veličina transakcije). Interval od 25 milisekundi je proizvoljan, nije premalen niti predug. Njegova prisutnost znači da podaci dolaze u uslugu već u međuspremniku. Bilo bi lako implementirati međuspremnik na strani usluge, uključujući dinamički međuspremnik ovisno o trenutnom opterećenju, ali radi jednostavnosti, usredotočit ćemo se na fiksni interval.

Usluga mora brojati svaku minutu za svaki dolazni simbol iz stupca sym skup funkcija zbrajanja - maksimalna cijena, prosječna cijena, veličina zbroja itd. korisna informacija. Radi jednostavnosti, pretpostavit ćemo da se sve funkcije mogu izračunati inkrementalno, tj. da biste dobili novu vrijednost, dovoljno je znati dva broja - staru i ulaznu vrijednost. Na primjer, funkcije max, prosjek, zbroj imaju ovo svojstvo, ali funkcija medijana nema.

Također ćemo pretpostaviti da je dolazni tok podataka vremenski uređen. To će nam dati priliku da radimo samo u posljednjoj minuti. U praksi je dovoljno moći raditi s trenutnim i prethodnim minutama u slučaju da neka ažuriranja kasne. Radi jednostavnosti, nećemo razmatrati ovaj slučaj.

Funkcije agregacije

Potrebne funkcije združivanja navedene su u nastavku. Uzeo sam ih što je više moguće kako bih povećao opterećenje usluge:

  • high – max price – maksimalna cijena po minuti.
  • low – min price – minimalna cijena minute.
  • firstPrice – prva cijena – prva cijena po minuti.
  • lastPrice – zadnja cijena – zadnja cijena minute.
  • firstSize – prva veličina – prva veličina trgovine po minuti.
  • lastSize – zadnja veličina — zadnja veličina trgovine u minuti.
  • numTrades – count i – broj trgovina u minuti.
  • volume – sum size – zbroj trgovinskih veličina po minuti.
  • pvolume – sum price – zbroj cijena po minuti, potreban za avgPrice.
  • – zbroj prometa cijena*veličina – ukupni volumen transakcija po minuti.
  • avgPrice – pvolume%numTrades – prosječna cijena po minuti.
  • avgSize – volume%numTrades – prosječna veličina trgovine po minuti.
  • vwap – promet% volumen – prosječna cijena po minuti ponderirana prema veličini transakcije.
  • cumVolume – ukupni volumen – akumulirana veličina transakcija tijekom cijelog vremena.

Raspravimo odmah jednu neočitu točku - kako inicijalizirati ove stupce prvi put i za svaku sljedeću minutu. Neki stupci tipa firstPrice moraju se inicijalizirati na null svaki put; njihova je vrijednost nedefinirana. Ostale vrste glasnoće uvijek moraju biti postavljene na 0. Postoje i stupci koji zahtijevaju kombinirani pristup - na primjer, cumVolume se mora kopirati iz prethodne minute, a za prvu postaviti na 0. Postavimo sve te parametre pomoću podataka iz rječnika vrsta (analogno zapisu):

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

Dodao sam sym i vrijeme u rječnik radi praktičnosti, sada je initWith gotova linija iz konačne agregirane tablice, gdje ostaje postaviti točan sym i vrijeme. Možete ga koristiti za dodavanje novih redaka u tablicu.

Trebat će nam aggCols kada stvaramo funkciju agregacije. Popis mora biti obrnut zbog redoslijeda kojim se procjenjuju izrazi u Q (s desna na lijevo). Cilj je osigurati da izračun ide od visokog do cumVolume, jer neki stupci ovise o prethodnima.

Stupci koje je potrebno kopirati u novu minutu iz prethodne, stupac sym je dodan radi praktičnosti:

rollColumns:`sym`cumVolume;

Podijelimo sada stupce u grupe prema tome kako bi se trebali ažurirati. Mogu se razlikovati tri vrste:

  1. Akumulatori (volumen, promet,..) – ulaznu vrijednost moramo dodati prethodnoj.
  2. Uz posebnu točku (visoka, niska, ..) – prva vrijednost u minuti uzima se iz ulaznih podataka, ostale se izračunavaju pomoću funkcije.
  3. Odmor. Uvijek se izračunava pomoću funkcije.

Definirajmo varijable za ove klase:

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

Redoslijed izračuna

Zbirnu tablicu ažurirat ćemo u dvije faze. Radi učinkovitosti, prvo smanjujemo dolaznu tablicu tako da postoji samo jedan redak za svaki znak i minutu. Činjenica da su sve naše funkcije inkrementalne i asocijativne jamči da se rezultat ovog dodatnog koraka neće promijeniti. Možete smanjiti tablicu pomoću odabira:

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

Ova metoda ima nedostatak - skup izračunatih stupaca je unaprijed definiran. Srećom, u Q-u, select je također implementiran kao funkcija u kojoj možete zamijeniti dinamički stvorene argumente:

?[table;whereClause;byClause;selectClause]

Neću detaljno opisivati ​​format argumenata; u našem slučaju izrazi samo by i select bit će netrivijalni i trebali bi biti rječnici oblika stupci!izrazi. Stoga se funkcija skupljanja može definirati na sljedeći način:

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

Radi jasnoće, upotrijebio sam funkciju parse koja pretvara niz s Q izrazom u vrijednost koja se može proslijediti funkciji eval i koja je potrebna u funkciji select. Također imajte na umu da je pretproces definiran kao projekcija (tj. funkcija s djelomično definiranim argumentima) funkcije odabira, jedan argument (tablica) nedostaje. Ako na tablicu primijenimo pretproces, dobit ćemo komprimiranu tablicu.

Druga faza je ažuriranje zbirne tablice. Prvo napišimo algoritam u pseudokodu:

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

U Q-u je uobičajeno koristiti funkcije mapiranja/smanjivanja umjesto petlji. Ali budući da je Q vektorski jezik i možemo lako primijeniti sve operacije na sve simbole odjednom, tada u prvoj aproksimaciji možemo uopće bez petlje, izvodeći operacije na svim simbolima odjednom:

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

Ali možemo ići dalje, Q ima jedinstven i izuzetno moćan operator - generalizirani operator dodjele. Omogućuje vam promjenu skupa vrijednosti u složenoj strukturi podataka pomoću popisa indeksa, funkcija i argumenata. U našem slučaju to izgleda ovako:

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

Nažalost, za dodjelu tablici potreban vam je popis redaka, a ne stupaca, i morate transponirati matricu (popis stupaca u popis redaka) pomoću funkcije flip. Ovo je skupo za veliku tablicu, pa umjesto toga primjenjujemo generaliziranu dodjelu svakom stupcu zasebno, koristeći funkciju karte (koja izgleda kao apostrof):

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

Opet koristimo projekciju funkcije. Također imajte na umu da je u Q-u stvaranje popisa također funkcija i možemo je pozvati pomoću funkcije each(map) da bismo dobili popis popisa.

Kako bismo osigurali da skup izračunatih stupaca nije fiksan, izradit ćemo gornji izraz dinamički. Najprije definirajmo funkcije za izračun svakog stupca, koristeći varijable row i inp za upućivanje na agregirane i ulazne podatke:

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

Neki stupci su posebni; funkcija ne bi trebala izračunati njihovu prvu vrijednost. Možemo utvrditi da je prvi po stupcu row[`numTrades] - ako sadrži 0, onda je vrijednost prva. Q ima funkciju odabira - ?[Boolean list;list1;list2] - koja odabire vrijednost s popisa 1 ili 2 ovisno o uvjetu u prvom argumentu:

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

Ovdje sam pozvao generaliziranu dodjelu sa svojom funkcijom (izraz u vitičastim zagradama). Prima trenutnu vrijednost (prvi argument) i dodatni argument, koji prosljeđujem u 4. parametru.

Baterijske zvučnike dodajmo zasebno jer im je funkcija ista:

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

Ovo je normalno dodjeljivanje prema Q standardima, ali ja dodjeljujem popis vrijednosti odjednom. Na kraju, kreirajmo glavnu funkciju:

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

Ovim izrazom dinamički stvaram funkciju iz niza koji sadrži izraz koji sam dao gore. Rezultat će izgledati ovako:

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

Redoslijed vrednovanja stupca je obrnut jer je u Q redoslijed vrednovanja s desna na lijevo.

Sada imamo dvije glavne funkcije potrebne za izračune, samo trebamo dodati malo infrastrukture i usluga je spremna.

Završni koraci

Imamo funkcije pretprocesa i updateAgg koje rade sav posao. Ali i dalje je potrebno osigurati točan prijelaz kroz minute i izračunati indekse za agregaciju. Prije svega, definirajmo init funkciju:

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
 }

Također ćemo definirati funkciju roll koja će promijeniti trenutnu minutu:

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

Trebat će nam funkcija za dodavanje novih znakova:

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

I na kraju, funkcija upd (tradicionalni naziv za ovu funkciju za Q usluge), koju poziva klijent za dodavanje podataka:

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

To je sve. Evo kompletnog koda naše usluge, kao što smo obećali, samo nekoliko redaka:

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

Testiranje

Provjerimo izvedbu usluge. Da biste to učinili, pokrenite ga u zasebnom procesu (stavite kod u datoteku service.q) i pozovite init funkciju:

q service.q –p 5566

q)init[]

U drugoj konzoli pokrenite drugi Q proces i spojite se na prvi:

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

Prvo, napravimo popis simbola - 10000 XNUMX komada i dodamo funkciju za izradu slučajne tablice. U drugoj konzoli:

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

Na popis sam dodao tri stvarna simbola kako bih ih lakše tražio u tablici. Funkcija rnd stvara slučajnu tablicu s n redaka, gdje vrijeme varira od t do t+25 milisekundi.

Sada možete pokušati poslati podatke usluzi (dodajte prvih deset sati):

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

U servisu možete provjeriti je li tablica ažurirana:

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

Rezultat:

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

Provedimo sada testiranje opterećenja kako bismo saznali koliko podataka usluga može obraditi u minuti. Dopustite da vas podsjetim da smo interval ažuriranja postavili na 25 milisekundi. Sukladno tome, usluga mora (u prosjeku) stati u najmanje 20 milisekundi po ažuriranju kako bi korisnici imali vremena za traženje podataka. U drugom procesu unesite sljedeće:

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 je dvije minute. Možete prvo pokušati pokrenuti 1000 redaka svakih 25 milisekundi:

start 1000

U mom slučaju, rezultat je oko nekoliko milisekundi po ažuriranju. Stoga ću odmah povećati broj redaka na 10.000 XNUMX:

start 10000

Rezultat:

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

Opet, ništa posebno, ali ovo je 24 milijuna linija u minuti, 400 tisuća u sekundi. Za više od 25 milisekundi, ažuriranje se usporilo samo 5 puta, očito kada se promijenila minuta. Povećajmo na 100.000:

start 100000

Rezultat:

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

Kao što vidite, usluga se jedva nosi s tim, ali unatoč tome uspijeva ostati na površini. Takva količina podataka (240 milijuna redaka u minuti) iznimno je velika; u takvim slučajevima uobičajeno je pokrenuti nekoliko klonova (ili čak desetaka klonova) servisa, od kojih svaki obrađuje samo dio znakova. Ipak, rezultat je impresivan za interpretirani jezik koji se primarno fokusira na pohranu podataka.

Može se postaviti pitanje zašto vrijeme raste nelinearno s veličinom svakog ažuriranja. Razlog je taj što je funkcija skupljanja zapravo C funkcija, koja je puno učinkovitija od updateAgg. Počevši od određene veličine ažuriranja (oko 10.000 30), updateAgg doseže svoju gornju granicu i tada njegovo vrijeme izvršenja ne ovisi o veličini ažuriranja. Zahvaljujući preliminarnom koraku Q usluga može probaviti takve količine podataka. Ovo naglašava koliko je važno odabrati pravi algoritam pri radu s velikim podacima. Još jedna točka je ispravno pohranjivanje podataka u memoriju. Ako podaci nisu pohranjeni stupčasto ili nisu raspoređeni prema vremenu, tada bismo se upoznali s takvom stvari kao što je promašaj TLB predmemorije - odsutnost adrese memorijske stranice u predmemoriji adresa procesora. Traženje adrese traje oko XNUMX puta dulje ako je neuspješno, a ako su podaci raspršeni, može nekoliko puta usporiti uslugu.

Zaključak

U ovom sam članku pokazao da su KDB+ i Q baza podataka prikladne ne samo za pohranjivanje velikih podataka i jednostavno pristupanje njima putem odabira, već i za stvaranje usluga obrade podataka koje su sposobne probaviti stotine milijuna redaka/gigabajta podataka čak i u jedan jedini Q proces. Sam jezik Q omogućuje izuzetno konciznu i učinkovitu implementaciju algoritama vezanih uz obradu podataka zbog svoje vektorske prirode, ugrađenog SQL dijalektalnog interpretera i vrlo uspješnog skupa funkcija knjižnice.

Napomenut ću da je gore navedeno samo dio onoga što Q može, ima i druge jedinstvene značajke. Na primjer, iznimno jednostavan IPC protokol koji briše granicu između pojedinačnih Q procesa i omogućuje vam kombiniranje stotina tih procesa u jednu mrežu, koja se može nalaziti na desecima poslužitelja u različitim dijelovima svijeta.

Izvor: www.habr.com

Dodajte komentar