giovedì 16 settembre 2010

Divagazioni sul table partitioning in PostgreSQL - parte 1

Il table partitioning, italianamente reso come partizionamento delle tabelle, è una tecnica/tecnologia degli RDBMS per snellire le operazioni su tabelle molto grandi.
In sostanza una tabella madre viene suddivisa in un numero di tabelle figlie secondo dei criteri per i quali ogni riga non può che appartenere ad una ed una sola tabella figlia.
Tabelle più piccole e maneggevoli consentono operazioni di manutenzione (leggi VACUUM)  più brevi, comportano indici più piccoli che stiano più facilmente in memoria.
Ad esempio un caso tipico utilizza una colonna di tipo DATE o TIMESTAMP per suddividere la tabella madre in "sezioni temporali".
PostgreSQL di per sé non supporta il partizionamento ma supporta delle funzionalità di base con le quali farne un implementazione. La documentazione ufficiale dedica un intero capitolo, il 5.9, all'argomento con anche una proposta abbastanza dettagliata per l'implementazione.
Alla base vi sono alcuni meccanismi:

  1. l'ereditarietà gerarchica per definire una relazione di "figliolanza" fra la tabella madre e quelle figlie;
  2. le table constraint per garantire (e documentare al query planner) il criterio di partizionamento;
  3. la constraint exclusion per ottimizzare le query (SELECT) limitandole alle sole tabelle figlie "interessanti".

In pratica le SELECT sulla tabella madre (che di fatto resta vuota) vengono dirottate su una o più tabelle figlie in base al contenuto della condizione WHERE.
Le altre operazioni (INSERT, UPDATE, DELETE) vengono "catturate" al volo tramite delle funzioni TRIGGER e dirottate sulla giusta tabella figlia.
In questo modo, peraltro, la tabella madre resta sempre vuota, come solo riferimento (o "avatar") di tutta la gerarchia.
La tecnica ufficiale impone due importanti vincoli:

  1. la struttura gerarchica deve essere definita prima di ogni operazione;
  2. la struttura gerarchica deve poter ospitare tutte le possibili righe di ogni operazione
In generale il primo vincolo non è un grosso problema se non lo è anche il secondo. Ad esempio: si gestiscono movimentazioni di magazzino (in senso generico) appartenenti al solo anno solare in corso e si sono create partizioni (tabelle figlie) per ogni mese dell'anno.
Se ad esempio esistono magazzini "virtuali" per gli ordini schedulati nel futuro o se si effettuano movimenti correttivi "retrodatati", allora il vincolo n.2 potrebbe non essere soddisfatto a meno di poter estendere la struttura gerarchica nel passato e/o nel futuro.
E' facile pensare ad una prima variante. Invece di creare la gerarchia in modo preventivo, la si può creare "on demand", man mano che le tabelle figlie diventano necessarie.
Chiaramente se pensiamo al partizionamento per mesi, la cosa ha poco senso. Se pensiamo ad un partizionamento per settimane, magazzini e, magari, categorie di prodotti, la considerazione assume un'altro valore e tutt'altro peso.
Risparmiandoci la creazione statica e preventiva della gerarchia, risolviamo in un colpo solo tutti e due i vincoli imposti dalla soluzione ufficiale. Ogni movimento, sia esso passato o futuro, troverà certamente posto in una tabella figlia.
Questa soluzione impone però un radicale cambiamento nell'implementazione.
Innanzitutto si deve automatizzare tutta la parte DML che serve per creare dinamicamente le tabelle figlie. Compito non facile, ma certamente un buon esercizio, soprattutto in PL/PgSQL.
In seconda battuta, va notato come l'implementazione ufficiale si basa su TRIGGER in cui una cascata di IF viene usata per selezionare la tabella figlia giusta. Non discuto sull'efficienza della cosa, anche perché non so se l'interprete PL/PgSQL operi qualche ottimizzazione (non credo, però). Mi preoccupo del fatto che quella cascata di IF va ricreata ogni volta che si aggiungono (o tolgono) tabelle figlie. Per poterlo fare serve una tabella di supporto in cui riportare da un lato il criterio di selezione e dall'altro il nome della corrispondente tabella. E possibilmente una funzione per automatizzare la cosa: devono essere anche ridefinite le funzioni TRIGGER.
E questo ci porta al prossimo secondo passo.

Nessun commento:

Posta un commento