Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Poiché ClickHouse è un sistema specializzato, è importante considerare le peculiarità della sua architettura durante il suo utilizzo. In questo rapporto, Aleksey parlerà di esempi di errori tipici nell'uso di ClickHouse, che possono portare a performance inefficaci. Attraverso esempi pratici, verrà mostrato come la scelta di uno schema di elaborazione dei dati piuttosto che un altro possa influenzare notevolmente le prestazioni.

Ciao a tutti! Mi chiamo Aleksey e lavoro con ClickHouse.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Innanzitutto, sono felice di dirvi che oggi non vi parlerò di cosa sia ClickHouse. A dire il vero, mi sono stancato di farlo. Ogni volta spiego cosa sia. E probabilmente tutti ormai lo sanno.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Invece, parlerò di quali sono le possibili insidie, cioè come è possibile utilizzare ClickHouse in modo errato. In realtà, non c'è motivo di preoccuparsi, perché stiamo sviluppando ClickHouse come un sistema semplice, comodo, che funziona out-of-the-box. Installato e via, nessun problema.

Tuttavia, è comunque necessario considerare che questo è un sistema specializzato e si può facilmente imbattersi in uno scenario d'uso insolito che porterà questo sistema fuori dalla sua zona di comfort.

Quindi, quali sono le insidie? Principalmente parlerò di cose ovvie. Per tutti è tutto chiaro, tutti capiscono e possono essere contenti di essere così intelligenti, e chi non capisce, scoprirà qualcosa di nuovo.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Il primo esempio molto semplice, che purtroppo si incontra frequentemente, è un grande numero di inserti con piccoli batch, cioè un gran numero di inserti piccoli.

Se consideriamo come ClickHouse esegue un inserimento, potete inviare un flusso di dati fino a un terabyte in una sola richiesta. Non è un problema.

E diamo un'occhiata a quale sarà la prestazione tipica. Ad esempio, abbiamo una tabella con dati di Yandex.Metrica. Hit. 105 colonne diverse. 700 byte in forma non compressa. E inseriremo in modo corretto batch di un milione di righe.

Inserendo nella tabella MergeTree, otteniamo mezzo milione di righe al secondo. Ottimo. Nella tabella replicata, sarà leggermente inferiore, circa 400.000 righe al secondo.

E se attiviamo l'inserimento a maggioranza, otteniamo qualcosa di meno, ma comunque una prestazione decente, 250.000 righe al secondo. L'inserimento a maggioranza è una funzionalità non documentata in ClickHouse*.

* al 2020, già documentata.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Cosa succede se si fa male? Inseriamo una riga alla volta nella tabella MergeTree e otteniamo 59 righe al secondo. Questo è 10.000 volte più lento. Nel ReplicatedMergeTree – 6 righe al secondo. E se si attiva anche il quorum, otteniamo 2 righe al secondo. A mio parere, è un autentico disastro. Come si può rallentare in questo modo? Ho persino una maglietta con scritto che ClickHouse non deve rallentare. Tuttavia, succede comunque a volte.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

In realtà, è la nostra mancanza. Potremmo facilmente fare in modo che tutto funzioni correttamente, ma non lo abbiamo fatto. E non l'abbiamo fatto perché per il nostro scenario non era necessario. Avevamo già dei batch. I batch arrivavano da noi senza problemi. Inseriamo e tutto funziona normalmente. Ma, ovviamente, possono esserci vari scenari. Ad esempio, quando hai un sacco di server dove vengono generati i dati. E inseriscono i dati non così frequentemente, ma ci sono comunque inserimenti frequenti. E bisogna in qualche modo evitarlo.

Da un punto di vista tecnico, la questione è che quando si esegue un inserimento in ClickHouse, i dati non finiscono in alcun memtable. Non abbiamo nemmeno un vero log structure MergeTree, ma solo un MergeTree, perché non ci sono log né memTable. Scriviamo direttamente i dati nel file system, già distribuiti in colonne. E se hai 100 colonne, dovrai scrivere più di 200 file in una directory separata. È tutto abbastanza ingombrante.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

E sorge la domanda: «Come si fa correttamente?», se abbiamo questa situazione dove dobbiamo comunque scrivere i dati in ClickHouse.

Metodo 1. Questo è il modo più semplice. Usare qualche coda distribuita. Ad esempio, Kafka. Semplicemente estrai i dati da Kafka, batch ogni secondo. E tutto andrà bene, scrivi e tutto funzionerà normalmente.

Gli svantaggi sono che Kafka è un'altra pesante sistema distribuito. Posso capire se in azienda hai già Kafka. È utile, è comodo. Ma se non ce l'hai, vale la pena riflettere tre volte prima di portare un altro sistema distribuito nel tuo progetto. Pertanto, è meglio considerare alternative.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Metodo 2. Ecco un'alternativa old school, molto semplice. Hai un server che genera i tuoi log. Il server registra semplicemente i log in un file. E una volta al secondo, ad esempio, rinominiamo questo file e ne apriamo uno nuovo. Un'apposita script, tramite cron o un daemon, prende il file più vecchio e lo registra in ClickHouse. Se registri i log ogni secondo, va tutto alla grande.

Ma il difetto di questo metodo è che se il tuo server, dove vengono generati i log, scompare, anche i dati andranno persi.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Metodo 3. C'è un altro modo interessante, senza file temporanei. Ad esempio, hai uno script di pubblicità o un altro daemon interessante che genera dati. Puoi accumulare un insieme di dati direttamente in memoria, nel buffer. E quando trascorre un periodo di tempo sufficiente, tieni da parte quel buffer, crei uno nuovo, e in un thread separato inserisci ciò che è già accumulato in ClickHouse.

D'altra parte, i dati scompaiono anche con un kill -9. Se il tuo server va giù, perderai quei dati. Inoltre, c'è un problema: se non riesci a registrare nel database, i dati si accumuleranno in memoria. E o finisci la memoria, o semplicemente perdi i dati.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Metodo 4. Un altro modo interessante. Hai un qualche processo server. Può inviare dati a ClickHouse direttamente, ma farlo in una sola connessione. Ad esempio, invia una richiesta http con transfer-encoding: chunked con un'operazione di insert. E genera i chunk non troppo raramente; puoi inviare ogni riga, anche se ci sarà un overhead per il framing di questi dati.

Tuttavia, in questo caso, i dati verranno inviati a ClickHouse immediatamente. E ClickHouse li bufferizzerà da solo.

Ma si presentano anche dei problemi. Adesso perderai dati, inclusi quelli quando il tuo processo si ferma e, se il processo ClickHouse si interrompe, perché sarà un insert non completato. In ClickHouse, gli insert sono atomici fino a un certo limite specificato in termini di righe. In linea di principio, è un modo interessante. Può essere utilizzato anche questo.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Metodo 5. Ecco un altro modo interessante. È un server sviluppato dalla community per il batching dei dati. Non l'ho esaminato personalmente, quindi non posso garantire nulla. Tuttavia, anche per ClickHouse non vengono fornite garanzie. Anche questo è open source, ma d'altra parte, potresti essere abituato a uno standard di qualità che cerchiamo di garantire. Per questa cosa, non so, vai su GitHub, guarda il codice. Potrebbe essere stato scritto qualcosa di decente.

* a partire dal 2020, occorre anche aggiungere da considerare KittenHouse.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Metodo 6. Un altro metodo è l'uso delle tabelle Buffer. I vantaggi di questo metodo sono che è molto semplice iniziare a utilizzarlo. Crei una tabella Buffer e ci inserisci dati.

Ma lo svantaggio è che il problema non viene risolto completamente. Se, durante l'inserimento con MergeTree, devi raggruppare i dati a un batch al secondo, con l'inserimento nella tabella buffer, devi raggruppare almeno fino a qualche migliaio al secondo. Se ci sono più di 10.000 al secondo, andrà comunque male. E se inserisci a batch, hai visto che arrivano a centinaia di migliaia di righe al secondo. E questo già con dati piuttosto pesanti.

E anche le tabelle buffer non hanno un log. E se c'è qualcosa che non va con il tuo server, i dati andranno persi.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

E come bonus, recentemente in ClickHouse è stata introdotta la possibilità di prelevare dati da Kafka. C'è un motore di tabelle – Kafka. Devi solo crearlo. E su di esso puoi montare viste materializzate. In questo caso, preleverà automaticamente i dati da Kafka e li inserirà nelle tabelle che desideri.

E ciò che è particolarmente rassicurante in questa funzionalità è che non l'abbiamo realizzata noi. È una funzione della community. E quando dico "funzione della community", lo dico senza alcun disprezzo. Abbiamo letto il codice, abbiamo fatto la revisione, dovrebbe funzionare normalmente.

* a partire dal 2020, è stata introdotta un'analoga supporto per RabbitMQ.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Cosa altro potrebbe essere scomodo o inaspettato durante l'inserimento dei dati? Se fai una richiesta insert values e nel values scrivi alcune espressioni calcolate. Ad esempio, now() è anch'essa un'espressione calcolata. E in questo caso ClickHouse è costretto a eseguire l'interprete di queste espressioni per ogni riga, e le prestazioni caleranno drasticamente. È meglio evitarlo.

* Al momento, il problema è completamente risolto, non ci sono più regressioni nelle prestazioni quando si utilizzano espressioni in VALUES.

Un altro esempio di quando potrebbero esserci alcuni problemi è quando in un unico batch i dati appartengono a più partizioni. Per impostazione predefinita, in ClickHouse le partizioni sono mensili. Se si inserisce un batch di un milione di righe, e i dati coprono alcuni anni, si avrà a che fare con diverse decine di partizioni. Questo equivale ad avere batch di dimensioni diverse, poiché all'interno vengono sempre prima suddivisi per partizioni.

* Recentemente, in ClickHouse, è stata aggiunta in modalità sperimentale la supporto per il formato compatto dei pezzi e dei pezzi in memoria con write-ahead log, il che risolve quasi completamente il problema.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Ora consideriamo il secondo tipo di problema: la tipizzazione dei dati.

La tipizzazione dei dati può essere rigorosa oppure stringa. Stringa significa che hai semplicemente dichiarato che tutti i campi sono di tipo string. Questo non va bene. Non si dovrebbe fare così.

Vediamo come fare correttamente nei casi in cui vogliamo specificare che un campo è una stringa, e lasciare che ClickHouse si occupi del resto, senza preoccuparci troppo. Tuttavia, vale la pena fare alcuni sforzi.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Ad esempio, abbiamo un indirizzo IP. In un caso lo abbiamo salvato come stringa, ad esempio 192.168.1.1. In un altro caso, sarà un numero di tipo UInt32*. 32 bit sono sufficienti per un indirizzo IPv4.

Innanzitutto, sorprendentemente, i dati si comprimono in modo simile. Ci sarà ovviamente qualche differenza, ma non così significativa. Pertanto, non ci sono particolari problemi con l'ingresso/uscita su disco.

Tuttavia, c'è una notevole differenza nel tempo di CPU e nel tempo di esecuzione della query.

Calcoliamo il numero di indirizzi IP unici, se sono memorizzati come numeri. Risulta 137 milioni di righe al secondo. Se lo stesso viene fatto come stringhe, si arriva a 37 milioni di righe al secondo. Non so perché ci sia questa corrispondenza. Io stesso ho eseguito queste query. Tuttavia, risulta essere circa 4 volte più lento.

E se si calcola la differenza nello spazio su disco, c'è anche una differenza. E questa differenza è di circa un quarto, poiché ci sono molti indirizzi IP unici. Se ci fossero stringhe con un numero ridotto di valori diversi, si comprimeranno facilmente in modo simile a un dizionario occupando circa lo stesso spazio.

E la differenza di quattro volte nel tempo di viaggio non è da prendere alla leggera. Forse a te non importa, ma quando vedo una tale differenza, mi sento triste.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Consideriamo diversi casi.

1. Un caso è quando hai pochi valori unici. In questo caso utilizziamo una pratica semplice che probabilmente conosci e puoi usare per qualsiasi DBMS. Questo ha senso non solo per ClickHouse. Semplicemente registri in un database identificatori numerici. E la conversione in stringhe e viceversa può avvenire sul lato della tua applicazione.

Ecco, ad esempio, hai una regione. E provi a salvarla come stringa. E ci sarà scritto: Mosca e MO. E quando vedo che c'è scritto 'Mosca', va ancora bene, ma quando c'è anche MO, diventa davvero triste. Quanti byte sono necessari.

Invece, scriviamo semplicemente il numero Ulnt32 e 250. Abbiamo 250 in Yandex, mentre per te potrebbe essere diverso. Dico per sicurezza che in ClickHouse c'è una funzionalità integrata per lavorare con le geodatabase. Basta registrare un elenco di regioni, inclusa la gerarchia, cioè ci sarà Mosca, MO e tutto ciò di cui hai bisogno. E puoi effettuare la conversione a livello di query.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

La seconda opzione è simile, ma già con supporto interno in ClickHouse. Questo è il tipo di dato Enum. Scrivi semplicemente all'interno di Enum tutti i valori di cui hai bisogno. Ad esempio, il tipo di dispositivo e scrivi: desktop, mobile, tablet, televisore. Solo 4 opzioni.

Il problema è che devi periodicamente fare alter. Hai aggiunto solo un'opzione. Fai alter table. In realtà, alter table in ClickHouse è gratuito. Soprattutto è gratuito per Enum, poiché i dati su disco non cambiano. Tuttavia, l'alter provoca un blocco sulla tabella e deve attendere che tutte le selezioni siano completate. Solo dopo che questi sono stati completati, l'alter avrà successo, quindi ci sono comunque alcuni inconvenienti.

* nelle versioni più recenti di ClickHouse, ALTER è completamente non bloccante.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Un'altra opzione abbastanza unica per ClickHouse è l'integrazione di dizionari esterni. Puoi scrivere in ClickHouse numeri e mantenere i tuoi dizionari in qualsiasi sistema ti sia più comodo. Ad esempio, puoi utilizzare: MySQL, Mongo, Postgres. Puoi anche sviluppare un tuo microservizio che fornisca questi dati tramite http. E a livello di ClickHouse scrivi una funzione che convertirà questi dati da numeri a stringhe.

Questo è un modo specializzato, ma molto efficace per eseguire un join con una tabella esterna. Esistono due varianti. In una variante, questi dati saranno completamente memorizzati nella cache, completamente presenti in memoria e aggiornati con una certa periodicità. Nell'altra variante, se i dati non possono essere memorizzati in memoria, sarà possibile memorizzarli parzialmente nella cache.

Ecco un esempio. C'è Yandex.Direct, dove ci sono campagne pubblicitarie e banner. Le campagne pubblicitarie sono probabilmente circa dieci milioni e possono essere memorizzate in memoria. I banner, invece, sono miliardi e non possono essere memorizzati. Utilizziamo un dizionario memorizzabile nella cache da MySQL.

L'unico problema è che il dizionario memorizzabile nella cache funzionerà correttamente solo se il tasso di hit è vicino al 100%. Se è inferiore, durante l'elaborazione delle richieste per ogni gruppo di dati sarà necessario prelevare effettivamente le chiavi mancanti e recuperare i dati da MySQL. Per quanto riguarda ClickHouse, posso assicurare che non rallenta, ma non parlerò di altri sistemi.

Inoltre, come bonus, i dizionari sono un modo molto semplice per aggiornare i dati in ClickHouse retroattivamente. Cioè, se avevi un report su campagne pubblicitarie, e un utente cambia semplicemente la campagna pubblicitaria, tutti i dati vecchi e i report verranno aggiornati con queste nuove informazioni. Se scrivi righe direttamente nella tabella, sarà impossibile aggiornarl.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Un altro modo è quando non sai da dove prendere gli identificatori per le tue righe. Puoi semplicemente creare un hash. Il modo più semplice è utilizzare un hash a 64 bit.

L'unico problema è che se l'hash è a 64 bit, le collisioni si verificheranno quasi sicuramente. Perché se ci sono miliardi di righe, la probabilità diventa significativa.

Non sarebbe molto opportuno fare così per creare hash dei nomi delle campagne pubblicitarie. Se i nomi delle campagne pubblicitarie vengono confusi tra diverse aziende, ci sarà qualcosa di incomprensibile.

E c'è un trucco semplice. Certo, non è molto adatto per dati importanti, ma se hai qualcosa di meno serio, aggiungi semplicemente l'identificativo del cliente alla chiave del dizionario. E allora avrai delle collisioni, ma solo all'interno di un singolo cliente. Questo metodo è utilizzato per la mappa dei link in Yandex.Metrica. Abbiamo URL lì, conserviamo gli hash. E sappiamo che le collisioni ci sono, ovviamente. Ma quando la pagina viene visualizzata, la probabilità che su una singola pagina un utente abbia degli URL collisi e che venga notato è da trascurare.

Come bonus – per molte operazioni bastano solo gli hash e le stringhe non devono essere archiviate da nessuna parte.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Un altro esempio è se le stringhe sono corte, ad esempio, i domini dei siti. Possono essere memorizzati così come sono. Oppure, ad esempio, la lingua del browser ru – 2 byte. Certo, mi dispiace per i byte, ma non preoccuparti, 2 byte non sono un problema. Per favore, memorizza così com'è, non farti problemi.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Un'altra situazione è quando ci sono molte stringhe e in esse tante sono uniche, e il numero potenziale non è limitato. Un esempio tipico sono le frasi di ricerca o gli URL. Le frasi di ricerca, anche a causa di errori di battitura. Vediamo quante frasi di ricerca uniche ci sono in un giorno. Risulta che quasi metà di tutti gli eventi sono queste. E in questo caso, potresti pensare che sia necessario normalizzare i dati, calcolare gli identificatori, metterli in una tabella separata. Ma non è necessario fare così. Semplicemente memorizza queste stringhe così come sono.

Meglio – non inventare nulla, perché se si memorizza separatamente, sarà necessario fare un join. E questo join – nel migliore dei casi un accesso casuale in memoria, se ci sta ancora in memoria. Se non ci sta, ci saranno problemi.

E se i dati sono memorizzati in in place, vengono semplicemente letti nell'ordine necessario dal file system e tutto va bene.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Se hai URL o qualche altra stringa lunga e complessa, vale la pena considerare che puoi calcolare un'estrazione in anticipo e registrarla in una colonna separata.

Per gli URL, ad esempio, puoi memorizzare separatamente il dominio. E se hai davvero bisogno del dominio, basta utilizzare questa colonna, mentre gli URL rimarranno qui, e non dovrai nemmeno toccarli.

Vediamo che differenza c'è. In ClickHouse c'è una funzione specializzata che calcola il dominio. È molto veloce e l'abbiamo ottimizzata. E, a dirla tutta, non corrisponde nemmeno all'RFC, eppure calcola tutto ciò di cui abbiamo bisogno.

In un caso, estrarremo semplicemente gli URL e calcoleremo il dominio. Ci vogliono 166 millisecondi. Se prendiamo un dominio pronto, ci vogliono solo 67 millisecondi, cioè quasi tre volte più veloce. E questo non è dovuto a calcoli, ma al fatto che leggiamo meno dati.

Per qualche motivo, un'interrogazione che è più lenta risulta avere una maggiore velocità in gigabyte al secondo. Perché legge più gigabyte. Questi sono dati completamente superflui. L'interrogazione sembra funzionare più velocemente, ma richiede più tempo per essere eseguita.

Se guardiamo la dimensione dei dati su disco, scopriamo che l'URL occupa 126 megabyte, mentre il dominio solo 5 megabyte. Questo è quindi 25 volte meno. Tuttavia, l'interrogazione viene eseguita solo 4 volte più velocemente. Questo è perché i dati sono 'caldi'. Se fossero stati 'freddi', sarebbe sicuramente stato 25 volte più veloce a causa dell'input/output del disco.

A proposito, se valutiamo quanto il dominio sia più piccolo dell'URL, risulta circa 4 volte. Ma per qualche motivo, i dati su disco occupano 25 volte meno. Perché? A causa della compressione. Sia l'URL che il dominio vengono compressi. Tuttavia, spesso l'URL contiene un sacco di spazzatura.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

E, ovviamente, è fondamentale utilizzare i tipi di dati corretti, progettati specificamente per i valori necessari o che si adattano. Se si tratta di IPv4, memorizzate UInt32*. Se è IPv6, allora FixedString(16), perché un indirizzo IPv6 è lungo 128 bit, cioè memorizzate direttamente in formato binario.

E cosa fare se avete talvolta indirizzi IPv4 e talvolta IPv6? Sì, potete memorizzare entrambi. Una colonna per IPv4, un'altra per IPv6. Certamente, c'è l'opzione di rappresentare l'IPv4 in IPv6. Questo funzionerà comunque, ma se nelle interrogazioni avete spesso bisogno dell'indirizzo IPv4, sarebbe utile metterlo in una colonna separata.

* ora in ClickHouse ci sono tipi di dati separati per IPv4 e IPv6, che memorizzano i dati in modo altrettanto efficiente come i numeri, ma li rappresentano in modo altrettanto conveniente come le stringhe.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

È importante notare che è opportuno pre-elaborare i dati in anticipo. Ad esempio, se ricevi dei log grezzi. E, forse, non dovresti inserirli immediatamente in ClickHouse, anche se è molto allettante non fare nulla e tutto funzionerà. Ma è comunque opportuno eseguire i calcoli che è possibile fare.

Ad esempio, la versione del browser. In un certo dipartimento vicino, a cui non voglio puntare il dito, la versione del browser è memorizzata in questo modo, cioè come stringa: 12.3. E poi, per generare un report, prendono questa stringa, la dividono per un array, e poi per il primo elemento dell'array. È ovvio che tutto si rallenta. Ho chiesto perché fanno così. Mi hanno risposto che non amano l'ottimizzazione prematura. E io non amo la pessimità prematura.

Quindi in questo caso sarebbe più corretto dividere in 4 colonne. Qui non abbiate paura, perché è ClickHouse. ClickHouse è un database a colonne. E più piccole colonne pulite hai, meglio è. Se hai 5 BrowserVersion, crea 5 colonne. Va bene.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Adesso consideriamo cosa fare se hai molte stringhe molto lunghe, array molto lunghi. Non è necessario conservarli affatto in ClickHouse. Invece puoi memorizzare in ClickHouse solo un qualche identificatore. E inserisci queste lunghe stringhe in un'altra sistema.

Ad esempio, in uno dei nostri servizi analitici ci sono alcuni parametri degli eventi. E se gli eventi ricevono molti parametri, salviamo semplicemente i primi 512 che capitano. Perché 512 non è un problema.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

E se non riesci a decidere i tuoi tipi di dato, puoi comunque scrivere i dati in ClickHouse, ma in una tabella temporanea di tipo Log, speciale per i dati temporanei. Dopodiché puoi analizzare quale sia la distribuzione dei valori, cosa c'è e classificare i tipi corretti.

* attualmente in ClickHouse c'è il tipo di dato LowCardinality che consente di memorizzare efficacemente stringhe con minori costi di lavoro.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Adesso consideriamo un altro caso interessante. A volte per le persone tutto funziona in modo strano. Entro e vedo qualcosa del genere. E subito si presenta l'idea che questo sia stato fatto da un amministratore molto esperto e intelligente, con una grande esperienza nell'impostazione di MySQL versione 3.23.

Qui vediamo mille tabelle, ognuna delle quali contiene il resto di una divisione di non si sa cosa per mille.

In linea di principio, rispetto l'esperienza altrui e comprendo anche le sofferenze che possono derivare da questa esperienza.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Le ragioni sono più o meno chiare. Questi sono vecchi stereotipi che possono essersi accumulati lavorando con altri sistemi. Ad esempio, nelle tabelle MyISAM non c'è una chiave primaria cluster. Questo modo di separare i dati potrebbe essere un disperato tentativo di ottenere la stessa funzionalità.

Un'altra ragione è che è difficile eseguire operazioni come alter su grandi tabelle. Tutto rimarrà bloccato. Tuttavia, nelle versioni moderne di MySQL questo problema non è così grave.

Oppure, ad esempio, il micro-sharding, ma di questo parleremo tra poco.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

In ClickHouse non è necessario farlo, perché, innanzitutto, la chiave primaria è cluster, e i dati sono ordinati per chiave primaria.

A volte mi chiedono: «Come cambia le prestazioni delle query di intervallo in ClickHouse in base alle dimensioni della tabella?». Rispondo che non cambiano affatto. Ad esempio, se hai una tabella con un miliardo di righe e leggi un intervallo di un milione di righe. Tutto va bene. Se la tabella ha un trilione di righe e leggi un milione di righe, sarà quasi la stessa cosa.

In secondo luogo, non è necessario usare cose come partizioni manuali. Se accedi e guardi quello che c'è nel filesystem, vedrai che la tabella è un elemento piuttosto serio. E ci sono dentro cose come partizioni. Cioè, ClickHouse fa tutto per te e non devi soffrire.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Alter in ClickHouse è gratuito, se si tratta di alter add/drop column.

E non vale la pena creare tabelle piccole, perché se hai una tabella con 10 righe o 10.000 righe, non ha importanza. ClickHouse è un sistema che ottimizza il throughput, non la latenza, quindi elaborare 10 righe non ha senso.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

È giusto utilizzare un'unica grande tabella. Liberati dai vecchi stereotipi, andrà tutto bene.

E come bonus, nell'ultima versione è stata introdotta la possibilità di creare una chiave di partizionamento arbitraria per eseguire varie operazioni di manutenzione su singole partizioni.

Ad esempio, è necessario avere molte piccole tabelle, per esempio quando si ha bisogno di elaborare alcuni dati intermedi, si ricevono dei chunk e si devono effettuare trasformazioni su di essi prima di scriverli nella tabella finale. Per questo caso esiste un meraviglioso motore di tabella – StripeLog. È simile a TinyLog, ma migliore.

* ora in ClickHouse c'è anche la funzione tabellare input.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Un altro antipattern è il microsharding. Ad esempio, hai bisogno di shardare dei dati e hai 5 server, ma domani ci sarà 6 server. E ti chiedi come riequilibrare questi dati. E invece di questo, li suddividi non in 5 shard, ma in 1.000 shard. E poi mappi ciascuno di questi microshard su un server separato. Quindi, ad esempio, avrai 200 ClickHouse su un server. Istanza separate su porte separate o database separati.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Ma in ClickHouse non funziona molto bene. Perché anche un singolo instance di ClickHouse tende a utilizzare tutte le risorse disponibili del server per elaborare una singola richiesta. Cioè, hai un server e lì, ad esempio, 56 core CPU. Esegui una query che impiega un secondo, e questa utilizzerà 56 core. Ma se hai messo 200 ClickHouse su un singolo server, si risulterà che si avvieranno 10.000 thread. In generale, sarà tutto molto male.

Un'altra ragione è che la distribuzione del lavoro tra queste istanze sarà irregolare. Alcune termineranno prima, alcune dopo. Se tutto ciò avvenisse in un'unica istanza, ClickHouse gestirebbe autonomamente come distribuire correttamente i dati tra i thread.

E un'altra ragione è che ci sarà comunicazione interprocesso tramite TCP. I dati dovranno essere serializzati, deserializzati e ci saranno una quantità enorme di microshard. Semplicemente non sarà efficiente.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Un altro antipattern, anche se è difficile chiamarlo tale. È un'elevata quantità di preaggregazione.

In generale, la preaggregazione è benefica. Avevi un miliardo di righe, le hai aggregate e sono diventate 1.000 righe, e ora la query si esegue istantaneamente. Tutto magnifico. Si può fare. A tal fine, in ClickHouse esiste un tipo di tabella speciale, AggregatingMergeTree, che esegue l'aggregazione incrementale durante l'inserimento dei dati.

Tuttavia, ci sono casi in cui pensate che possiamo aggregare i dati in questo modo e poi in quest'altro modo. E in un altro dipartimento vicino, di cui non voglio parlare, utilizzano le tabelle SummingMergeTree per sommare in base alla chiave primaria, e come chiave primaria usano circa 20 colonne. Ho cambiato per precauzione i nomi di alcune colonne per coprire la loro identità, ma circa così è.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

E sorgono tali problemi. Prima di tutto, il volume dei dati non diminuisce in modo significativo. Ad esempio, diminuisce di tre volte. Tre volte sarebbe un buon prezzo per garantirci possibilità illimitate di analisi, che si presentano se i dati non sono aggregati. Se i dati sono aggregati, otteniamo solo misere statistiche anziché analisi.

E cosa c'è di particolarmente fastidioso? Che queste persone del dipartimento vicino vanno e talvolta chiedono di aggiungere un'altra colonna alla chiave primaria. Cioè, abbiamo aggregato i dati in questo modo, e ora vogliamo un po' di più. Ma in ClickHouse non c'è la possibilità di modificare la chiave primaria. Quindi tocca scrivere degli script in C++. E non mi piacciono gli script, anche se sono in C++.

E se guardiamo a cosa è stato creato ClickHouse, i dati non aggregati sono proprio quello scenario per cui è stato concepito. Se usate ClickHouse per dati non aggregati, state facendo tutto correttamente. Se state aggregando, a volte è perdonabile.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Un altro caso interessante sono le query in ciclo infinito. A volte mi connetto a qualche server in produzione e guardo show processlist. E ogni volta scopro che sta succedendo qualcosa di terribile.

Ad esempio, questo. Qui è subito chiaro che si sarebbe potuto eseguire tutto in una sola query. Basta scrivere url in e l'elenco.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Perché avere molte di queste query in ciclo infinito è un problema? Se l'indice non è utilizzato, avrete molti passaggi sugli stessi dati. Ma se l'indice è utilizzato, ad esempio, se avete una chiave primaria in ru e scrivete url = qualcosa. E pensate che verrà letto un solo url dalla tabella, andrà tutto bene. Ma in realtà no. Perché ClickHouse fa tutto in batch.

Quando ha bisogno di leggere un certo intervallo di dati, ne legge un po' di più, perché l'indice in ClickHouse è sparso. Questo indice non permette di trovare una singola riga nella tabella, ma solo un intervallo. E i dati vengono compressi in blocchi. Per leggere una riga, è necessario prendere un intero blocco e decomprimerlo. E se esegui un sacco di richieste, avrai molte sovrapposizioni e un sacco di lavoro verrà eseguito di nuovo e di nuovo.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

E come bonus, si può notare che in ClickHouse non c'è bisogno di avere paura di trasmettere anche megabyte e persino centinaia di megabyte nella sezione IN. Ricordo dalla nostra pratica che se in MySQL trasmettiamo un sacco di valori nella sezione IN, ad esempio, trasmettiamo 100 megabyte di numeri, MySQL consuma 10 gigabyte di memoria e non succede nient'altro, tutto funziona male.

E la seconda cosa è che in ClickHouse, se le tue query usano l'indice, non è mai più lento di una scansione completa, ovvero se deve leggere quasi tutta la tabella, procederà in modo sequenziale e leggerà tutta la tabella. In generale, si regolerà da solo.

Tuttavia, ci sono alcune complessità. Ad esempio, il fatto che l'IN con una sottoquery non utilizza l'indice. Ma questo è un nostro problema e dobbiamo correggerlo. Non c'è nulla di fondamentale qui. Lo sistemeremo.

E un'altra cosa interessante è che se hai una richiesta molto lunga e l'elaborazione delle richieste è distribuita, questa richiesta molto lunga verrà inviata a ogni server senza compressione. Ad esempio, 100 megabyte e 500 server. E, di conseguenza, verranno trasferiti 50 gigabyte sulla rete. Saranno trasferiti e poi verrà eseguita con successo.

* già utilizzato; abbiamo sistemato tutto, come promesso.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

E un caso piuttosto comune è se le richieste arrivano da un'API. Ad esempio, hai creato un tuo servizio. E se il tuo servizio è necessario a qualcuno, hai aperto un'API e già dopo due giorni vedi che succede qualcosa di strano. Tutto è sovraccarico e arrivano alcune terribili richieste che non avrebbero mai dovuto essere.

E qui c'è una sola soluzione. Se hai aperto un'API, dovrai limitarla. Ad esempio, introdurre delle quote. Non ci sono altre soluzioni normali. Altrimenti, scriveranno subito uno script e ci saranno problemi.

E in ClickHouse c'è una funzionalità speciale: il conteggio delle quote. Si può anche fornire la propria chiave di quota. Questo, ad esempio, è un identificatore interno dell'utente. E le quote verranno calcolate in modo indipendente per ognuno di essi.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Ora un'altra cosa interessante. Questa è la replica manuale.

So di molti casi in cui, nonostante ClickHouse abbia un supporto integrato per la replica, le persone replicano ClickHouse manualmente.

Qual è il principio? Avete un pipeline di elaborazione dei dati. E funziona in modo indipendente, ad esempio, in diversi data center. Scrivete gli stessi dati nello stesso modo in ClickHouse. Tuttavia, la pratica dimostra che i dati tendono comunque a divergere a causa di alcune peculiarità nel vostro codice. Spero che nel vostro caso non accada.

E periodicamente dovrete comunque sincronizzare manualmente. Ad esempio, una volta al mese gli amministratori eseguono rsync.

In realtà, è molto più semplice utilizzare la replica integrata di ClickHouse. Ma ci possono essere alcune controindicazioni, perché per fare ciò è necessario utilizzare ZooKeeper. Non dirò niente di male su ZooKeeper, in linea di massima è un sistema funzionante, ma a volte le persone non lo utilizzano a causa della java-fobia, perché ClickHouse è un ottimo sistema scritto in C++ che funziona bene. E ZooKeeper è in java. E in qualche modo non sembra nemmeno utile, ma allora potete usare la replica manuale.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

ClickHouse è un sistema pratico. Tiene conto delle vostre esigenze. Se avete una replica manuale, potete creare una tabella Distribuita che osserva le vostre repliche manuali e gestisce autonomamente il failover tra di esse. E c'è anche un'opzione speciale che permette di evitare fluttuazioni, anche se le vostre repliche divergono sistematicamente.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

In seguito possono sorgere problemi se utilizzate motori di tabella primitiva. ClickHouse è un costruttore che offre un sacco di diversi motori di tabelle. Per tutti i casi seri, come indicato nella documentazione, utilizzate le tabelle della famiglia MergeTree. E tutte le altre sono così, per casi specifici o per test.

Nella tabella MergeTree non è necessario avere una data e un orario specifici. Potete comunque utilizzarla. Se non ci sono data e ora, indicatelo come default – l'anno 2000. Questo funzionerà e non richiederà risorse.

E nella nuova versione del server è anche possibile specificare un partizionamento personalizzato senza chiave di partizione. Sarà la stessa cosa.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

D'altra parte, è possibile utilizzare motori di tabella primitivi. Ad esempio, caricare i dati una sola volta e vedere, provare e cancellare. Puoi utilizzare Log.

Oppure memorizzare piccole quantità per elaborazioni intermedie – questo è StripeLog o TinyLog.

Memory può essere usato se il volume dei dati è piccolo e puoi semplicemente lavorare su qualcosa in RAM.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

ClickHouse non ama molto i dati troppo normalizzati.

Ecco un esempio tipico. Si tratta di un gran numero di URL. Li hai messi in una tabella separata. E poi hai deciso di fare un JOIN con loro, ma questo non funzionerà, in genere, perché ClickHouse supporta solo Hash JOIN. Se la RAM non è sufficiente per un grande numero di dati da unire, il JOIN non sarà possibile*.

Se i dati sono di alta cardinalità, non preoccuparti, conservali in forma denormalizzata, URL direttamente in place nella tabella principale.

* ora in ClickHouse c'è anche merge join e funziona in condizioni in cui i dati intermedi non entrano nella RAM. Ma non è efficiente e rimane valida la raccomandazione.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Ecco un altro paio di esempi, ma ormai ho dei dubbi se siano antipattern o meno.

In ClickHouse c'è un noto difetto. Non supporta gli aggiornamenti*. In un certo senso, questo è anche positivo. Se hai dati importanti, ad esempio, contabilità, non possono essere inviati, perché non ci sono aggiornamenti.

* è stata da tempo aggiunta la supporto per aggiornamenti e cancellazioni in modalità batch.

Ma ci sono alcuni metodi speciali che consentono di fare aggiornamenti in background. Ad esempio, tabelle tipo ReplaceMergeTree. Eseguono aggiornamenti durante le fusioni in background. Puoi forzarlo usando optimize table. Ma non farlo troppo spesso, perché comporterebbe una completa riscrittura della partizione.

I JOIN distribuiti in ClickHouse sono anche poco gestiti dal pianificatore di query.

Male, ma a volte va bene.

Utilizzo di ClickHouse solo per leggere nuovamente i dati tramite select*.

Non consiglierei di usare ClickHouse per calcoli pesanti. Ma non è proprio così, perché ci stiamo già allontanando da questa raccomandazione. E recentemente è stata aggiunta la possibilità di applicare modelli di machine learning in ClickHouse - Catboost. E questo mi preoccupa, perché penso: «Che disastro. Quanti cicli per byte ci sono?!». È davvero un peccato sprecare cicli per byte.

Utilizzo efficace di ClickHouse. Aleksey Milovidov (Yandex)

Ma non temete, installate ClickHouse, andrà tutto bene. In caso di problemi, abbiamo una comunità. A proposito, la comunità siete voi. E se avete qualche problema, potete almeno entrare nella nostra chat, e spero vi aiuteranno.

Domande

Grazie per la presentazione! Dove posso lamentarmi per il crash di ClickHouse?

Potete lamentarvi direttamente a me adesso.

Recentemente ho iniziato a usare ClickHouse. Ho subito fatto crashare l'interfaccia cli.

Sei fortunato.

Poco dopo ho crashato il server con una piccola select.

Hai talento.

Ho aperto un bug su GitHub, ma è stato ignorato.

Vedremo.

Aleksei mi ha ingannato facendomi venire alla presentazione, promettendo di raccontare come comprimete i dati internamente.

Molto semplice.

L'ho capito già ieri. Serve più concretezza.

Non ci sono trucchi terribili. È solo compressione per blocchi. Di default viene usato LZ4, puoi attivare ZSTD*. I blocchi vanno da 64 kilobyte a 1 megabyte.

* ci sono anche codec di compressione specializzati che possono essere utilizzati in catena con altri algoritmi.

Nei blocchi ci sono solo dati grezzi?

Non proprio grezzi. Ci sono degli array. Se hai una colonna numerica, i numeri sono memorizzati consecutivamente in un array.

Chiaro.

Aleksei, l'esempio che avevamo con uniqExact sugli indirizzi IP, cioè, che uniqExact impiega più tempo sui dati testuali che su quelli numerici e così via. E se applicassimo un trucco e facessimo il cast nel momento della lettura? Cioè, sembrava che dicessi che su disco non ci sono grandi differenze. Se leggiamo le stringhe dal disco, facciamo il cast, allora avremo aggregati più rapidi o no? O comunque non guadagneremmo significativamente qui? Mi sembra che tu lo abbia testato, ma per qualche motivo non l'hai indicato nel benchmark.

Penso che sarà più lento rispetto a senza il cast. In questo caso, l'indirizzo IP deve essere estratto dalla stringa. Certo, in ClickHouse anche il parsing degli indirizzi IP è ottimizzato. Abbiamo fatto del nostro meglio, ma lì i numeri sono scritti in forma decimale. Molto scomodo. D'altra parte, la funzione uniqExact lavorerà più lentamente sulle stringhe non solo perché sono stringhe, ma anche perché viene scelta un'altra specializzazione dell'algoritmo. Le stringhe vengono semplicemente elaborate in modo diverso.

E se prendessimo un tipo di dato più primitivo? Ad esempio, abbiamo registrato l'user id, che è nel nostro in, l'abbiamo registrato come stringa e poi l'abbiamo castato, sarà più divertente o no?

Dubbio. Penso che sarà persino più triste, perché analizzare numeri è un problema serio. Mi sembra che questo collega abbia persino tenuto una presentazione su quanto sia difficile analizzare i numeri in forma decimale, o forse no.

Alexey, grazie mille per la presentazione! E grazie mille anche per ClickHouse! Ho una domanda riguardo ai piani. Ci sono previsioni per una funzionalità di aggiornamento dei dizionari non completamente?

Cioè, un riavvio parziale?

Sì, sì. Tipo la possibilità di indicare un campo MySQL, cioè aggiornare dopo, affinché vengano caricati solo quei dati, se il dizionario è molto grande.

Funzionalità molto interessante. E mi sembra che qualcuno l'abbia proposta nella nostra chat. Forse era anche tu.

Non credo di essere io.

Ottimo, ora risultano due richieste. E possiamo iniziare a farlo con calma. Ma voglio avvisarti subito che questa funzionalità è piuttosto semplice da implementare. Cioè, in teoria, basta scrivere nella tabella il numero di versione e poi continuare a scrivere: versione inferiore a quello. E ciò significa che, molto probabilmente, proporremo di farlo agli appassionati. Sei un appassionato?

Sì, ma sfortunatamente non in C++.

I tuoi colleghi sanno scrivere in C++?

Troverò qualcuno.

Ottimo*.

* la funzionalità è stata aggiunta due mesi dopo la presentazione - è stata sviluppata dall'autore della domanda e ha inviato la sua pull request.

Grazie!

Ciao! Grazie per la presentazione! Hai menzionato che ClickHouse utilizza molto bene tutte le risorse disponibili. E il relatore vicino a Luxoft ha parlato della sua soluzione per Posta Russa. Ha detto che a loro è piaciuto molto ClickHouse, ma non lo hanno usato al posto del loro principale concorrente proprio perché consumava tutta la CPU. E non sono riusciti a integrarlo nella loro architettura, nel loro ZooKeeper con i container Docker. È possibile limitare ClickHouse in modo che non consumi tutto ciò che diventa disponibile?

Sì, è possibile e molto facile. Se vuoi che utilizzi meno core, basta scrivere set max_threads = 1. E tutto, eseguirà la query su un solo core. Inoltre, è possibile impostare questa configurazione in modo diverso per utenti diversi. Quindi non ci sono problemi. E fai sapere ai tuoi colleghi di Luxoft che non è corretto che non abbiano trovato questa impostazione nella documentazione.

Ciao Aleksej! Vorrei porre una domanda. Non è la prima volta che sento dire che molti iniziano a utilizzare ClickHouse come archiviazione per i log. Nella presentazione hai detto di non farlo, cioè non è necessario memorizzare stringhe lunghe. Cosa ne pensi?

Innanzitutto, i log non sono, di regola, stringhe lunghe. Ci possono essere delle eccezioni. Ad esempio, un servizio scritto in Java lancia eccezioni, queste vengono registrate. E così in un ciclo infinito, e lo spazio sul disco finisce. La soluzione è molto semplice. Se le stringhe sono molto lunghe, tagliale. E cosa significa 'lunghe'? Decine di kilobyte è un problema*.

*Nelle versioni recenti di ClickHouse è stata introdotta "granularità adattativa dell'indice", il che risolve in gran parte il problema di archiviazione delle stringhe lunghe.

E un kilobyte è normale?

Normale.

Ciao! Grazie per la presentazione! Ho già chiesto di questo in chat, ma non ricordo se ho ricevuto una risposta. È previsto in qualche modo di ampliare la sezione WITH in stile CTE?

Per ora no. La sezione WITH da noi è piuttosto superficiale. È una piccola funzionalità.

Ho capito. Grazie!

Grazie per la presentazione! Molto interessante! Domanda globale. È previsto di fare, magari, in forma di qualche placeholder, una modifica per l'eliminazione dei dati?

Certamente. Questa è la nostra priorità nella nostra lista di cose da fare. Stiamo attualmente pensando attivamente a come fare tutto nel modo giusto. E bisogna cominciare a digitare sulla tastiera*.

* hanno premuto i pulsanti sulla tastiera e hanno fatto tutto.

Questo influirà in qualche modo sulle prestazioni del sistema o no? L'inserimento sarà veloce come ora?

Forse le eliminazioni e gli aggiornamenti stessi saranno molto pesanti, ma ciò non influirà sulle prestazioni delle selezioni e sulle prestazioni degli inserimenti.

E un'ultima domanda. Nella presentazione hai parlato della chiave primaria. Di conseguenza, abbiamo una partizionamento, che per impostazione predefinita è mensile, corretto? E quando impostiamo l'intervallo di date che rientra nel mese, viene letta solo quella partizione, giusto?

Sì.

Una domanda. Se non possiamo identificare nessuna chiave primaria, è corretto farla basare sul campo "Data" per ridurre la ristrutturazione di questi dati in background e ordinarli meglio? Se non hai richieste a intervalli e non riesci nemmeno a selezionare alcuna chiave primaria, ha senso inserire la data nella chiave primaria?

Sì.

Forse ha senso inserire nella chiave primaria un campo che permetta una migliore compressione dei dati, se sono ordinati per quel campo. Ad esempio, l'identificativo dell'utente. Se un utente visita lo stesso sito. In questo caso, inserisci l'id dell'utente e il tempo. Così i tuoi dati si comprimeranno meglio. Per quanto riguarda la data, se non hai realmente richieste a intervalli sulle date e non le avrai mai, non è necessario includere la data nella chiave primaria.

Va bene, grazie mille!

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster