Funktioner i Q- og KDB+-sproget ved hjælp af eksemplet på en realtidstjeneste

Du kan læse om, hvad KDB+ basen, Q-programmeringssproget er, hvad deres styrker og svagheder er i min tidligere artiklen og kort i indledningen. I artiklen vil vi implementere en tjeneste på Q, der vil behandle den indgående datastrøm og beregne forskellige aggregeringsfunktioner hvert minut i "realtids"-tilstand (dvs. den vil have tid til at beregne alt før den næste del af data). Hovedtræk ved Q er, at det er et vektorsprog, der giver dig mulighed for ikke at arbejde med enkelte objekter, men med deres arrays, arrays af arrays og andre komplekse objekter. Sprog som Q og dets slægtninge K, J, APL er berømte for deres korthed. Ofte kan et program, der optager flere skærmbilleder med kode på et velkendt sprog som Java, skrives på dem på få linjer. Det er, hvad jeg vil demonstrere i denne artikel.

Funktioner i Q- og KDB+-sproget ved hjælp af eksemplet på en realtidstjeneste

Indledning

KDB+ er en kolonneformet database med fokus på meget store mængder data, ordnet på en bestemt måde (primært efter tid). Det bruges primært i finansielle institutioner - banker, investeringsfonde, forsikringsselskaber. Q-sproget er det interne sprog i KDB+, der giver dig mulighed for effektivt at arbejde med disse data. Q-ideologien er korthed og effektivitet, mens klarhed ofres. Dette begrundes med, at vektorsproget under alle omstændigheder vil være svært at forstå, og optagelsens korthed og rigdom giver dig mulighed for at se en meget større del af programmet på én skærm, hvilket i sidste ende gør det lettere at forstå.

I denne artikel implementerer vi et fuldgyldigt program i Q, og du vil måske prøve det. For at gøre dette skal du bruge det faktiske Q. Du kan downloade den gratis 32-bit version på kx virksomhedens hjemmeside - www.kx.com. Der, hvis du er interesseret, vil du finde referenceoplysninger om Q, bogen Q For dødelige og diverse artikler om dette emne.

Formulering af problemet

Der er en kilde, der sender en tabel med data hvert 25. millisekund. Da KDB+ primært bruges i økonomi, vil vi antage, at dette er en tabel over transaktioner (handler), som har følgende kolonner: tid (tid i millisekunder), sym (virksomhedsbetegnelse på børsen - IBM, AAPL,...), pris (den pris, hvortil aktierne blev købt), størrelse (transaktionens størrelse). Intervallet på 25 millisekunder er vilkårligt, ikke for lille og ikke for langt. Dens tilstedeværelse betyder, at dataene kommer til tjenesten, der allerede er bufferet. Det ville være nemt at implementere buffering på servicesiden, herunder dynamisk buffering afhængigt af den aktuelle belastning, men for nemheds skyld vil vi fokusere på et fast interval.

Tjenesten skal tælle hvert minut for hvert indkommende symbol fra sym-kolonnen et sæt af aggregeringsfunktioner - maks. pris, gennemsnitspris, sumstørrelse osv. brugbar information. For nemheds skyld vil vi antage, at alle funktioner kan beregnes trinvist, dvs. for at opnå en ny værdi, er det nok at kende to tal - den gamle og den indgående værdi. For eksempel har funktionerne max, gennemsnit, sum denne egenskab, men medianfunktionen har ikke.

Vi vil også antage, at den indgående datastrøm er tidsbestemt. Dette vil give os mulighed for kun at arbejde med det sidste minut. I praksis er det nok at kunne arbejde med det nuværende og tidligere referat, hvis nogle opdateringer kommer for sent. For nemheds skyld vil vi ikke overveje denne sag.

Aggregationsfunktioner

De nødvendige aggregeringsfunktioner er angivet nedenfor. Jeg tog så mange af dem som muligt for at øge belastningen på tjenesten:

  • høj – maks. pris – maks. pris pr. minut.
  • lav – min. pris – minimumspris pr. minut.
  • firstPrice – førstepris – førstepris pr. minut.
  • lastPrice – sidste pris – sidste pris per minut.
  • firstSize – første størrelse – første handelsstørrelse pr. minut.
  • lastSize – sidste størrelse – sidste handelsstørrelse på et minut.
  • numTrades – count i – antal handler pr. minut.
  • volumen – sum størrelse – summen af ​​handelsstørrelser pr. minut.
  • pvolume – sumpris – sum af priser pr. minut, påkrævet for gennemsnitspris.
  • – sumsomsætningspris*størrelse – total mængde transaktioner pr. minut.
  • avgPrice – pvolume%antalTrades – gennemsnitspris pr. minut.
  • avgSize – volumen%antalTrades – gennemsnitlig handelsstørrelse pr. minut.
  • vwap – omsætning%volumen – gennemsnitspris pr. minut vægtet efter transaktionsstørrelse.
  • cumVolume – sumvolumen – akkumuleret størrelse af transaktioner over hele tiden.

Lad os straks diskutere et ikke-indlysende punkt - hvordan man initialiserer disse kolonner for første gang og for hvert efterfølgende minut. Nogle kolonner af typen firstPrice skal initialiseres til null hver gang; deres værdi er udefineret. Andre volumentyper skal altid sættes til 0. Der er også kolonner, der kræver en kombineret tilgang - for eksempel skal cumVolume kopieres fra det foregående minut, og for det første indstilles til 0. Lad os indstille alle disse parametre ved hjælp af ordbogsdataene type (analogt med en post):

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

Jeg har tilføjet sym og tid til ordbogen for nemheds skyld, nu er initWith en færdig linje fra den endelige aggregerede tabel, hvor det er tilbage at indstille det korrekte sym og tidspunkt. Du kan bruge den til at tilføje nye rækker til en tabel.

Vi skal bruge aggCols, når vi opretter en aggregeringsfunktion. Listen skal vendes på grund af den rækkefølge, udtryk i Q evalueres i (fra højre mod venstre). Målet er at sikre, at beregningen går fra høj til cumVolume, da nogle kolonner afhænger af tidligere.

Kolonner, der skal kopieres til et nyt minut fra det forrige, er symbolkolonnen tilføjet for nemheds skyld:

rollColumns:`sym`cumVolume;

Lad os nu opdele kolonnerne i grupper efter, hvordan de skal opdateres. Der kan skelnes mellem tre typer:

  1. Akkumulatorer (volumen, omsætning,..) – vi skal lægge den indgående værdi til den forrige.
  2. Med et særligt punkt (høj, lav, ..) - den første værdi i minuttet tages fra de indgående data, resten beregnes ved hjælp af funktionen.
  3. Hvile. Altid beregnet ved hjælp af en funktion.

Lad os definere variabler for disse klasser:

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

Beregningsrækkefølge

Vi vil opdatere den aggregerede tabel i to trin. For effektivitetens skyld formindsker vi først den indgående tabel, så der kun er én række for hvert tegn og minut. Det faktum, at alle vores funktioner er inkrementelle og associative, garanterer, at resultatet af dette ekstra trin ikke ændres. Du kan formindske bordet ved at vælge:

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

Denne metode har en ulempe - sættet af beregnede kolonner er foruddefineret. Heldigvis er select også implementeret i Q som en funktion, hvor du kan erstatte dynamisk oprettede argumenter:

?[table;whereClause;byClause;selectClause]

Jeg vil ikke beskrive i detaljer formatet af argumenterne; i vores tilfælde vil kun af og udvalgte udtryk være ikke-trivielle, og de bør være ordbøger med formkolonner!udtryk. Således kan krympefunktionen defineres som følger:

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

For klarhedens skyld brugte jeg parse-funktionen, som omdanner en streng med et Q-udtryk til en værdi, der kan overføres til eval-funktionen, og som er påkrævet i funktionsvælgeren. Bemærk også, at preprocess er defineret som en projektion (dvs. en funktion med delvist definerede argumenter) af select-funktionen, et argument (tabellen) mangler. Hvis vi anvender forproces på en tabel, får vi en komprimeret tabel.

Den anden fase er opdatering af den aggregerede tabel. Lad os først skrive algoritmen i pseudokode:

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

I Q er det almindeligt at bruge map/reducer-funktioner i stedet for loops. Men da Q er et vektorsprog, og vi nemt kan anvende alle operationer på alle symboler på én gang, så kan vi til en første tilnærmelse undvære en loop overhovedet og udføre operationer på alle symboler på én gang:

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

Men vi kan gå længere, Q har en unik og ekstremt kraftfuld operatør - den generaliserede tildelingsoperatør. Det giver dig mulighed for at ændre et sæt værdier i en kompleks datastruktur ved hjælp af en liste over indekser, funktioner og argumenter. I vores tilfælde ser det sådan ud:

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

Desværre, for at tildele til en tabel, har du brug for en liste over rækker, ikke kolonner, og du skal transponere matrixen (liste over kolonner til liste over rækker) ved hjælp af flip-funktionen. Dette er dyrt for en stor tabel, så i stedet anvender vi en generaliseret tildeling til hver kolonne separat ved hjælp af kortfunktionen (der ligner en apostrof):

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

Vi bruger igen funktionsprojektion. Bemærk også, at i Q er oprettelse af en liste også en funktion, og vi kan kalde den ved at bruge funktionen each(map) for at få en liste over lister.

For at sikre at sættet af beregnede kolonner ikke er fast, vil vi oprette ovenstående udtryk dynamisk. Lad os først definere funktioner til at beregne hver kolonne ved at bruge række- og inp-variablerne til at referere til de aggregerede data og inputdata:

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

Nogle kolonner er specielle; deres første værdi bør ikke beregnes af funktionen. Vi kan bestemme, at det er den første ved kolonnen række[`numTrades] - hvis den indeholder 0, så er værdien først. Q har en valgfunktion - ?[Boolsk liste;liste1;liste2] - som vælger en værdi fra liste 1 eller 2 afhængigt af betingelsen i det første argument:

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

Her kaldte jeg en generaliseret opgave med min funktion (et udtryk i krøllede seler). Den modtager den aktuelle værdi (det første argument) og et ekstra argument, som jeg sender i den 4. parameter.

Lad os tilføje batterihøjttalere separat, da funktionen er den samme for dem:

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

Dette er en normal tildeling efter Q-standarder, men jeg tildeler en liste over værdier på én gang. Lad os endelig oprette hovedfunktionen:

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

Med dette udtryk opretter jeg dynamisk en funktion fra en streng, der indeholder det udtryk, jeg gav ovenfor. Resultatet vil se således ud:

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

Kolonneevalueringsrækkefølgen er inverteret, fordi i Q er evalueringsrækkefølgen fra højre mod venstre.

Nu har vi to hovedfunktioner, der er nødvendige for beregninger, vi skal blot tilføje lidt infrastruktur, og tjenesten er klar.

Afsluttende trin

Vi har forproces- og updateAgg-funktioner, der gør alt arbejdet. Men det er stadig nødvendigt at sikre den korrekte overgang gennem minutter og beregne indekser for aggregering. Først og fremmest, lad os definere init-funktionen:

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
 }

Vi vil også definere rullefunktionen, som vil ændre det aktuelle minut:

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

Vi skal bruge en funktion til at tilføje nye tegn:

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

Og endelig, upd-funktionen (det traditionelle navn for denne funktion for Q-tjenester), som kaldes af klienten for at tilføje data:

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

Det er alt. Her er den komplette kode for vores tjeneste, som lovet, kun et par linjer:

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

Test

Lad os tjekke ydeevnen af ​​tjenesten. For at gøre dette, lad os køre det i en separat proces (indsæt koden i service.q-filen) og kalder init-funktionen:

q service.q –p 5566

q)init[]

I en anden konsol skal du starte den anden Q-proces og oprette forbindelse til den første:

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

Lad os først oprette en liste med symboler - 10000 stykker og tilføje en funktion for at skabe en tilfældig tabel. I den anden konsol:

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

Jeg tilføjede tre rigtige symboler til listen for at gøre det nemmere at lede efter dem i tabellen. rnd-funktionen opretter en tilfældig tabel med n rækker, hvor tiden varierer fra t til t+25 millisekunder.

Nu kan du prøve at sende data til tjenesten (tilføj de første ti timer):

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

Du kan tjekke i tjenesten, at tabellen er blevet opdateret:

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

Resultat:

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

Lad os nu udføre belastningstest for at finde ud af, hvor meget data tjenesten kan behandle pr. minut. Lad mig minde dig om, at vi sætter opdateringsintervallet til 25 millisekunder. Derfor skal tjenesten (i gennemsnit) passe ind i mindst 20 millisekunder pr. opdatering for at give brugerne tid til at anmode om data. Indtast følgende i den anden proces:

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 er to minutter. Du kan prøve at køre først i 1000 rækker hvert 25. millisekund:

start 1000

I mit tilfælde er resultatet omkring et par millisekunder pr. opdatering. Så jeg vil straks øge antallet af rækker til 10.000:

start 10000

Resultat:

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

Igen, ikke noget særligt, men det er 24 millioner linjer i minuttet, 400 tusinde i sekundet. I mere end 25 millisekunder bremsede opdateringen kun 5 gange, tilsyneladende da minuttet ændrede sig. Lad os øge til 100.000:

start 100000

Resultat:

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

Som du kan se, kan tjenesten næsten ikke klare sig, men ikke desto mindre formår den at holde sig oven vande. En sådan mængde data (240 millioner rækker i minuttet) er ekstremt stor; i sådanne tilfælde er det almindeligt at lancere flere kloner (eller endda snesevis af kloner) af tjenesten, som hver kun behandler en del af karaktererne. Alligevel er resultatet imponerende for et fortolket sprog, der primært fokuserer på datalagring.

Spørgsmålet kan opstå, hvorfor tiden vokser ikke-lineært med størrelsen af ​​hver opdatering. Årsagen er, at shrink-funktionen faktisk er en C-funktion, som er meget mere effektiv end updateAgg. Startende fra en bestemt opdateringsstørrelse (omkring 10.000), når updateAgg sit loft, og derefter afhænger dens udførelsestid ikke af opdateringsstørrelsen. Det er på grund af det indledende trin Q, at tjenesten er i stand til at fordøje sådanne mængder af data. Dette fremhæver, hvor vigtigt det er at vælge den rigtige algoritme, når man arbejder med big data. Et andet punkt er den korrekte lagring af data i hukommelsen. Hvis dataene ikke blev lagret i kolonner eller ikke var ordnet efter tid, ville vi blive bekendt med sådan noget som en TLB-cache-miss - fraværet af en hukommelsessideadresse i processoradressecachen. At søge efter en adresse tager omkring 30 gange længere, hvis det ikke lykkes, og hvis dataene er spredt, kan det bremse tjenesten flere gange.

Konklusion

I denne artikel viste jeg, at KDB+ og Q-databasen ikke kun er egnet til at gemme store data og let få adgang til dem gennem udvalgte, men også til at skabe databehandlingstjenester, der er i stand til at fordøje hundredvis af millioner af rækker/gigabyte data, selv i en enkelt Q-proces. Q-sproget i sig selv giver mulighed for ekstremt kortfattet og effektiv implementering af algoritmer relateret til databehandling på grund af dets vektornatur, indbyggede SQL-dialektfortolker og et meget vellykket sæt af biblioteksfunktioner.

Jeg vil bemærke, at ovenstående kun er en del af, hvad Q kan gøre, det har også andre unikke funktioner. For eksempel en ekstremt simpel IPC-protokol, der sletter grænsen mellem individuelle Q-processer og giver dig mulighed for at kombinere hundredvis af disse processer til et enkelt netværk, som kan være placeret på snesevis af servere i forskellige dele af verden.

Kilde: www.habr.com

Tilføj en kommentar