Karakteristike Q i KDB+ jezika na primjeru usluge u realnom vremenu

O tome šta su KDB+ baza, Q programski jezik, koje su njihove prednosti i mane 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 različite funkcije agregacije svake minute u načinu rada u "realnom vremenu" (tj. imat će vremena da sve izračuna prije sljedećeg dijela podataka). Glavna karakteristika Q-a je da je to vektorski jezik koji vam omogućava da radite ne sa pojedinačnim objektima, već sa 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 na poznatom jeziku kao što je Java može napisati na njima u nekoliko redova. To je ono što želim pokazati u ovom članku.

Karakteristike Q i KDB+ jezika na primjeru usluge u realnom vremenu

Uvod

KDB+ je stupasta baza podataka fokusirana na vrlo velike količine podataka, poredanih na specifičan način (prvenstveno po vremenu). Koristi se prvenstveno u finansijskim institucijama - bankama, investicionim fondovima, osiguravajućim društvima. Q jezik je interni jezik KDB+ koji vam omogućava da efikasno radite sa ovim podacima. Q ideologija je kratkoća i efikasnost, dok se jasnoća žrtvuje. To se opravdava činjenicom da će vektorski jezik u svakom slučaju biti teško razumljiv, a sažetost i bogatstvo snimka omogućava vam da na jednom ekranu vidite mnogo veći dio programa, što ga u konačnici čini lakšim za razumijevanje.

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

Izjava o problemu

Postoji izvor koji šalje tabelu sa podacima svakih 25 milisekundi. Budući da se KDB+ koristi prvenstveno u finansijama, pretpostavićemo da se radi o tabeli transakcija (trgova), koja ima sledeće kolone: ​​vreme (vreme u milisekundama), sim (oznaka kompanije na berzi - IBM, AAPL,…), cijena (cijena po kojoj su dionice kupljene), veličina (veličina transakcije). Interval od 25 milisekundi je proizvoljan, nije premali niti predug. Njegovo prisustvo znači da podaci dolaze u servis već u baferu. Bilo bi lako implementirati baferovanje na strani usluge, uključujući dinamičko baferovanje u zavisnosti od trenutnog opterećenja, ali radi jednostavnosti, fokusiraćemo se na fiksni interval.

Usluga mora svake minute brojati za svaki dolazni simbol iz kolone sym skup funkcija agregiranja - maksimalna cijena, prosječna cijena, veličina sume, itd. korisne informacije. Radi jednostavnosti, pretpostavit ćemo da se sve funkcije mogu izračunati inkrementalno, tj. da biste dobili novu vrijednost, dovoljno je znati dva broja - stari i dolazne vrijednosti. Na primjer, funkcije max, prosjek, sum imaju ovo svojstvo, ali funkcija medijana nema.

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

Funkcije agregacije

Potrebne funkcije agregacije su navedene u nastavku. Uzeo sam ih što je više moguće da povećam opterećenje usluge:

  • visoka – maksimalna cijena – maksimalna cijena po minuti.
  • niska – min cijena – minimalna cijena po minuti.
  • firstPrice – prva cijena – prva cijena po minuti.
  • lastPrice – zadnja cijena – zadnja cijena po minuti.
  • firstSize – prva veličina – prva veličina trgovine u minuti.
  • lastSize – zadnja veličina — zadnja veličina trgovine u minuti.
  • numTrades – count i – broj trgovina u minuti.
  • volume – veličina sume – zbir veličina trgovine u minuti.
  • pvolume – zbir cijena – zbir cijena po minuti, potreban za avgPrice.
  • – zbirna cijena prometa*veličina – ukupan obim 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 veličinom transakcije.
  • cumVolume – zbirni volumen – akumulirana veličina transakcija tokom cijelog vremena.

Hajdemo odmah razgovarati o jednoj neočiglednoj stvari - kako inicijalizirati ove stupce prvi put i svaki sljedeći minut. Neki stupci tipa firstPrice moraju biti inicijalizirani na null svaki put; njihova vrijednost je nedefinirana. Ostali tipovi volumena uvijek moraju biti postavljeni na 0. Postoje i kolone koje zahtijevaju kombinovani pristup - na primjer, cumVolume se mora kopirati iz prethodnog minuta, a za prvi se postaviti na 0. Postavimo sve ove parametre koristeći podatke iz rječnika tip (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 rečnik radi praktičnosti, sada je initWith gotova linija iz završne agregirane tabele, gdje ostaje podesiti ispravan sym i vrijeme. Možete ga koristiti za dodavanje novih redova u tabelu.

Trebat će nam aggCols kada kreiramo funkciju agregacije. Lista mora biti obrnuta zbog redosleda kojim se procenjuju izrazi u Q (s desna na levo). Cilj je osigurati da izračunavanje ide od high do cumVolume, jer neke kolone zavise od prethodnih.

Kolone koje je potrebno kopirati u novu minutu od prethodne, kolona sym se dodaje radi praktičnosti:

rollColumns:`sym`cumVolume;

Sada podijelimo kolone u grupe prema tome kako ih treba ažurirati. Mogu se razlikovati tri tipa:

  1. Akumulatori (volumen, promet,..) – moramo dodati ulaznu vrijednost prethodnoj.
  2. Sa posebnom tačkom (visoka, niska, ..) – prva vrijednost u minuti se uzima iz dolaznih podataka, ostale se izračunavaju pomoću funkcije.
  3. Odmori se. Uvijek se izračunava pomoću funkcije.

Definirajmo varijable za ove klase:

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

Red kalkulacije

Agregiranu tabelu ćemo ažurirati u dvije faze. Radi efikasnosti, prvo smanjujemo dolaznu tabelu tako da postoji samo jedan red za svaki znak i minut. Činjenica da su sve naše funkcije inkrementalne i asocijativne garantuje da se rezultat ovog dodatnog koraka neće promijeniti. Možete smanjiti tabelu koristeći select:

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

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

?[table;whereClause;byClause;selectClause]

Neću detaljno opisivati ​​format argumenata; u našem slučaju, samo by i select izrazi će biti netrivijalni i oni bi trebali biti rječnici oblika columns!expressions. Dakle, funkcija skupljanja može se 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, koristio sam funkciju parse, koja pretvara string sa 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 predproces definiran kao projekcija (tj. funkcija s djelomično definiranim argumentima) funkcije za odabir, jedan argument (tabela) nedostaje. Ako primenimo predproces na tabelu, dobićemo kompresovanu tabelu.

Druga faza je ažuriranje agregirane tabele. Hajde da prvo napišemo 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, uobičajeno je koristiti funkcije mape/reduce umjesto petlji. Ali pošto je Q vektorski jezik i možemo bezbedno primeniti sve operacije na sve simbole odjednom, onda u prvoj aproksimaciji možemo uopšte bez petlje, izvršavajuć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 - generalizovani operator dodele. Omogućuje vam promjenu skupa vrijednosti u složenoj strukturi podataka koristeći listu 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, da biste dodijelili tablicu, potrebna vam je lista redova, a ne kolona, ​​i morate transponirati matricu (listu kolona u listu redova) koristeći funkciju flip. Ovo je skupo za veliku tablicu, pa umjesto toga primjenjujemo generaliziranu dodjelu na svaku kolonu posebno, koristeći funkciju mape (koja izgleda kao apostrof):

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

Opet koristimo projekciju funkcije. Takođe imajte na umu da je u Q kreiranje liste takođe funkcija i možemo je pozvati pomoću funkcije every(map) da bismo dobili listu lista.

Kako bismo osigurali da skup izračunatih kolona nije fiksan, kreirat ćemo gornji izraz dinamički. Hajde da prvo definiramo funkcije za izračunavanje svake kolone, koristeći varijable reda 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; njihova prva vrijednost ne bi trebala biti izračunata od strane funkcije. Možemo odrediti da je prvi po koloni row[`numTrades] - ako sadrži 0, tada je vrijednost prva. Q ima funkciju odabira - ?[Boolean list;list1;list2] - koja bira vrijednost sa liste 1 ili 2 u zavisnosti od uslova u prvom argumentu:

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

Ovdje sam nazvao generalizirani zadatak sa svojom funkcijom (izraz u vitičastim zagradama). Prima trenutnu vrijednost (prvi argument) i dodatni argument, koji prosljeđujem u 4. parametar.

Dodajmo odvojeno baterijske zvučnike, jer im je funkcija ista:

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

Ovo je normalna dodjela po Q standardima, ali listu vrijednosti dodjeljujem 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),")]}";

Sa ovim izrazom, ja dinamički kreiram funkciju iz stringa 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 evaluacije stupca je obrnut jer je u Q redoslijed evaluacije s desna na lijevo.

Sada imamo dvije glavne funkcije potrebne za proračune, samo trebamo dodati malo infrastrukture i servis je spreman.

Završni koraci

Imamo preprocess i updateAgg funkcije koje obavljaju sav posao. Ali još uvijek je potrebno osigurati ispravan 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 klijent poziva da doda podatke:

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 je obećano, 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

Hajde da proverimo performanse usluge. Da bismo to učinili, pokrenimo ga u zasebnom procesu (stavimo kod u datoteku service.q) i pozovimo init funkciju:

q service.q –p 5566

q)init[]

U drugoj konzoli pokrenite drugi Q proces i povežite se s prvim:

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

Prvo, napravimo listu simbola - 10000 komada i dodajmo funkciju za kreiranje nasumične 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)}

Dodao sam tri prava simbola na listu kako bih ih lakše tražio u tabeli. Funkcija rnd kreira slučajnu tabelu sa n redova, gde vreme varira od t do t+25 milisekundi.

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

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

U servisu možete provjeriti da li je tabela 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

Hajde da sada izvršimo testiranje opterećenja da saznamo koliko podataka usluga može da obradi u minuti. Da vas podsjetim da smo postavili interval ažuriranja na 25 milisekundi. Shodno tome, usluga mora (u prosjeku) stati u najmanje 20 milisekundi po ažuriranju kako bi korisnicima dala vremena da zatraže podatke. 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 dva minuta. Možete pokušati prvo pokrenuti 1000 redova svakih 25 milisekundi:

start 1000

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

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 miliona linija u minuti, 400 hiljada u sekundi. Za više od 25 milisekundi, ažuriranje je usporilo samo 5 puta, očigledno kada se promijenila minuta. Hajde da povećamo 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, servis se jedva snalazi, ali ipak uspijeva da opstane. Takav obim podataka (240 miliona redova u minuti) je izuzetno velik; u takvim slučajevima uobičajeno je pokrenuti nekoliko klonova (ili čak desetine klonova) servisa, od kojih svaki obrađuje samo dio karaktera. Ipak, rezultat je impresivan za interpretirani jezik koji se prvenstveno fokusira na skladištenje podataka.

Može se postaviti pitanje zašto vrijeme raste nelinearno s veličinom svakog ažuriranja. Razlog je taj što je funkcija shrink zapravo C funkcija, koja je mnogo efikasnija od updateAgg-a. Počevši od određene veličine ažuriranja (oko 10.000), updateAgg dostiže svoj plafon i tada njegovo vrijeme izvršenja ne ovisi o veličini ažuriranja. Zbog preliminarnog koraka Q usluga je u stanju da probavi takve količine podataka. Ovo naglašava koliko je važno odabrati pravi algoritam kada radite s velikim podacima. Druga stvar je ispravno skladištenje podataka u memoriji. Ako podaci nisu bili pohranjeni u stupcu ili nisu bili poređani prema vremenu, tada bismo se upoznali sa takvom stvari kao što je promašaj TLB keša - odsustvo adrese memorijske stranice u kešu adresa procesora. Traženje adrese traje oko 30 puta duže ako ne uspije, a ako su podaci razbacani, može nekoliko puta usporiti uslugu.

zaključak

U ovom članku sam pokazao da su KDB+ i Q baza podataka prikladne ne samo za pohranjivanje velikih podataka i lak pristup njima putem odabira, već i za kreiranje usluga obrade podataka koje su u stanju probaviti stotine miliona redova/gigabajta podataka čak iu jedan Q proces. Sam Q jezik omogućava izuzetno konciznu i efikasnu implementaciju algoritama vezanih za obradu podataka zbog svoje vektorske prirode, ugrađenog SQL dijalektnog tumača i vrlo uspješnog skupa bibliotečkih funkcija.

Napominjem da je gore navedeno samo dio onoga što Q može učiniti, ima i druge jedinstvene karakteristike. Na primjer, izuzetno jednostavan IPC protokol koji briše granicu između pojedinačnih Q procesa i omogućava vam da kombinirate stotine ovih procesa u jednu mrežu, koja se može nalaziti na desetinama servera u različitim dijelovima svijeta.

izvor: www.habr.com

Dodajte komentar