KDB+, prodotto dell'azienda è un database colonnare estremamente veloce e ampiamente riconosciuto, progettato per memorizzare serie temporali e per effettuare calcoli analitici basati su di esse. Inizialmente ha goduto di grande popolarità nel settore finanziario, utilizzato da tutte le prime 10 banche d'investimento e molti noti fondi hedge, borse e altre organizzazioni. Recentemente, KX ha deciso di espandere la propria clientela e ora offre soluzioni anche in altri settori con grandi quantità di dati ordinati nel tempo o in altro modo, come telecomunicazioni, bioinformatica, produzione, ecc. Hanno anche diventato partner del team Aston Martin Red Bull Racing nella Formula 1, dove aiutano a raccogliere e elaborare i dati dai sensori delle auto e ad analizzare i test nella galleria del vento. In questo articolo, voglio discutere quali caratteristiche rendono KDB+ super performante, perché le aziende sono pronte a investire somme considerevoli in essa, e infine, perché non è davvero un database.

In questo articolo cercherò di descrivere in generale cosa è KDB+, quali funzionalità e limitazioni presenta, e quale sia il suo valore per le aziende che desiderano elaborare grandi volumi di dati. Non entrerò nei dettagli dell'implementazione di KDB+ e del suo linguaggio di programmazione Q. Entrambi questi argomenti sono molto ampi e meritano articoli separati. Molte informazioni su questi temi possono essere trovate sul sito code.kx.com, incluso il libro su Q — Q For Mortals (vedi il link qui sotto).
Alcuni termini
- Database in-memory. Un database che memorizza i dati nella memoria operativa per accelerare l'accesso. I vantaggi di questo tipo di database sono evidenti, mentre gli svantaggi comprendono la possibilità di perdita di dati e la necessità di avere molta memoria sul server.
- Database a colonne. Un database in cui i dati sono memorizzati colonna per colonna, piuttosto che riga per riga. Il principale vantaggio di questo tipo di database è che i dati di una singola colonna sono memorizzati insieme su disco e in memoria, il che accelera notevolmente l'accesso. Non è necessario caricare colonne che non sono utilizzate nella query. Il principale svantaggio è la difficoltà di modificare e eliminare i record.
- Serie temporal. Dati con una colonna di tipo data o ora. Di norma, per questi dati è importante l'ordinamento temporale, così da poter facilmente determinare quale record preceda o segua l'attuale, o per applicare funzioni il cui risultato dipende dall'ordine dei record. I database classici sono costruiti su un principio completamente diverso: la rappresentazione di un insieme di record come una collezione, dove l'ordine dei record non è definito.
- Vettore. Nel contesto di KDB+ — è un elenco di elementi di un unico tipo atomico, come i numeri. In altre parole, un array di elementi. Gli array, a differenza degli elenchi, possono essere memorizzati in modo compatto e elaborati utilizzando istruzioni vettoriali del processore.
Nota storica
L'azienda KX è stata fondata nel 1993 da Arthur Whitney, che in precedenza aveva lavorato presso Morgan Stanley sul linguaggio A+, un linguaggio molto originale e popolare nel mondo finanziario. Naturalmente, in KX Arthur ha continuato nella stessa direzione creando il linguaggio vettoriale-funzionale K, seguendo le idee del radicale minimalismo. I programmi in K sembrano un insieme disordinato di punteggiatura e simboli speciali, il significato dei simboli e delle funzioni dipende dal contesto, e ogni operazione porta con sé molto più significato rispetto a quanto avviene nei linguaggi di programmazione tradizionali. Grazie a questo, un programma in K occupa uno spazio minimo: poche righe possono sostituire pagine di testo in linguaggi prolissi come Java, risultando in un'attuazione super concentrata dell'algoritmo.
Una funzione in K che implementa gran parte del generatore LL1 del parser secondo la grammatica data:
1. pp:{q:{(x;p3(),y)};r:$[-11=@x;$x;11=@x;q[`N;$*x];10=abs@@x;q[`N;x]
2. ($)~*x;(`P;p3 x 1);(1=#x)&11=@*x;pp[{(1#x;$[2=#x;;,:]1_x)}@*x]
3. (?)~*x;(`Q;pp[x 1]);(*)~*x;(`M;pp[x 1]);(+)~*x;(`MP;pp[x 1]);(!)~*x;(`Y;p3 x 1)
4. (2=#x)&(@x 1)in 100 101 107 7 -7h;($[(@x 1)in 100 101 107h;`Ff;`Fi];p3 x 1;pp[*x])
5. (|)~*x;`S,(pp'1_x);2=#x;`C,{@[@[x;-1+#x;{x,")"}];0;"(",]}({$[".s.C"~4#x;6_-2_x;x]}'pp'x);'`pp];
6. $[@r;r;($[1<#r;".s.";""],$*r),$[1<#r;"[",(";"/1_r),"]";""]]}
Questa filosofia di massima efficienza con il minimo movimento è stata incarnata da Arthur anche in KDB+, apparso nel 2003 (ora capisci da dove viene la lettera K nel nome) ed è niente meno che un interprete della quarta versione del linguaggio K. Su K è stata aggiunta una versione più user-friendly chiamata Q. In Q è stata inoltre integrata la supporto per un dialetto SQL specifico — QSQL, e nell'interprete è stata aggiunta la gestione delle tabelle come tipo di dato sistemico, strumenti per lavorare con le tabelle in memoria e su disco ecc.
Pertanto, dal punto di vista dell'utente, KDB+ è semplicemente un interprete del linguaggio Q con supporto per tabelle e espressioni simili a SQL nello stile di LINQ di C#. Questa è la principale differenza tra KDB+ e altri database, nonché il suo principale vantaggio competitivo, spesso trascurato. Non è un database + un linguaggio di supporto limitato, ma un linguaggio di programmazione potente e completo + supporto integrato per le funzioni del database. Questa differenza giocherà un ruolo determinante nel delineare tutti i vantaggi di KDB+. Per esempio…
Dimension
Con gli standard moderni, KDB+ ha una dimensione semplicemente microscopica. È letteralmente un singolo file eseguibile di meno di un megabyte e un piccolo file di testo con alcune funzioni di sistema. In realtà, meno di un megabyte, e per questo software le aziende pagano decine di migliaia di dollari all'anno per un processore su un server.
- Questa dimensione consente a KDB+ di adattarsi perfettamente a qualsiasi hardware, dal microcomputer Pi a server con terabyte di memoria. Questo non influisce affatto sulla funzionalità; anzi, Q si avvia istantaneamente, permettendo di utilizzarlo anche come linguaggio di scripting.
- Con questa dimensione, l'interprete Q si adatta completamente nella cache del processore, accelerando l'esecuzione dei programmi.
- Con questa dimensione del file eseguibile, il processo Q occupa uno spazio insignificante nella memoria, consentendo di avviarlo in centinaia. Inoltre, se necessario, Q può operare con decine o centinaia di gigabyte di memoria all'interno di un singolo processo.
Versatilità
Q è perfetto per una vasta gamma di compiti. Il processo Q può fungere da database storico e offrire rapido accesso a terabyte di informazioni. Ad esempio, abbiamo decine di database storici, alcuni dei quali contengono un'intera giornata di dati non compressi che occupano oltre 100 gigabyte. Tuttavia, con limiti ragionevoli, la query al database viene eseguita in decine o centinaia di millisecondi. In generale, per le richieste degli utenti, abbiamo un timeout universale di 30 secondi, che si attiva molto raramente.
Con la stessa facilità, Q può fungere da database in-memory. L'aggiunta di nuovi dati alle tabelle in memoria avviene così rapidamente che il fattore limitante sono le richieste degli utenti. I dati nelle tabelle sono memorizzati per colonne, il che significa che qualsiasi operazione su una colonna utilizzerà al massimo la cache del processore. Inoltre, in KX hanno cercato di implementare tutte le operazioni di base, come quelle aritmetiche, attraverso istruzioni vettoriali del processore, massimizzandone la velocità. Q può anche svolgere compiti non tipici dei database — ad esempio, elaborare dati in streaming e calcolare in 'tempo reale' (con un ritardo da decine di millisecondi a qualche secondo a seconda del compito) varie funzioni di aggregazione per strumenti finanziari su diversi intervalli di tempo o costruire un modello di influsso di una transazione sul mercato e profilare quasi immediatamente dopo la sua esecuzione. In tali compiti, il principale ritardo temporale è spesso dovuto non a Q, ma alla necessità di sincronizzare i dati da diverse fonti. L'elevata velocità è ottenuta grazie al fatto che i dati e le funzioni che li elaborano si trovano nello stesso processo, e l'elaborazione si riduce all'esecuzione di diverse espressioni QSQL e join, che non vengono interpretati, ma eseguiti in codice binario.
Finalmente, su Q può gestire anche qualsiasi processo di servizio. Ad esempio, i processi di Gateway che distribuiscono automaticamente le richieste degli utenti tra i database e i server giusti. Il programmatore ha completa libertà di implementare qualsiasi algoritmo per il bilanciamento, la prioritizzazione, la resilienza, i diritti di accesso, le quote e praticamente qualsiasi altra cosa gli venga in mente. Il problema principale è che dovrà implementare tutto questo da solo.
Come esempio, elencherò quali tipi di processi abbiamo. Tutti sono attivamente utilizzati e lavorano insieme, unendo decine di vari database, elaborando dati da molteplici fonti e servendo centinaia di utenti e applicazioni.
- Connettori (feedhandler) per le fonti di dati. Questi processi utilizzano in genere librerie esterne che vengono caricate in Q. L'interfaccia C in Q è estremamente semplice e consente di creare facilmente funzioni proxy per qualsiasi libreria C/C++. Q è sufficientemente veloce da gestire, ad esempio, il flusso di messaggi FIX da tutte le borse europee contemporaneamente.
- Distribuitori di dati (tickerplant), che fungono da intermediari tra i connettori e i consumatori. Allo stesso tempo, registrano i dati in ingresso in un apposito log binario, garantendo stabilità per i consumatori contro la perdita di connessione o i riavvii.
- Database in-memory (rdb). Questi database offrono l'accesso più rapido ai dati freschi, memorizzandoli in memoria. Di solito, accumulano i dati in tabelle durante il giorno e li azzerano di notte.
- Database persistenti (pdb). Questi database garantiscono la conservazione dei dati del giorno corrente in una base storica. Di norma, a differenza degli rdb, non memorizzano dati in memoria, ma utilizzano una cache speciale su disco durante il giorno e copiano i dati a mezzanotte nella base storica.
- Basi storiche (hdb). Queste basi forniscono accesso ai dati dei giorni, mesi e anni precedenti. La loro dimensione (in giorni) è limitata solo dalle dimensioni dei dischi rigidi. I dati possono essere ubicati ovunque, inclusi diversi dischi per accelerare l'accesso. È possibile comprimere i dati utilizzando vari algoritmi a scelta. La struttura della base è ben documentata e semplice, i dati sono archiviati in file ordinari in modo sequenziale, consentendo di elaborarli anche tramite gli strumenti del sistema operativo.
- Basi con informazioni aggregate. Conservano varie aggregazioni, generalmente raggruppate per nome dello strumento e intervallo di tempo. Le basi in-memory aggiornano il loro stato ad ogni messaggio in arrivo, mentre quelle storiche conservano dati pre-calcolati per velocizzare l'accesso ai dati storici.
- Infine, gateway processi, che supportano applicazioni e utenti. Q permette di implementare un'elaborazione completamente asincrona dei messaggi in entrata, la loro distribuzione tra i database, il controllo dei diritti di accesso, e così via. È importante notare che i messaggi non sono limitati e spesso non sono espressioni SQL, come avviene in altri database. Nella maggior parte dei casi, l'espressione SQL è nascosta in una funzione speciale e viene costruita in base ai parametri richiesti dall'utente: si effettua una conversione dell'orario, filtraggio e normalizzazione dei dati (ad esempio, il prezzo delle azioni viene aggiustato in caso di pagamento di dividendi), ecc.
Architettura tipica per un tipo di dati:

Velocità
Sebbene Q sia un linguaggio interpretato, è anche un linguaggio vettoriale. Ciò significa che molte funzioni incorporate, in particolare quelle aritmetiche, accettano argomenti di qualsiasi forma: numeri, vettori, matrici, liste. Si prevede che il programmatore implementi il programma come operazioni su array. In un tale linguaggio, se sommi due vettori di un milione di elementi, non ha importanza che il linguaggio sia interpretato; la somma verrà eseguita tramite una funzione binaria superottimizzata. Poiché gran parte del tempo nei programmi Q è dedicata alle operazioni su tabelle che utilizzano queste funzioni vettorializzate di base, si ottiene una velocità di esecuzione piuttosto elevata, consentendo di elaborare enormi volumi di dati anche in un singolo processo. Questo è simile alle librerie matematiche in Python: sebbene Python stesso sia un linguaggio piuttosto lento, ha molte ottime librerie come numpy, che permettono di elaborare dati numerici alla velocità di un linguaggio compilato (tra l'altro, numpy è ideologicamente affine a Q).
Inoltre, KX ha prestato particolare attenzione alla progettazione delle tabelle e all'ottimizzazione del loro utilizzo. In primo luogo, supporta diversi tipi di indici, che sono supportati da funzioni integrate e possono essere applicati non solo alle colonne delle tabelle, ma anche a qualsiasi vettore: raggruppamento, ordinamento, attributo di unicità e un raggruppamento speciale per le basi storiche. Gli indici vengono applicati in modo semplice e si aggiornano automaticamente quando vengono aggiunti elementi in una colonna/vettore. Gli indici possono essere applicati con successo alle colonne delle tabelle sia in memoria che su disco. Durante l'esecuzione di una query QSQL, gli indici vengono utilizzati automaticamente, se possibile. In secondo luogo, il lavoro con i dati storici avviene tramite un meccanismo di mappatura dei file OS (memory map). Le tabelle di grandi dimensioni non vengono mai caricate interamente in memoria; al contrario, le colonne necessarie vengono visualizzate direttamente in memoria e solo la parte di esse (gli indici aiutano in questo) necessaria viene realmente caricata. Per il programmatore, non c'è differenza se i dati siano in memoria o meno, il meccanismo di lavoro con mmap è completamente nascosto nel cuore di Q.
KDB+ è un database non relazionale, in cui le tabelle possono contenere dati arbitrari; l'ordine delle righe nella tabella non cambia all'aggiunta di nuovi elementi e può e deve essere utilizzato nella scrittura delle query. Questa caratteristica è particolarmente necessaria per lavorare con serie temporali (dati da borse, telemetria, log degli eventi), perché se i dati sono ordinati per tempo, non è necessario applicare alcun trucco SQL per trovare nella tabella la prima o l'ultima riga per data o N righe, determinare quale riga segue la N-esima riga, ecc. Gli join delle tabelle sono ulteriormente semplificati; ad esempio, trovare l'ultima quotazione per 16.000 transazioni di VOD.L (Vodafone) in una tabella di 500 milioni di elementi richiede circa un secondo su disco e una decina di millisecondi in memoria.
Un esempio di join temporale: la tabella quote viene caricata in memoria, quindi non è necessario specificare VOD.L in where; si utilizza implicitamente l'indice sulla colonna sym e il fatto che i dati siano ordinati per tempo. Quasi tutti gli join in Q sono funzioni comuni e non fanno parte dell'espressione select:
1. aj[`sym`time;select from trade where date=2019.03.26, sym=`VOD.L;select from quote where date=2019.03.26]
Infine, è importante sottolineare che gli ingegneri di KX, a partire da Arthur Whitney, sono realmente ossessionati dall'efficienza e si dedicano a massimizzare le funzionalità standard di Q e ottimizzare i modelli di utilizzo più frequenti.
Risultato
KDB+ è particolarmente apprezzata dalle aziende grazie alla sua straordinaria versatilità: funge egregiamente sia da database in-memory che da archivio per terabyte di dati storici, oltre a essere una piattaforma per l'analisi dei dati. Il fatto che l'elaborazione dei dati avvenga direttamente all'interno del database consente di ottenere elevata velocità operativa e risparmio di risorse. Un linguaggio di programmazione completo, integrato con le funzioni del database, consente di gestire sull'unica piattaforma l'intero stack dei processi necessari, dalla raccolta dei dati alla gestione delle richieste degli utenti.
Ulteriori informazioni
Svantaggi
Uno svantaggio significativo di KDB+/Q è l'alta barriera d'ingresso. Il linguaggio ha una sintassi insolita, alcune funzioni sono molto sovraccariche (ad esempio, value ha circa 11 varianti d'uso). La cosa più importante è che richiede un approccio radicalmente diverso alla scrittura di programmi. In un linguaggio vettoriale, bisogna sempre pensare in termini di trasformazioni di array, implementando tutti i cicli attraverso varie funzioni map/reduce (che in Q sono chiamate avverbi), e non cercare mai di risparmiare sostituendo operazioni vettoriali con operazioni atomiche. Ad esempio, per trovare l'indice dell'N-esimo ingresso di un elemento in un array, si deve scrivere:
1. (where element=vector)[N]
anche se questo sembra terribilmente inefficiente rispetto a C/Java (= crea un vettore booleano, where restituisce gli indici degli elementi veri in esso). Ma questa scrittura rende più chiaro il significato dell'espressione e utilizzare operazioni vettoriali veloci invece di quelle atomiche lente. La differenza concettuale tra un linguaggio vettoriale e gli altri è paragonabile alla differenza tra approcci imperativi e funzionali alla programmazione, e bisogna essere pronti ad affrontarlo.
Alcuni utenti possono essere insoddisfatti di QSQL. Infatti, somiglia a SQL, ma in realtà è solo un interprete di espressioni simili a SQL, che non supporta l'ottimizzazione delle query. L'utente deve scrivere query ottimali da solo, utilizzando il Q, cosa a cui molti non sono pronti. D'altra parte, è sempre possibile scrivere manualmente una query ottimale, anziché affidarsi a un ottimizzatore a scatola nera.
Un vantaggio è che il libro su Q — Q For Mortals è disponibile gratuitamente su , dove sono raccolti anche molti altri materiali utili.
Un altro grande svantaggio è il costo della licenza. Si tratta di decine di migliaia di dollari all'anno per un singolo CPU. Solo le grandi aziende possono permettersi tali spese. Recentemente, KX ha reso la politica di licenza più flessibile e offre la possibilità di pagare solo per il tempo di utilizzo o noleggiare KDB+ nelle nuvole di Google e Amazon. Inoltre, KX offre per il download (versione a 32 bit o 64 bit su richiesta).
Concorrenti
Esistono molte basi di dati specializzate costruite su principi simili: colonne, in-memory, orientate verso volumi di dati molto grandi. Il problema è che queste sono precisamente basi di dati specializzate. Un esempio emblematico è Clickhouse. Questo database ha un principio di archiviazione dei dati su disco e costruzione dell'indice molto simile a KDB+, e alcune query vengono eseguite più velocemente di KDB+, anche se non in modo significativo. Tuttavia, anche come database, Clickhouse è più specializzato rispetto a KDB+: analisi web contro serie temporali arbitrarie (questa distinzione è molto importante: per esempio, in Clickhouse non è possibile utilizzare l'ordinamento dei record). Ma, cosa più importante, Clickhouse non ha la versatilità di KDB+, un linguaggio che consente di elaborare i dati direttamente nel database, senza doverli caricare preventivamente in un'applicazione separata, costruire espressioni SQL arbitrarie, applicare funzioni arbitrarie in una query, creare processi non legati all'esecuzione delle funzioni di un database storico. Pertanto, è difficile confrontare KDB+ con altri database: possono essere migliori in scenari d'uso specifici o semplicemente più performanti quando si tratta di compiti di database classici. Tuttavia, non conoscono uno strumento altrettanto efficace e versatile per elaborare dati temporali.
Integrazione con Python
Per semplificare l'utilizzo di KDB+ per coloro che non sono familiari con la tecnologia, KX ha creato librerie per un'integrazione stretta con Python all'interno di un unico processo. È possibile chiamare qualsiasi funzione Python da Q e viceversa: chiamare qualsiasi funzione Q da Python (in particolare le espressioni QSQL). Le librerie effettuano la conversione dei dati da un formato all'altro quando necessario (per efficienza, non sempre). Alla fine, Q e Python esistono in un simbiotico tale che i confini tra i due si sfumano. Di conseguenza, lo sviluppatore ha accesso completo a innumerevoli utili librerie Python e, dall'altra parte, ottiene un database integrato in Python veloce per lavorare con grandi quantità di dati, particolarmente vantaggioso per chi si occupa di machine learning o modellizzazione.
Lavorare con Q in Python:
1. >>> q()
2. q)trade:([]date:();sym:();qty:())
3. q)
4. >>> q.insert('trade', (date(2006,10,6), 'IBM', 200))
5. k(',0')
6. >>> q.insert('trade', (date(2006,10,6), 'MSFT', 100))
7. k(',1')
Link
Sito aziendale —
Sito per sviluppatori —
Libro Q For Mortals (in inglese) —
Articoli sugli usi di KDB+/Q da parte dei dipendenti di kx —
Fonte: habr.com
