Domanda

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 posso inserire tutti i dati, quindi creare tutti gli indici.

In questa tabella da 1,2 TB, ho una colonna denominata run_id che divide in modo pulito i dati.Abbiamo ottenuto ottime prestazioni creando indici che coprono una vasta 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 danno la velocità di query desiderata.Purtroppo la creazione di ogni indice parziale richiede circa 70 minuti.

Sembra che la nostra CPU sia limitata (top sta mostrando il 100% per il processo).
C'è qualcosa che posso fare per accelerare la creazione dei nostri indici parziali?

Specifiche del sistema:

  • 18 nuclei Xeon
  • 192 GB di RAM
  • 12 SSD in RAID
  • Gli aspiratori automatici sono disattivati
  • manutenzione_lavoro_mem:64GB (Troppo alto?)

Specifiche della tabella:

  • Misurare:1,26 TBC
  • Numero di righe: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 un po' offuscati.)

Informazioni generali:

  • Abbiamo un team separato in loco che utilizza questi dati, ma in realtà ci sono solo uno o due utenti.(Questi dati vengono tutti generati tramite una simulazione.) Gli utenti iniziano ad analizzare i dati solo una volta terminati gli inserimenti e completati gli indici.La nostra preoccupazione principale è ridurre il tempo necessario per generare dati utilizzabili e in questo momento il collo di bottiglia è il tempo di creazione dell'indice.
  • La velocità delle query è stata completamente adeguata quando si utilizzano i partial.In effetti, penso che potremmo aumentare il numero di esecuzioni coperte da ciascun indice e mantenere comunque prestazioni delle query sufficientemente buone.
  • La mia ipotesi è che dovremo partizionare la tabella.Stiamo cercando di esaurire tutte le altre opzioni prima di intraprendere questa strada.
È stato utile?

Soluzione

Indice BRIN

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

BRIN sta per Block Range Index.Brin è progettato per la gestione di tavoli molto grandi in cui alcune colonne hanno una certa correlazione naturale con la loro posizione fisica all'interno del tavolo.UN bloccare La gamma è un gruppo di pagine fisicamente adiacenti nella tabella;Per ogni intervallo di blocchi, alcune informazioni di riepilogo sono archiviate dall'indice.

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

L'ottimale per il tuo caso:Se sai scrivere righe raggruppato SU run_id, l'indice diventa molto piccolo e la creazione molto più economica.

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

Potresti anche semplicemente indicizzare il file tavolo intero.

Disposizione della tabella

Qualunque altra cosa tu faccia, puoi risparmiare 8 byte persi nel riempimento a causa dei requisiti di allineamento per riga ordinando le colonne in questo modo:

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
);

Rimpicciolisce la tabella di 79 GB 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 uno numero intero la colonna è NULL, esiste un caso d'angolo per un paradosso di archiviazione:sarebbe più economico utilizzare invece 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 tue query effettive, potrebbe essere più efficiente avere questi cinque indici parziali invece di 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.

Rimozione run_id come colonna dell'indice in questo modo si risparmiano 8 byte per voce di indice: 32 invece di 40 byte per riga.Ogni indice è anche più economico da creare, ma crearne cinque invece di uno solo richiede sostanzialmente più tempo per una tabella troppo grande per rimanere nella cache (come hanno commentato @Jürgen e @Chris).Quindi questo potrebbe esserti utile o meno.

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 dei genitori vengono esaminati durante l'esclusione dei vincoli, quindi è probabile che un gran numero di partizioni aumenti considerevolmente il tempo di pianificazione delle query.Quindi il partizionamento basato sull'eredità dell'eredità funzionerà bene fino a forse un centinaio di partizioni;non provare a utilizzare molte migliaia di partizioni.

L'enfasi è mia.Di conseguenza, stimando 1000 valori diversi per run_id, creeresti partizioni che coprono circa 10 valori ciascuna.


maintenance_work_mem

Mi è mancato ciò a cui ti stai già adattando maintenance_work_mem nella mia prima lettura.Lascerò citazioni e consigli nella mia risposta come riferimento.Per documentazione:

maintenance_work_mem (numero intero)

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

Tieni presente che quando autovacuum corre, fino a autovacuum_max_workers Times questa memoria può essere allocata, quindi fai attenzione a non impostare il valore predefinito troppo in alto.Potrebbe essere utile controllarlo separatamente setting autovacuum_work_mem.

Lo imposterei solo al livello necessario, che dipende dalla dimensione dell'indice sconosciuta (a noi).E solo localmente per la sessione di esecuzione.Come spiega la citazione, un valore troppo alto generale altrimenti l'impostazione può affamare il server, perché anche l'autovacuum potrebbe richiedere più RAM.Inoltre, non impostarlo molto più in alto del necessario, anche nella sessione di esecuzione, la RAM libera potrebbe essere utilizzata nella memorizzazione nella cache dei dati.

Potrebbe assomigliare a questo:

BEGIN;

IMPOSTA LOCALE 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;

Di SET LOCAL:

Gli effetti di SET LOCAL ultimo solo fino alla fine dell'attuale transazione, commessa o no.

Per misurare le dimensioni degli oggetti:

Il server dovrebbe generalmente essere configurato in modo ragionevole altrimenti, ovviamente.

Altri suggerimenti

Forse è semplicemente troppo ingegnerizzato.Hai effettivamente provato a utilizzare un singolo indice completo?Gli indici parziali che coprono insieme l'intera tabella non forniscono molto guadagno, se non nessuno, per le ricerche sugli indici, e dal tuo testo deduco che hai indici per tutti i run_id?Potrebbero esserci alcuni vantaggi nelle scansioni degli indici con indici parziali, tuttavia valuterei prima la semplice soluzione a un indice.

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

Per una stima approssimativa di Maintenance_work_mem richiesta per ordinare tutti i run_id, che sono bigint da 8 byte, in memoria sarebbero necessari 10,5 * 8 GB + un po' di sovraccarico.

Potresti anche creare gli indici su tablespace diversi da quelli predefiniti.Questi tablespace potrebbero puntare a dischi che non sono ridondanti (basta ricreare gli indici se falliscono) o che si trovano su array più veloci.

Potresti anche considerare di partizionare la tabella utilizzando gli stessi criteri degli indici parziali.Ciò consentirebbe la stessa velocità dell'indice durante l'esecuzione di query, senza creare effettivamente alcun indice.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top