Funksjoner i Q- og KDB+-språket ved å bruke eksempelet på en sanntidstjeneste

Du kan lese om hva KDB+-basen, Q-programmeringsspråket er, hva deres styrker og svakheter er i min forrige artikkel og kort i innledningen. I artikkelen vil vi implementere en tjeneste på Q som vil behandle den innkommende datastrømmen og beregne ulike aggregeringsfunksjoner hvert minutt i "sanntids"-modus (dvs. den vil ha tid til å beregne alt før neste del av data). Hovedtrekket til Q er at det er et vektorspråk som lar deg operere ikke med enkeltobjekter, men med deres arrays, arrays of arrays og andre komplekse objekter. Språk som Q og dets slektninger K, J, APL er kjent for sin korthet. Ofte kan et program som tar opp flere skjermer med kode på et kjent språk som Java skrives på dem på noen få linjer. Det er dette jeg ønsker å demonstrere i denne artikkelen.

Funksjoner i Q- og KDB+-språket ved å bruke eksempelet på en sanntidstjeneste

Innledning

KDB+ er en kolonneformet database med fokus på svært store datamengder, sortert på en bestemt måte (primært etter tid). Det brukes først og fremst i finansinstitusjoner - banker, investeringsfond, forsikringsselskaper. Q-språket er det interne språket til KDB+ som lar deg jobbe effektivt med disse dataene. Q-ideologien er korthet og effektivitet, mens klarhet ofres. Dette begrunnes med at vektorspråket uansett vil være vanskelig å forstå, og kortheten og rikdommen i opptaket gjør at du kan se en mye større del av programmet på én skjerm, noe som til syvende og sist gjør det lettere å forstå.

I denne artikkelen implementerer vi et fullverdig program i Q, og du vil kanskje prøve det ut. For å gjøre dette trenger du selve Q. Du kan laste ned den gratis 32-biters versjonen på kx-selskapets nettsted – www.kx.com. Der, hvis du er interessert, finner du referanseinformasjon om Q, boken Q For dødelige og ulike artikler om dette emnet.

Formulering av problemet

Det er en kilde som sender en tabell med data hvert 25. millisekund. Siden KDB+ primært brukes innen finans, vil vi anta at dette er en tabell over transaksjoner (handler), som har følgende kolonner: tid (tid i millisekunder), sym (selskapsbetegnelse på børs - IBM, AAPL,...), pris (prisen aksjene ble kjøpt til), størrelse (transaksjonens størrelse). Intervallet på 25 millisekunder er vilkårlig, ikke for lite og ikke for langt. Dens tilstedeværelse betyr at dataene kommer til tjenesten som allerede er bufret. Det vil være enkelt å implementere buffering på tjenestesiden, inkludert dynamisk buffering avhengig av gjeldende belastning, men for enkelhets skyld vil vi fokusere på et fast intervall.

Tjenesten må telle hvert minutt for hvert innkommende symbol fra sym-kolonnen et sett med aggregeringsfunksjoner - makspris, gjennomsnittspris, sumstørrelse, etc. nyttig informasjon. For enkelhets skyld vil vi anta at alle funksjoner kan beregnes inkrementelt, dvs. for å få en ny verdi, er det nok å kjenne to tall - den gamle og de innkommende verdiene. For eksempel har funksjonene maks, gjennomsnitt, sum denne egenskapen, men medianfunksjonen har ikke.

Vi vil også anta at den innkommende datastrømmen er tidsbestemt. Dette vil gi oss muligheten til å jobbe kun med siste minutt. I praksis er det nok å kunne jobbe med gjeldende og tidligere minutter i tilfelle noen oppdateringer kommer for sent. For enkelhets skyld vil vi ikke vurdere denne saken.

Aggregasjonsfunksjoner

De nødvendige aggregeringsfunksjonene er oppført nedenfor. Jeg tok så mange av dem som mulig for å øke belastningen på tjenesten:

  • høy – ​​makspris – makspris per minutt.
  • lav – min pris – minstepris per minutt.
  • firstPrice – førstepris – førstepris per minutt.
  • lastPrice – siste pris – siste pris per minutt.
  • firstSize – første størrelse – første handelsstørrelse per minutt.
  • lastSize – siste størrelse – siste handelsstørrelse på et minutt.
  • numTrades – count i – antall handler per minutt.
  • volum – sum størrelse – summen av handelsstørrelser per minutt.
  • pvolume – sumpris – sum av priser per minutt, kreves for avgPrice.
  • – sum omsetningspris*størrelse – totalt volum av transaksjoner per minutt.
  • avgPrice – pvolume%antallTrades – gjennomsnittlig pris per minutt.
  • avgSize – volum%antallTrades – gjennomsnittlig handelsstørrelse per minutt.
  • vwap – omsetning%volum – gjennomsnittlig pris per minutt vektet etter transaksjonsstørrelse.
  • cumVolum – sumvolum – akkumulert størrelse på transaksjoner over hele tiden.

La oss umiddelbart diskutere ett ikke-opplagt poeng - hvordan initialisere disse kolonnene for første gang og for hvert påfølgende minutt. Noen kolonner av typen firstPrice må initialiseres til null hver gang; verdien deres er udefinert. Andre volumtyper må alltid settes til 0. Det er også kolonner som krever en kombinert tilnærming - for eksempel må cumVolume kopieres fra forrige minutt, og for det første settes til 0. La oss sette alle disse parameterne ved hjelp av ordbokdataene 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 la til sym og tid i ordboken for enkelhets skyld, nå er initWith en ferdig linje fra den endelige aggregerte tabellen, hvor det gjenstår å stille inn riktig sym og tid. Du kan bruke den til å legge til nye rader i en tabell.

Vi trenger aggCols når vi lager en aggregeringsfunksjon. Listen må inverteres på grunn av rekkefølgen uttrykk i Q evalueres i (fra høyre til venstre). Målet er å sikre at beregningen går fra høy til cumVolume, siden noen kolonner avhenger av tidligere.

Kolonner som må kopieres til et nytt minutt fra det forrige, sym-kolonnen legges til for enkelhets skyld:

rollColumns:`sym`cumVolume;

La oss nå dele kolonnene inn i grupper etter hvordan de skal oppdateres. Tre typer kan skilles:

  1. Akkumulatorer (volum, omsetning,..) – vi må legge den innkommende verdien til den forrige.
  2. Med et spesielt punkt (høy, lav, ..) - den første verdien i minuttet er hentet fra de innkommende dataene, resten beregnes ved hjelp av funksjonen.
  3. Hvile. Alltid beregnet ved hjelp av en funksjon.

La oss definere variabler for disse klassene:

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

Beregningsrekkefølge

Vi vil oppdatere den aggregerte tabellen i to trinn. For effektiviteten krymper vi først den innkommende tabellen slik at det bare er én rad for hvert tegn og minutt. Det faktum at alle funksjonene våre er inkrementelle og assosiative garanterer at resultatet av dette ekstra trinnet ikke vil endres. Du kan krympe tabellen ved å velge:

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

Denne metoden har en ulempe - settet med beregnede kolonner er forhåndsdefinert. Heldigvis, i Q, er select også implementert som en funksjon der du kan erstatte dynamisk opprettede argumenter:

?[table;whereClause;byClause;selectClause]

Jeg vil ikke beskrive i detalj formatet til argumentene; i vårt tilfelle vil bare av og utvalgte uttrykk være ikke-trivielle og de bør være ordbøker med formkolonner!uttrykk. Dermed kan krympefunksjonen 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 klarhetens skyld brukte jeg parse-funksjonen, som gjør en streng med et Q-uttrykk til en verdi som kan sendes til eval-funksjonen og som kreves i funksjonsvalg. Legg også merke til at preprocess er definert som en projeksjon (dvs. en funksjon med delvis definerte argumenter) av select-funksjonen, ett argument (tabellen) mangler. Hvis vi bruker forprosess på en tabell, vil vi få en komprimert tabell.

Det andre trinnet er å oppdatere den aggregerte tabellen. La oss 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 vanlig å bruke kart-/reduseringsfunksjoner i stedet for loops. Men siden Q er et vektorspråk og vi enkelt kan bruke alle operasjoner på alle symboler samtidig, kan vi til en første tilnærming klare oss uten en løkke i det hele tatt, og utføre operasjoner på alle symboler samtidig:

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

Men vi kan gå lenger, Q har en unik og ekstremt kraftig operatør - den generaliserte oppdragsoperatøren. Den lar deg endre et sett med verdier i en kompleks datastruktur ved å bruke en liste over indekser, funksjoner og argumenter. I vårt tilfelle ser det slik ut:

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

Dessverre, for å tilordne en tabell trenger du en liste over rader, ikke kolonner, og du må transponere matrisen (liste over kolonner til liste over rader) ved å bruke flip-funksjonen. Dette er dyrt for en stor tabell, så i stedet bruker vi en generalisert tilordning for hver kolonne separat, ved å bruke kartfunksjonen (som ser ut som en apostrof):

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

Vi bruker igjen funksjonsprojeksjon. Legg også merke til at i Q er det å lage en liste også en funksjon, og vi kan kalle det ved å bruke each(map)-funksjonen for å få en liste over lister.

For å sikre at settet med beregnede kolonner ikke er fikset, vil vi lage uttrykket ovenfor dynamisk. La oss først definere funksjoner for å beregne hver kolonne, ved å bruke rad- og inp-variablene for å referere til de aggregerte dataene og inndataene:

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

Noen kolonner er spesielle; deres første verdi skal ikke beregnes av funksjonen. Vi kan fastslå at det er den første ved rad[`numTrades]-kolonnen - hvis den inneholder 0, er verdien først. Q har en valgfunksjon - ?[Boolsk liste;liste1;liste2] - som velger en verdi fra liste 1 eller 2 avhengig av betingelsen i det første argumentet:

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

Her kalte jeg en generalisert oppgave med min funksjon (et uttrykk i krøllete bukseseler). Den mottar gjeldende verdi (det første argumentet) og et ekstra argument, som jeg sender i den fjerde parameteren.

La oss legge til batterihøyttalere separat, siden funksjonen er den samme for dem:

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

Dette er en normal tildeling etter Q-standarder, men jeg tildeler en liste med verdier på en gang. Til slutt, la oss lage hovedfunksjonen:

// ":",/: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 uttrykket lager jeg dynamisk en funksjon fra en streng som inneholder uttrykket jeg ga ovenfor. Resultatet vil se slik ut:

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

Kolonneevalueringsrekkefølgen er invertert fordi i Q er evalueringsrekkefølgen fra høyre til venstre.

Nå har vi to hovedfunksjoner som er nødvendige for beregninger, vi trenger bare å legge til litt infrastruktur og tjenesten er klar.

Siste trinn

Vi har forprosess og updateAgg-funksjoner som gjør alt arbeidet. Men det er fortsatt nødvendig å sikre riktig overgang gjennom minutter og beregne indekser for aggregering. Først av alt, la oss definere init-funksjonen:

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 rullefunksjonen, som vil endre gjeldende minutt:

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

Vi trenger en funksjon for å legge til 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 til slutt, upd-funksjonen (det tradisjonelle navnet på denne funksjonen for Q-tjenester), som kalles av klienten for å legge til 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 koden for tjenesten vår, som lovet, bare noen få 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];
 };

Testing

La oss sjekke ytelsen til tjenesten. For å gjøre dette, la oss kjøre den i en egen prosess (legg inn koden i service.q-filen) og kalle init-funksjonen:

q service.q –p 5566

q)init[]

I en annen konsoll starter du den andre Q-prosessen og kobler til den første:

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

La oss først lage en liste med symboler - 10000 XNUMX stykker og legge til en funksjon for å lage en tilfeldig tabell. I den andre konsollen:

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 la til tre ekte symboler i listen for å gjøre det lettere å se etter dem i tabellen. Rnd-funksjonen lager en tilfeldig tabell med n rader, hvor tiden varierer fra t til t+25 millisekunder.

Nå kan du prøve å sende data til tjenesten (legg til de første ti timene):

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

Du kan sjekke i tjenesten at tabellen er oppdatert:

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

La oss nå utføre lasttesting for å finne ut hvor mye data tjenesten kan behandle per minutt. La meg minne deg på at vi satte oppdateringsintervallet til 25 millisekunder. Følgelig må tjenesten (i gjennomsnitt) passe inn i minst 20 millisekunder per oppdatering for å gi brukerne tid til å be om data. Skriv inn følgende i den andre prosessen:

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 å kjøre først i 1000 rader hvert 25. millisekund:

start 1000

I mitt tilfelle er resultatet rundt et par millisekunder per oppdatering. Så jeg vil umiddelbart øke antall rader til 10.000 XNUMX:

start 10000

Resultat:

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

Igjen, ikke noe spesielt, men dette er 24 millioner linjer per minutt, 400 tusen per sekund. I mer enn 25 millisekunder ble oppdateringen redusert bare 5 ganger, tilsynelatende når minuttet endret seg. La oss øke 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 ser klarer tjenesten knapt, men likevel klarer den å holde seg flytende. Et slikt datavolum (240 millioner rader per minutt) er ekstremt stort; i slike tilfeller er det vanlig å lansere flere kloner (eller til og med dusinvis av kloner) av tjenesten, som hver behandler bare en del av karakterene. Likevel er resultatet imponerende for et tolket språk som først og fremst fokuserer på datalagring.

Spørsmålet kan oppstå om hvorfor tiden vokser ikke-lineært med størrelsen på hver oppdatering. Årsaken er at krympefunksjonen faktisk er en C-funksjon, som er mye mer effektiv enn updateAgg. Starter fra en viss oppdateringsstørrelse (rundt 10.000 30), når updateAgg taket, og deretter avhenger ikke utførelsestiden av oppdateringsstørrelsen. Det er på grunn av det foreløpige trinnet Q at tjenesten er i stand til å fordøye slike datamengder. Dette fremhever hvor viktig det er å velge riktig algoritme når man jobber med big data. Et annet poeng er riktig lagring av data i minnet. Hvis dataene ikke ble lagret kolonnevis eller ikke var ordnet etter tid, ville vi blitt kjent med noe som en TLB-cache-miss - fraværet av en minnesideadresse i prosessoradressebufferen. Å søke etter en adresse tar omtrent XNUMX ganger lengre tid hvis det ikke lykkes, og hvis dataene er spredt, kan det bremse tjenesten flere ganger.

Konklusjon

I denne artikkelen viste jeg at KDB+- og Q-databasen er egnet ikke bare for å lagre store data og enkelt få tilgang til dem gjennom utvalgte, men også for å lage databehandlingstjenester som er i stand til å fordøye hundrevis av millioner rader/gigabyte med data selv i én enkelt Q-prosess. Q-språket i seg selv tillater ekstremt kortfattet og effektiv implementering av algoritmer relatert til databehandling på grunn av dets vektornatur, innebygde SQL-dialekttolker og et meget vellykket sett med bibliotekfunksjoner.

Jeg vil merke at det ovennevnte bare er en del av det Q kan gjøre, det har også andre unike funksjoner. For eksempel en ekstremt enkel IPC-protokoll som visker ut grensen mellom individuelle Q-prosesser og lar deg kombinere hundrevis av disse prosessene til et enkelt nettverk, som kan plasseres på dusinvis av servere i forskjellige deler av verden.

Kilde: www.habr.com

Legg til en kommentar