Accelerare la creazione dell'indice parziale di Postgres


8

Sto cercando di creare indici parziali per una tabella statica di grandi dimensioni (1,2 TB) in Postgres 9.4.

I miei dati sono completamente statici, quindi sono in grado di inserire tutti i dati, quindi creare tutti gli indici.

In questa tabella da 1,2 TB, ho una colonna denominata run_idche divide in modo pulito i dati. Abbiamo ottenuto grandi prestazioni creando indici che coprono una gamma di run_ids. Ecco un esempio:

CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

Questi indici parziali ci forniscono la velocità di query desiderata. Sfortunatamente, la creazione di ciascun indice parziale richiede circa 70 minuti.

Sembra che siamo limitati alla CPU ( topsta mostrando il 100% per il processo).
C'è qualcosa che posso fare per accelerare la creazione dei nostri indici parziali?

Specifiche di sistema:

  • 18 core Xeon
  • 192 GB di RAM
  • 12 SSD in RAID
  • Gli autovuoti sono disattivati
  • maintenance_work_mem: 64 GB (troppo alto?)

Specifiche della tabella:

  • Dimensioni: 1,26 TB
  • Numero di file: 10.537 miliardi
  • Dimensione tipica dell'indice: 3,2 GB (esiste una variazione di ~ 0,5 GB)

Definizione della tabella:

CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))

(Non leggere troppo nei nomi delle colonne - li ho offuscati un po '.)

Informazioni sullo sfondo:

  • Abbiamo un team separato in loco che consuma questi dati, ma in realtà ci sono solo uno o due utenti. (Tutti questi dati vengono generati tramite una simulazione.) Gli utenti iniziano ad analizzare i dati solo dopo aver terminato gli inserti e aver creato gli indici. La nostra principale preoccupazione è ridurre il tempo necessario per generare dati utilizzabili e in questo momento il collo di bottiglia è il tempo di creazione dell'indice.
  • La velocità della query è stata completamente adeguata durante l'utilizzo dei parziali. In effetti, penso che potremmo aumentare il numero di esecuzioni coperte da ciascun indice e mantenere comunque prestazioni di query sufficientemente buone.
  • La mia ipotesi è che dovremo partizionare la tabella. Stiamo cercando di esaurire tutte le altre opzioni prima di intraprendere questa strada.

Queste informazioni aggiuntive sarebbero strumentali: tipi di dati delle colonne coinvolte, query tipica, cardinalità (conteggio delle righe), quante diverse run_id? Uniformemente distribuito? Dimensione dell'indice risultante sul disco? I dati sono statici, ok. Ma sei l'unico utente?
Erwin Brandstetter,

Aggiornato con maggiori informazioni.
Burnsy

1
"Gli autovacu sono disattivati " - perché? Questa è una pessima idea. Questo impedisce la raccolta di statistiche e quindi genererà piani di query
errati

@a_horse_with_no_name Lanciamo manualmente un'analisi dopo l'
inserimento di

La tua situazione non è ancora chiara per me. Come sono le tue domande? Se il tuo tavolo è completely static, allora cosa intendi We have a separate team onsite that consumes this data? Indicizzi solo l'intervallo run_id >= 266 AND run_id <= 270o l'intera tabella? Qual è l'aspettativa di vita di ciascun indice / quante query lo utilizzeranno? Quanti valori diversi per run_id? Suona come ~ 15 Mio. righe per run_id, che renderebbe circa 800 valori diversi per run_id? Perché sono obj_type_set, by_s_id, seqnon definita NOT NULL? Quale percentuale approssimativa di valori NULL per ciascuno?
Erwin Brandstetter,

Risposte:


8

Indice BRIN

Disponibile da Postgres 9.5 e probabilmente proprio quello che stai cercando. Creazione di indici molto più veloce, indice molto più piccolo. Ma le query non sono in genere così veloci. Il manuale:

BRIN è l'acronimo di Block Range Index. BRIN è progettato per gestire tabelle molto grandi in cui alcune colonne hanno una correlazione naturale con la loro posizione fisica all'interno della tabella. Un intervallo di blocchi è un gruppo di pagine fisicamente adiacenti nella tabella; per ogni intervallo di blocco, alcune informazioni di riepilogo vengono archiviate dall'indice.

Continua a leggere, c'è di più.
Depesz ha eseguito un test preliminare.

L'optimum per il vostro caso: Se è possibile scrivere file in cluster su run_id, l'indice diventa molto piccola e la creazione molto più economico.

CREATE INDEX foo ON run.perception USING brin (run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

Potresti anche solo indicizzare l' intera tabella .

Layout della tabella

Qualunque cosa tu faccia, puoi salvare 8 byte persi a causa del riempimento a causa dei requisiti di allineamento per riga ordinando colonne come questa:

CREATE TABLE run.perception(
  id               bigint NOT NULL PRIMARY KEY
, run_id           bigint NOT NULL
, frame            bigint NOT NULL
, by_anyone        bigint NOT NULL
, by_me            bigint NOT NULL
, owning_p_id      bigint NOT NULL
, subj_id          bigint NOT NULL
, subj_state_frame bigint NOT NULL
, obj_type_set     bigint
, by_s_id          integer
, seq              integer
, by               varchar(45) NOT NULL -- or just use type text
);

Rende la tua tabella 79 GB più piccola se nessuna delle colonne ha valori NULL. Dettagli:

Inoltre, hai solo tre colonne che possono essere NULL. La bitmap NULL occupa 8 byte per 9 - 72 colonne. Se solo una colonna intera è NULL, esiste un caso angolare per un paradosso della memoria: sarebbe invece più economico utilizzare un valore fittizio: 4 byte sprecati ma 8 byte salvati non necessitando di una bitmap NULL per la riga. Maggiori dettagli qui:

Indici parziali

A seconda delle query effettive, potrebbe essere più efficiente disporre di questi cinque indici parziali anziché quello sopra:

CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 266;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 267;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 268;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 269;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 270;

Esegui una transazione per ciascuno.

La rimozione run_idcome colonna di indice in questo modo consente di risparmiare 8 byte per voce di indice - 32 anziché 40 byte per riga. Ogni indice è anche più economico da creare, ma la creazione di cinque anziché uno solo richiede molto più tempo per un tavolo troppo grande per rimanere nella cache (come commentato da @ Jürgen e @Chris). In modo che possa o meno essere utile per te.

partizionamento

Basato sull'eredità - l'unica opzione fino a Postgres 9.5.
(Il nuovo partizionamento dichiarativo in Postgres 11 o, preferibilmente, 12 è più intelligente.)

Il manuale:

Tutti i vincoli su tutti i figli della tabella padre vengono esaminati durante l'esclusione dei vincoli, quindi è probabile che un numero elevato di partizioni aumenti notevolmente i tempi di pianificazione delle query. Quindi il partizionamento basato sull'eredità legacy funzionerà bene con un massimo di forse cento partizioni ; non provare a usare molte migliaia di partizioni.

Enorme enfasi sulla mia. Di conseguenza, stimando 1000 valori diversi per run_id, faresti partizioni che si estendono intorno a 10 valori ciascuno.


maintenance_work_mem

Mi mancava che ti stia già adeguando maintenance_work_memnella mia prima lettura. Lascerò citazione e consigli nella mia risposta come riferimento. Per documentazione:

maintenance_work_mem (numero intero)

Specifica la quantità massima di memoria da utilizzare per le operazioni di manutenzione, ad esempio VACUUM, CREATE INDEXe ALTER TABLE ADD FOREIGN KEY. L'impostazione predefinita è 64 megabyte ( 64MB). Poiché solo una di queste operazioni può essere eseguita alla volta da una sessione del database e un'installazione normalmente non ne ha molte in esecuzione contemporaneamente, è sicuro impostare questo valore significativamente più grande di work_mem. Impostazioni più grandi potrebbero migliorare le prestazioni per l'aspirazione e il ripristino dei dump del database.

Si noti che quando autovacuumviene eseguito, fino a autovacuum_max_workersvolte questa memoria può essere allocata, quindi fare attenzione a non impostare un valore predefinito troppo alto. Può essere utile controllarlo separatamente setting autovacuum_work_mem.

Lo impostarei solo quanto basta, il che dipende dalle dimensioni dell'indice sconosciuto (per noi). E solo localmente per la sessione di esecuzione. Come spiega la citazione, un'impostazione generale troppo alta può far morire di fame il server altrimenti, poiché autovacuum potrebbe richiedere anche più RAM. Inoltre, non impostarlo molto più in alto del necessario, anche nella sessione di esecuzione, la RAM libera potrebbe essere utilizzata nella cache dei dati.

Potrebbe apparire così:

BEGIN;

SET LOCAL maintenance_work_mem = 10GB;  -- depends on resulting index size

CREATE INDEX perception_run_frame_idx_run_266_thru_270 ON run.perception(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

COMMIT;

Circa SET LOCAL:

Gli effetti SET LOCALdurano solo fino alla fine della transazione corrente, sia commessa che no.

Per misurare le dimensioni degli oggetti:

Il server dovrebbe generalmente essere configurato ragionevolmente altrimenti, ovviamente.


Scommetto che il suo lavoro è legato all'IO poiché il tavolo è molto più grande della RAM. Leggere la tabella ancora più spesso aggraverà il problema, indipendentemente dal fatto che ci sia memoria sufficiente per ordinare o meno ogni indice creato in memoria.
Jürgen Strobel,

Sono con Jurgen su questo. Credo che a causa delle dimensioni della tabella, in sostanza devi eseguire una scansione sequenziale completa sulla tabella per indice creato. Inoltre, non sono sicuro che vedrai tutto questo aumento di prestazioni dalla creazione di indici parziali separati (sono sicuro al 90% che non vedrai alcun aumento, ma su questo potrei essere spento.) Credo che un migliore la soluzione per la creazione di indici implicherebbe la creazione di un indice sull'intero intervallo su cui si desidera eseguire la query come "indice parziale singolo" per ridurre i tempi complessivi di costruzione.
Chris,

@Chris: sono d'accordo, la creazione di 5 indici richiederà più tempo rispetto a uno solo (anche se tutti insieme sono più piccoli, la creazione di ciascun indice è più economica e le query possono essere più veloci). Pensandoci ancora un po ', questo dovrebbe essere un caso d'uso perfetto per un indice BRIN in Postgres 9.5.
Erwin Brandstetter,

3

Forse questo è solo troppo ingegnerizzato. Hai effettivamente provato a utilizzare un singolo indice completo? Gli indici parziali che coprono l'intera tabella insieme non forniscono molto guadagno, se del caso, per le ricerche di indice, e dal tuo testo ne deduco che hai indici per tutti i run_ids? Potrebbero esserci dei vantaggi nell'indicizzare le scansioni con indici parziali, tuttavia vorrei prima confrontare la semplice soluzione a un indice.

Per ogni creazione di indice è necessaria una scansione completa rilegata IO attraverso la tabella. Pertanto, la creazione di più indici parziali richiede molta più I / O nella lettura della tabella rispetto a un singolo indice, sebbene l'ordinamento si riverserà su disco per il singolo indice di grandi dimensioni. Se insisti su indici parziali potresti provare a costruire tutti (o diversi) indici contemporaneamente in parallelo (memoria permettendo).

Per una stima approssimativa di maintenance_work_mem richiesta per ordinare tutti i run_ids, che sono origini a 8 byte, in memoria occorrerebbero 10,5 * 8 GB + un certo overhead.


0

È inoltre possibile creare indici su tablespace diversi da quelli predefiniti. Questi tablespace potrebbero puntare a dischi non ridondanti (ricreare gli indici in caso di errore) o che si trovano su array più veloci.

È inoltre possibile prendere in considerazione il partizionamento della tabella utilizzando gli stessi criteri degli indici parziali. Ciò consentirebbe la stessa velocità dell'indice durante le query, senza creare alcun indice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.