Ottimizzazione delle prestazioni di aggiornamento in blocco in PostgreSQL


37

Utilizzo di PG 9.1 su Ubuntu 12.04.

Al momento ci vogliono fino a 24 ore per eseguire un ampio set di istruzioni UPDATE su un database, che sono nel formato:

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid

(Stiamo solo sovrascrivendo i campi degli oggetti identificati dall'ID.) I valori provengono da un'origine dati esterna (non già presente nel DB in una tabella).

Le tabelle hanno una manciata di indici ciascuna e nessun vincolo di chiave esterna. Nessun COMMIT viene effettuato fino alla fine.

Sono necessarie 2 ore per importare un pg_dumpintero DB. Sembra una linea di base che dovremmo ragionevolmente prendere di mira.

A corto di produrre un programma personalizzato che in qualche modo ricostruisce un set di dati per PostgreSQL da reimportare, c'è qualcosa che possiamo fare per avvicinare le prestazioni di UPDATE di massa a quelle dell'importazione? (Questa è un'area che crediamo che gli alberi di unione strutturati in tronchi gestiscano bene, ma ci stiamo chiedendo se c'è qualcosa che possiamo fare in PostgreSQL.)

Qualche idea:

  • eliminare tutti gli indici non ID e ricostruire successivamente?
  • aumentando checkpoint_segments, ma questo aiuta effettivamente a sostenere un throughput a lungo termine?
  • usando le tecniche menzionate qui ? (Carica nuovi dati come tabella, quindi "unisci" i vecchi dati in cui l'ID non viene trovato nei nuovi dati)

Fondamentalmente ci sono un sacco di cose da provare e non siamo sicuri di quali siano i più efficaci o se stiamo trascurando altre cose. Passeremo i prossimi giorni a sperimentare, ma abbiamo pensato di chiedere anche qui.

Ho un carico simultaneo sul tavolo ma è di sola lettura.


Mancano informazioni cruciali nella tua domanda: la tua versione di Postgres? Da dove vengono i valori? Sembra un file esterno al database, ma per favore chiarisci. Hai un carico simultaneo sulla tabella di destinazione? Se sì, cosa esattamente? O puoi permetterti di abbandonare e ricreare? Nessuna chiave esterna, ok - ma ci sono altri oggetti dipendenti come le viste? Modifica la tua domanda con le informazioni mancanti. Non schiacciarlo in un commento.
Erwin Brandstetter,

@ErwinBrandstetter Grazie, ho aggiornato la mia domanda.
Yang,

Presumo tu abbia verificato tramite explain analyzel'utilizzo di un indice per la ricerca?
rogerdpack,

Risposte:


45

ipotesi

Poiché le informazioni mancano nella Q, suppongo:

  • I tuoi dati provengono da un file sul server di database.
  • I dati sono formattati proprio come l' COPYoutput, con un unico id per riga per abbinare la tabella di destinazione.
    In caso contrario, formattalo correttamente prima o utilizza le COPYopzioni per gestire il formato.
  • Stai aggiornando ogni singola riga nella tabella di destinazione o la maggior parte di essi.
  • Puoi permetterti di eliminare e ricreare la tabella di destinazione.
    Ciò significa nessun accesso simultaneo. Altrimenti considera questa risposta correlata:
  • Non ci sono affatto oggetti dipendenti, ad eccezione degli indici.

Soluzione

Ti suggerisco di seguire un approccio simile come indicato al link dal tuo terzo proiettile . Con importanti ottimizzazioni.

Per creare la tabella temporanea, esiste un modo più semplice e veloce:

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

Un singolo grande UPDATEda una tabella temporanea all'interno del database sarà più veloce dei singoli aggiornamenti dall'esterno del database di diversi ordini di grandezza.

Nel modello MVCC di PostgreSQL , un UPDATEmezzo per creare una nuova versione di riga e contrassegnare quella vecchia come eliminata. È costoso quanto un INSERTe un DELETEcombinato. Inoltre, ti lascia con molte tuple morte. Dato che stai comunque aggiornando l'intera tabella, nel complesso sarebbe più veloce creare una nuova tabella e rilasciare quella vecchia.

Se hai abbastanza RAM disponibile, imposta temp_buffers(solo per questa sessione!) Abbastanza alto da contenere la tabella temporanea nella RAM - prima di fare qualsiasi altra cosa.

Per ottenere una stima della quantità di RAM necessaria, eseguire un test con un piccolo campione e utilizzare le funzioni di dimensione oggetto db :

SELECT pg_size_pretty(pg_relation_size('tmp_tbl'));  -- complete size of table
SELECT pg_column_size(t) FROM tmp_tbl t LIMIT 10;  -- size of sample rows

Sceneggiatura completa

SET temp_buffers = '1GB';        -- example value

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

COPY tmp_tbl FROM '/absolute/path/to/file';

CREATE TABLE tbl_new AS
SELECT t.col1, t.col2, u.field1, u.field2
FROM   tbl     t
JOIN   tmp_tbl u USING (id);

-- Create indexes like in original table
ALTER TABLE tbl_new ADD PRIMARY KEY ...;
CREATE INDEX ... ON tbl_new (...);
CREATE INDEX ... ON tbl_new (...);

-- exclusive lock on tbl for a very brief time window!
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

DROP TABLE tmp_tbl; -- will also be dropped at end of session automatically

Carico simultaneo

Le operazioni simultanee sulla tabella (che ho escluso nelle ipotesi all'inizio) aspetteranno, una volta che la tabella è bloccata vicino alla fine e falliranno non appena la transazione viene impegnata, perché il nome della tabella viene immediatamente risolto nel suo OID, ma la nuova tabella ha un OID diverso. La tabella rimane coerente, ma le operazioni simultanee possono ottenere un'eccezione e devono essere ripetute. Dettagli in questa risposta correlata:

Percorso di aggiornamento

Se (devi) seguire il UPDATEpercorso, elimina qualsiasi indice non necessario durante l'aggiornamento e ricrealo in seguito. È molto più economico creare un indice in un pezzo piuttosto che aggiornarlo per ogni singola riga. Ciò può anche consentire aggiornamenti HOT .

Ho delineato una procedura simile utilizzando UPDATEin questa risposta strettamente correlata su SO .

 


1
In realtà sto solo aggiornando il 20% delle righe nella tabella di destinazione - non tutte, ma una porzione abbastanza grande che una fusione è probabilmente migliore della ricerca di aggiornamenti casuali.
Yang,

1
@AryehLeibTaurog: Che non dovrebbe accadere in quanto DROP TABLEtira fuori una Access Exclusive Lock. Ad ogni modo, ho già elencato il prerequisito nella parte superiore della mia risposta: You can afford to drop and recreate the target table.potrebbe aiutare a bloccare la tabella all'inizio della transazione. Ti suggerisco di iniziare una nuova domanda con tutti i dettagli rilevanti della tua situazione in modo da poter arrivare in fondo a questo.
Erwin Brandstetter,

1
@ErwinBrandstetter Interessante. Sembra dipendere dalla versione del server. Ho riprodotto l'errore su 8.4 e 9.1 usando l'adattatore psycopg2 e usando il client psql . 9.3 non ci sono errori. Vedi i miei commenti nel primo script. Non sono sicuro che ci sia una domanda da pubblicare qui, ma potrebbe valere la pena di sollecitare alcune informazioni su una delle liste di postgresql.
Aryeh Leib Taurog,

1
Ho scritto una semplice classe di supporto in Python per automatizzare il processo.
Aryeh Leib Taurog,

3
Risposta molto utile Come una leggera variazione, si può creare la tabella temporanea con solo le colonne da aggiornare e le colonne di riferimento, eliminare le colonne da aggiornare dalla tabella originale, quindi unire le tabelle utilizzando CREATE TABLE tbl_new AS SELECT t.*, u.field1, u.field2 from tbl t NATURAL LEFT JOIN tmp_tbl u;, LEFT JOINconsentendo di mantenere le righe per le quali non è disponibile alcun aggiornamento. Naturalmente il NATURALpuò essere cambiato in qualsiasi valido USING()o ON.
Skippy le Grand Gourou,

2

Se i dati possono essere resi disponibili in un file strutturato, è possibile leggerli con un wrapper di dati esterno ed eseguire un'unione sulla tabella di destinazione.


3
Cosa intendi specificamente con "unisci sulla tabella di destinazione"? Perché usare FDW è meglio della COPIA in una tabella temporanea (come suggerito nel terzo punto nella domanda originale)?
Yang,

"Unisci" come nell'istruzione sql MERGE. L'uso di FDW consente di farlo senza il passaggio aggiuntivo di copia dei dati in una tabella temporanea. Suppongo che non stai sostituendo l'intero set di dati e che ci sarebbe una certa quantità di dati nel file che non rappresenterebbe una modifica rispetto al set di dati corrente - se una quantità significativa è cambiata, allora un completo la sostituzione della tabella potrebbe essere utile.
David Aldridge,

1
@DavidAldridge: sebbene definito nello standard SQL: 2003, MERGEnon è ancora implementato in PostgreSQL . Le implementazioni in altri RDBMS variano abbastanza. Considera le informazioni sui tag per MERGEe UPSERT.
Erwin Brandstetter,

@ErwinBrandstetter [glurk] Oh sì, proprio così. Beh, Merge è la ciliegina sulla torta, suppongo. L'accesso ai dati senza il passaggio da tabella a import-to-temporary è davvero il punto cruciale della tecnica FDW.
David Aldridge,
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.