Ottimizzare un database rendendolo piu' piccolo

Saturday, 12 May 07
I database relazionali sembrano non essere mai abbastanza veloci, internet e' piena di domande e risposte tra forum, newsgroups, articoli e canali IRC dove non si parla d'altro.

Quando avete gia' eseguito parecchie ottimizzazione agli indici e alla struttura del database, forse c'e' ancora una cosa che potete fare.

Infatti...
  • Il database viene memorizzato sul disco
  • E i dischi sono lenti
  • Ma la cache del disco e quella del sistema operativo aiutano
  • Per cui un database piu' piccolo sul disco sara' piu' veloce
Come renderlo piu' piccolo? Ovviamente usando i giusti tipi di dato, Se state utilizzando un intero per salvarci solo 0 o 1 e' meglio usare una ENUM, e cosi' via.

Riuscendo ad ottenere una diminuzione significativa della dimensione del database si avra' quasi certamente una migliore performance se ci sono tantissimi record nel database (che non riesce a stare nella cache interamente).
7004 views*
Posted at 14:01:53 | permalink | 24 comments | print
Do you like this article?
Subscribe to the RSS feed of this blog or use the newsletter service in order to receive a notification every time there is something of new to read here.

Note: you'll not see this box again if you are a usual reader.

Comments

anonimo writes:
12 May 07, 14:33:29
cliccando su "tipi di dato" non c'è nessun post...
antirez writes:
12 May 07, 14:36:18
@anonimo: sistemato, il link e' praticamente fondamentale per trarre giovamento dall'articolo in casi reali :) Grazie.
ludo writes:
12 May 07, 16:16:45
Giustissimo. Ci sono anche altri sistemi per velocizzare delle query rendendo più piccolo magari non tutto il db, ma una singola tabella.

Un esempio tratto dall'esperienza: una tabella dove vengono raccolti i post di un certo numero di blog, che contiene oltre a id, id del blog, titolo, data, ecc. anche il contenuto del post. E magari il contenuto è pure duplicato, in formato originale HTML e in formato puro testo per l'indicizzazione full text.

Non è difficile capire (ma a me la lampadina si è accesa dopo qualche tempo) che con 500k post nella tabella, e un gb e mezzo circa occupato, una query che recupera anche solo il titolo di un centinaio di post deve macinarsi parecchio disco. I record infatti occupano più spazio, e per recuperare record non contigui (come succede quasi sempre) la testina del disco deve fare un sacco di strada.

L'ottimizzazione è semplice: spostare i contenuti in due tabelle distinte (HTML e testo) e legarli alla tabella principale con una foreign key. Avevo fatto dei test con InnoDB, non ricordo esattamanete ma mi sembra che la differenze di prestazioni su query tipiche che non prendono i contenuti fosse circa 10x.
antirez writes:
12 May 07, 16:29:17
@ludo: Ottimo commento, infatti e' perfetto per integrare l'articolo. Infatti qualunque campo sia di tipo "TEXT" o "BLOB" (o simili) dovrebbe essere *sempre* normalizzato in una tabella diversa, anche quando non ci si aspettano ripetizioni.

Perche' come giustamente dici, anche selezionando solo un ID... il database deve gestire delle tabelle enormi.

Anche io ho trovato questo problema *the hard way*, avevo creato un motore di ricerca sperimentale che operava su un set particolare di pagine web. Le query necessarie per incrociare le tabelle che avevano a che fare con le "words" contenute avevano dei JOIN alla tabella che conteneva l'ID di una data pagina. Ma questa tabella aveva anche l'HTML originale! Dunque... la lentezza era allucinante.

Spostando l'HTML in una tabella a parte il tutto diventava una scheggia.

La morale del tuo commento, alla luce di questo post direi che e': "siccome le tabelle piccole sono performanti un modo per restringere le tabelle e' SPOSTARE FUORI i dati che non vengono usati come indici, ovvero che non compaiono quasi mai dopo il WHERE".

Anche se bisogna ammettere che un server SQL furbo potrebbe fare questa ottimizzazione automaticamente, seprando una tabella in due parti in maniera trasparente per l'utente, in cui gli tutti i campi indicizzati sono da una parte e tutti quelli senza indice in un file diverso.

Ultima battuta... (ormai sembra un post questo commento), in questi casi puo' non essere assurdo utilizzare il filesystem direttamente come DB per registrare questi contenuti grandi come l'HTML, utilizzando come filename gli ID delle rispettive tabelle SQL.

grazie del commento!
C8E writes:
12 May 07, 16:41:04
> Come renderlo piu' piccolo? Ovviamente usando i giusti tipi di dato, Se state utilizzando un intero per salvarci solo 0 o 1 e' meglio usare una ENUM, e cosi' via.

Perche` non un BIT?
ludo writes:
12 May 07, 16:43:28
Beh si, "the hard way" funziona sempre, nel senso che poi le cose non te le scordi. :)

Sulla tua ultima osservazione non sono completamente d'accordo, almeno nel mio caso. A volte avere dati di grossa dimensione nel db (come ad esempio il testo dei post) ti fa risparmiare parecchie linee di codice quando devi recuperarli. E ti evita di dover gestire le situazioni limite, tipo file non trovato, ecc. Nel mio caso poi era indispensabile, dato che i contenuti vengono "macinati" da sphinx quando crea gli indici di ricerca, che li pesca direttamente da MySQL. :)

PS - è il primo blog che vedo che permette l'editing dei commenti, una funzionalità che ho sempre desiderato (e non ho mai avuto la pazienza di scrivermi)
antirez writes:
12 May 07, 16:44:07
@C8E: perche' int(1) in SQL significa... che rimane di 4 byte... ma non lo sa quasi nessuno a quanto pare.
C8E writes:
12 May 07, 16:51:31
io veramente mi riferivo a <http://dev.mysql.com/doc/refman/5.0/en/numeric-typ...;... :-/
antirez writes:
12 May 07, 16:51:43
@ludo: Concordo che memorizzare sul disco pone alcune difficolta, non bisogna solo gestire i casi limite di file non trovato, ma la concorrenza, utilizzando le primitive che POSIX mette a disposizione (esportate in quasi tutti i linguaggi comunque). Insomma... e' come farsi un piccolo DB.

Pero' ci sono casi in cui questa tecnica puo' pagare pesantemente in performance, bisogna valutare di caso in caso e i contesti di utilizzo.

Ad esempio Segnalo e Oknotizie hanno un sistema di cache basato sul filesystem che crea un sistema di 65536 directory (256 che contengono a loro volta 256), in cui due diverse varianti di crc32 (modulo 256) designano univocamente una directory per ogni URL da mandare in cache.

Questo sistema ci ha permesso di tenere carichi altissimi anche quando ancora non erano stati fatti i giusti aggiornamenti dell'hardware.

Tanto per finire, visto che e' un argomento correlato, c'e' chi preferisce questo schema:

- L'applicazione memorizza TUTTO in mememoria...
- Per ogni modifica che fa in memoria scrive un file di log di quello che ha fatto, senza accedervi mai in lettura.

Quando bisogna fermare e far ripartire l'applicazione, ad esempio perche' serve un reboot della macchina o dopo un upgrade, l'applicazione scorre tutto il file di log (che e' un journal in pratica), e ricostruisce lo stato della memoria.

Credo che finiro' per parlare in maniera piu' dettagliata di questa tecnica in un prossimo post, o almeno mi piacerebbe farlo tempo permettendo.
ludo writes:
12 May 07, 16:58:44
Ah beh, se parliamo di cache mi trovi perfettamente d'accordo, i concorrenti spagnoli di Blogo usano una mia cache semplice semplice, che bastava però per fare 20M di pageview al mese su un web server solo. E comunque gestire la concorrenza su cache banali, quando ti basta un "best effort" non è troppo difficile.

Sulla seconda parte del tuo commento, immagino tu ti riferisca a sistemi tipo Prevayler. Li ho sempre trovati super-interessanti, ma non ho le competenze (e il tempo) per scrivermene uno da solo, e pypersist non è mai decollato.
antirez writes:
12 May 07, 17:00:09
@C8E: Purtroppo BOOLEAN e' solo un alias per TINYINT(1) che richiede 8 byte (lo spazio necessario e' indicato qui: http://dev.mysql.com/doc/refman/5.0/en/storage-req...), idem per il tipi bitfield che si creano tramite BIT, in cui lo spazio necessario e' (N+7)/8 bytes.

Non c'e' alcun modo per occupare meno di un byte. MySQL sarebbe lentissimo se non potesse fare il seek utilizzando il byte come unita' indivisibile.
ludo writes:
12 May 07, 17:00:30
Dimenticavo, sarebbe interessante un tuo post dettagliato su come funziona la cache di Segnalo e OKnotizie...
antirez writes:
12 May 07, 17:01:33
@ludo: Ok allora lo metto nella TODO list :)
antirez writes:
12 May 07, 17:09:06
@ludo: vedo solo ora il commento precedente, si parlavo di un meccanismo estremamente simile a prevalayer, ma la loro idea non e' affatto nuova, si usa da una ventina d'anni :)

Anche se sembra strano in realta' l'implementazione non e' particolarmente difficile, ma questi sistemi NON possono essere usati per ogni tipo di applicazione.

Il limite e' questo: se la mia struttura dati e' in memoria, devo sempre trovare dei sistemi per accedervi nei modi necessari alla mia applicazione molto efficaci in termini di prestazioni. Se finisco per avere una struttura talmente complessa perche' mi serve eseguire delle "query" complesse su di essa forse e' meglio passare ad un database relazionale.

Per esempio e' difficile accadere ad una hash table in maniera performante in relazione al suo valore, mentre il tempo medio di accesso via chiave e' proporzionale a O(1), allora che chiave scelgo? magari l'ID, ma mi serve anche creare una nuova hash table per accedervi tramite nome, e poi un albero perche' voglio sapere quale dei miei oggetti ha un tale campo piu' grande di tutti gli altri, e stamparli in maniera ordinata...


Insomma spero di aver reso l'idea: se l'applicazione non e' facile da pensare in termini delle strutture dati classiche quali hash tables, alberi, liste, si rischia di fare un pasticcio.
C8E writes:
12 May 07, 17:14:51
antirez: Purtroppo BOOLEAN e' solo un alias per TINYINT(1) che richiede 8 byte

Infatti io parlavo di BIT, non di BOOLEAN... -_-

antirez: idem per il tipi bitfield che si creano tramite BIT, in cui lo spazio necessario e' (N+7)/8 bytes.

... che a casa mia implica che un BIT(1) occupa (1+7)/8 == 1 byte, che e` esattamente quanto stavo cercando di dire. Perche` quindi usi *idem*?

antirez: Non c'e' alcun modo per occupare meno di un byte. MySQL sarebbe lentissimo se non potesse fare il seek utilizzando il byte come unita' indivisibile.

Per carita`, nessuno pretendeva tanto... ma un byte mi sembra un buon risultato.
(In realta` e` in teoria possibile ricorrere ad una packed allocation che permetta di allocare fino a 8 campi BIT(1) in un byte, ovviamente continuando a "fare il seek utilizzando il byte come unita' indivisibile".)
antirez writes:
12 May 07, 17:27:51
@C8E: Ok allora non ci eravamo capiti, io mi chiedevo perche' dicevi di usare BIT invece di ENUM, visto che ENUM esattamente come BIT usa un solo byte di spazio :)

Per quanto riguarda la packed allocation, e' un'ottima idea! Ho pensato proprio oggi di usarla per LLOOGG, un po' come nelle "vecchie" define del C. Concettualmente sarebbe (lo dico per gli altri che non lo sanno)

POMO = 1
PERA = 2
ARANCIO = 4
LIMONE = 8

$myfruits = POMO|ARANCIO;

Siccome sono tutte potenze di due sono registrate dentro l'intero come bit selettivamente attivi o non attivi (volendola vedere come informatici. C'e' un modo di vederla matematicamente che sara' molto piu' ovvio per qualcuno).

E cosi' via. Ovviamente non si possono indicizzare i singoli "attributi", ma a volte non serve.

Pensavo di usarlo per registrare gli attributi di un account PRO di LLOOGG in base alle opzioni attive.

NOTA: il simbolo | e' in quasi tutti i linguaggi algol-like l'operatore di BITWISE-OR.
Domenico writes:
12 May 07, 18:59:52
Ciao!

Parlare di dbms intendendo poi solo mysql non è correttissimo, anche perché i problemi che hai sollevato in altri dbms più evoluti non esistono (sono già stati risolti).

In linea di principio è corretto effettuare una partizione verticale per avere una tabella di titoli e una di contenuti (che contiene blob o anche solo testi lunghi), mi sembra un tantino fuori luogo pensare di poter pensare di far stare un db in cache. Suppongo però che tu ti riferisca alla singola tabella e quindi alla capacità della memoria (ram a questo punto) di contenere l'insieme dei db-block che contengono i ricord che soddisfano la mia ricerca.

Per fare questo tuttavia esistono gli indici e le proiezioni (select elencoCampi) che in un dbms dovrebbero dinamicamente ottenere il risultato voluto.

In altri termini: attenzione a non cadere nell'errore opposto: trovare soluzioni ottimizzate per un dbms che comportano uno schema più complesso (e quindi meno gestibile) solo perché il dbms che si usa è antiquato.

Inoltre visto che si parla di mysql, è bene sapere che gli indici vengono utilizzati solo se la where è scritta nel giusto ordine (grave limite), e più in generale, una join è un'operazione molto pesante, quindi una partizione verticale potrebbe non portare a miglioramenti significativi (è vero si potrebbero fare dei subquery).

Penso, per concludere, che le estrazioni debba farle il dbms non utilizzare codice applicativo.

Hai scritto un interessante articolo che si intitolava (+ o -) "andare oltre il php", in questo ci starebbe bene "andare oltre mysql". :)
antirez writes:
12 May 07, 19:22:34
@Domenico: Ciao, grazie pe ril tuo commento.
Ecco alcune considerazioni su quello che dici:

> Parlare di dbms intendendo poi solo mysql non è
> correttissimo

L'ottimizzazione di cui parlo vale per tutti i DB che utilizzano uno schema in cui le tabelle sono rappresentate sul filesystem da file in *qualunque* modo, dunque si applica praticamente a qualunque cosa.

Piu' e' piccolo il database maggiore e' la porzione di esso che puo' stare in cache.

> Suppongo però che tu ti riferisca alla singola
> tabella e quindi alla capacità della memoria (ram a
> questo punto)

E invece si... bisogna farci stare quanto piu' DB e' possibile ma farcelo stare TUTTO e' una cosa assolutamente normale:

. un server normale oggi ha 4Gb di RAM
. il database di oknotizie che conta ALCUNI MILIONI DI VOTI e diverse news, commenti, ..., in un anno di intenso utilizzo misura 500 MB di RAM.
. 2+2 = ... 4 ;)

E' molto piu' improbabile che il DB non stia in memoria (RAM) che il contrario. Alcune soluzioni
tipo mysqlCluster addirittura funzionano solo se
il DB sta tutto in memoria.

Dunque il fuori luogo dove sarebbe?

> In altri termini: attenzione a non cadere
> nell'errore opposto: trovare soluzioni ottimizzate
> per un dbms che comportano uno schema più complesso
> (e quindi meno gestibile) solo perché il dbms che
> si usa è antiquato.

Invece questo e' proprio il modello di sviluppo che
uso: tendo ad utilizzare il DB in maniera piu' semplice
possibile e mettere un layer di astrazione e
complessita' a livello delle applicazione:

Non uso MAI sub-queries, stored procedures, ...

In pratica potrei facilmente convertire qualunque
applicazione che scrivo per un altro database
relazionale in poco tempo modificando solo la API
di accesso al DB.

> Inoltre visto che si parla di mysql, è bene sapere
> che gli indici vengono utilizzati solo se la where è
> scritta nel giusto ordine.

Testo tutte le query con EXPLAIN prima, e' il miglior modo per avere la matematica certezza del tipo di accesso richiesto. Inoltre sempre per la filosofia
di sviluppo di cui sopra le mie strutture di DB
tendono ad essere molto semplici.

> Penso, per concludere, che le estrazioni debba
> farle il dbms non utilizzare codice applicativo.

Io esattamente il contrario :)

Penso che le astrazioni le debba fare il codice
applicativo, ma esportando al resto della applicazione
una API solida in cui si vede solo una blackbox
che risponde come l'applicazione vuole e con la
velocita' richiesta.

Il mondo e' bello perche' e' vario ;)
antirez writes:
12 May 07, 19:28:08
@all: 18 commenti, 19 con questo, di sabato sera, wow! ;) E' ora di andare a letto che la gita sull'Etna di oggi si fa sentire, grazie a tutti quelli che commentano di cose tecniche e non su questo blog. A domani.
Domenico writes:
13 May 07, 09:51:49
beh a questo punto è chiaro che siamo su due posizione antiteche con approcci molto diversi.

Se sviluppo un'applicazione molto db dipendente, dove per esempio si vuole fare molta reportistica da dati che vengono inseriti da procedure batch o simili, il codice applicativo deve essere estremamente scarno e la logica la sposto nel db.

Due aggiunte: quando parlo di db intendo oggetti che hanno una proporzione 1 a 10, dimensione ram dedicata dimensione disco, quindi se parliamo di 4 GB di ram mi aspetto di avere 40 GB di dati altrimenti che senso ha un dbms tanto vale usare cobol!

In Oracle, per esempio, esiste la db-cache che mantiene in ram i blocchi utilizzati con una politica LRU (di solito) più altre particolarità su cui non mi dilungo, però questo è quello che dovrebbe fare un dbms, pensare lui a darmi i dati in maniera corretta e ricordarsi quali ho utilizzato più di recente, per esempio un dbms dovrebbe di suo memorizzare i blob in una zona a parte, di fatto vanificando la partizione verticale, da te proposta; in tal senso mysql è ancora molto indietro questo è sempre bene ricordarlo, anche se poi ha altri vantaggi (es.: snellezza)

Ciao!!!
antirez writes:
14 May 07, 05:09:41
@Domenico: non capisco davvero questa cosa della dimensione 1 a 10 di ram/disco, in pratica spazzi via in un sol colpo l'utilita' del 99.99% delle installazioni di DBMS.

A parte questo, su una cosa siamo sicuramente d'accordo, che un DBMS dovrebbe essere in grado di splittare i campi con indice e quelli di piccole dimensioni dai restanti BLOB e varchar senza indice abbastanza lunghi. L'ho scritto tra i commenti iniziali e sono assolutamente convinto che e' una pecca di MySQL il fatto di non gestire questa cosa.

E' questa l'astrazione che dovebbe dare un DBMS, non le stored procedures, le views o altre cose che IMHO stanno molto meglio in una API che esporta l'applicazione.

Qualcuno sa quali DBMS opensource riescano a fare questa ottimizzazione in maniera trasparente?
Domenico writes:
14 May 07, 06:34:31
[quote] non capisco davvero questa cosa della dimensione 1 a 10 di ram/disco, in pratica spazzi via in un sol colpo l'utilita' del 99.99% delle installazioni di DBMS.[/quote]
No, è mysql che è anomalo, questo rapporto è normale, i db di google lavarno sui tera... Molte feature di oracle si applicano quando le tabelle superano i 100 GB non puoi certo aspettarti di trovare tutto il db in RAM .
Tra l'altro quello che sto dicendo è anche ampiamente espresso nella letteratura sulle basi di dati.

Su quale db free lo faccia non saprei... forse postgressql solo che trovare la documentazione che te lo dica non è semplice, perché magari lo fa in automatico e la cosa è documentata chissà dove...

Se ti interessa attualmente esiste anche una versione di Oracle free for developping e che dovrebbe avere costi accessibili anche in produzione.

Invece per quanto sono daccordo che le viste sono oggetti sostanzialmente inutili, stored-procedure e trigger fanno parte di un middle layer nativo, che è, giocoforza, molto più efficente di qualsiasi altro middle layer.

Naturalmente esistono degli standard (che non seguono tutti).

Il subquering, invece è una delle cose più utili che ci possano essere ed è standard (sql 99).


Cmq le scelte dipendono da che applicativo fai, però solitamente pensare al dbms come al db cioè un mero contentitore di dati, alla lunga è sconveniente, anche perché, sei davvero così sicuro di scrivere query compatibili con qualsiasi dbms?

Ciao!
antirez writes:
14 May 07, 07:43:08
@Domenico: cerco di organizzare un po' i concetti altrimenti questa discussione diventa inutile per chi ci legge:

1) Mysql non c'entra niente con la dimensione del DB. Ti sto solo dicendo che la stragrande maggioranza di applicazioni web hanno database con dimensione inferiore a 3/4 GB, ma di tanto.

2) Il discorso "piu' sta in RAM e meglio e'" non significa che il DB debba essere necessariamente piu' piccolo della RAM. Se invece di stare il 5% in cache ci sta il 10% c'e' un aumento delle performance piu' o meno lineare. I database utilizzano quasi sempre una forma di memorizzazione sul disco che tiene conto della localita' della cache (per questo non si usano mai le hash tables su disco).

3) Detto questo _sarebbe assolutamente desiderabile_ che mysql fosse in grado di separare su file diversi parti di tabelle che vengono lette in maniera piu' sporadica e che rappresentano in termini di occupazione sul disco una parte significativa.

4) L'azienda media non e' Google, e se sei Google vuoi ancor di meno Oracle, che infatti loro non usano (hanno rilasciato alcune patch per MySQL che usano per adsense qualche settimana fa). Infatti per le cose veramente pesanti bisogna fare sistemi verticali, non usare un DBMS, almeno questo e' cio' che penso (e cio' che Google ad altri con gli stessi problemi di enormita' del dataset fanno infatti).

5) Considerare un DBMS solo come un DB e' una questione di scelte, io ho un approccio bottom-up alla programmazione, inizio dai particolari e li unisco in layer via via di livello superiore. Progettare stored procedures e' molto piu' top-down. Non critico chi lo fa ma non e' il mio modo di pensare allo sviluppo del software.

Ciao, spero di tornare a parlare di queste cose in maniera un po' piu' dettagliata, specialmente per quanto riguarda le architetture non convenzionali.
Navigatore Anonimo writes:
25 Jul 07, 14:39:16
comments closed