Ciò che causa il grande inserto per rallentare e l'utilizzo del disco da esplodere?
Domanda
Ho una tabella di circa 3,1 milioni di righe con la seguente definizione e indici:
CREATE TABLE digiroad_liikenne_elementti (
ogc_fid serial NOT NULL,
wkb_geometry geometry(Geometry,4258),
tiee_tila numeric(9,0),
vaylatyypp numeric(9,0),
toiminnall numeric(9,0),
eurooppati character varying(254),
kansalline numeric(9,0),
tyyppi numeric(9,0),
liikennevi numeric(9,0),
ens_talo_o numeric(9,0),
talonumero numeric(9,0),
ens_talo_v numeric(9,0),
oik_puol_t character varying(254),
tieosan_ta numeric(9,0),
viim_talo_ numeric(9,0),
viim_tal_1 numeric(9,0),
vas_puol_t character varying(254),
laut_tyypp numeric(9,0),
lautta_lii numeric(9,0),
inv_paalu_ numeric(19,11),
inv_paal_1 numeric(19,11),
liitalue_o numeric(9,0),
ketju_oid numeric(9,0),
tietojoukk numeric(9,0),
ajoratanum numeric(4,0),
viite_guid character varying(254),
"timestamp" date,
tiee_kunta numeric(9,0),
toissij_ti character varying(254),
viite_oid numeric(9,0),
k_elem_id numeric(9,0),
region character varying(40) DEFAULT 'REGION'::character varying,
CONSTRAINT digiroad_liikenne_elementti_pkey PRIMARY KEY (ogc_fid)
);
CREATE INDEX digiroad_liikenne_elementti_wkb_geometry_geom_idx
ON digiroad_liikenne_elementti USING gist (wkb_geometry);
CREATE INDEX dle_k_elem_id_idx
ON digiroad_liikenne_elementti USING btree (k_elem_id);
CREATE INDEX dle_ogc_fid_idx
ON digiroad_liikenne_elementti USING btree (ogc_fid);
CREATE INDEX dle_region_idx
ON digiroad_liikenne_elementti USING btree (region COLLATE pg_catalog."default");
.
Un altro tavolo con 8,6 milioni di righe contiene attributi per le righe della prima tabella, le tabelle possono essere unite con k_elem_id
e region
.
CREATE TABLE digiroad_segmentti (
ogc_fid serial NOT NULL,
wkb_geometry geometry(Geometry,4258),
segm_tila numeric(9,0),
tyyppi numeric(9,0),
loppupiste numeric(19,11),
alkupiste numeric(19,11),
vaikutuska numeric(9,0),
vaikutussu numeric(9,0),
vaikutusai character varying(254),
tieosanume numeric(19,11),
tienumero numeric(9,0),
dyn_arvo numeric(9,0),
dyn_tyyppi numeric(9,0),
omistaja_t numeric(9,0),
pysakki_va numeric(9,0),
pysakki_ty numeric(9,0),
pysakki_su numeric(9,0),
pysakki_ka numeric(9,0),
pysakki_yl character varying(254),
palvelu_pa numeric(9,0),
toissijain numeric(9,0),
siltataitu numeric(9,0),
rdtc_tyypp numeric(9,0),
rdtc_alaty numeric(9,0),
rdtc_paikk numeric(19,11),
rdtc_luokk numeric(9,0),
rdtc_liitt character varying(254),
palvelu_ob numeric(9,0),
ketju_oid numeric(9,0),
tietojoukk numeric(9,0),
ajoratanum numeric(4,0),
viite_guid character varying(254),
"timestamp" date,
sivusiirty numeric(19,11),
toissij_ti character varying(254),
viite_oid numeric(9,0),
k_elem_id numeric(9,0),
region character varying(40) DEFAULT 'REGION'::character varying,
CONSTRAINT digiroad_segmentti_pkey PRIMARY KEY (ogc_fid)
);
CREATE INDEX digiroad_segmentti_wkb_geometry_geom_idx
ON digiroad_segmentti USING gist (wkb_geometry);
CREATE INDEX ds_dyn_arvo_idx
ON digiroad_segmentti USING btree (dyn_arvo);
CREATE INDEX ds_dyn_tyyppi_idx
ON digiroad_segmentti USING btree (dyn_tyyppi);
CREATE INDEX ds_k_elem_id_idx
ON digiroad_segmentti USING btree (k_elem_id);
CREATE INDEX ds_ogc_fid_idx
ON digiroad_segmentti USING btree (ogc_fid);
CREATE INDEX ds_region_idx
ON digiroad_segmentti USING btree (region COLLATE pg_catalog."default");
CREATE INDEX ds_tyyppi_idx
ON digiroad_segmentti USING btree (tyyppi);
.
Sto cercando di inserire le righe della prima tabella (con alcune modifiche) in una nuova tabella:
CREATE TABLE edge_table (
id serial NOT NULL,
geom geometry,
source integer,
target integer,
km double precision,
kmh double precision DEFAULT 60,
kmh_winter double precision DEFAULT 50,
cost double precision,
cost_winter double precision,
reverse_cost double precision,
reverse_cost_winter double precision,
x1 double precision,
y1 double precision,
x2 double precision,
y2 double precision,
k_elem_id integer,
region character varying(40),
CONSTRAINT edge_table_pkey PRIMARY KEY (id)
);
.
Dal momento che eseguire una singola dichiarazione di inserimento richiederebbe molto tempo e non sarei in grado di vedere se la dichiarazione è bloccata o qualcosa del genere, ho deciso di farlo in pezzi più piccoli all'interno di un anello in una funzione.
La funzione è simile a questa:
DROP FUNCTION IF EXISTS insert_function();
CREATE OR REPLACE FUNCTION insert_function()
RETURNS VOID AS
$$
DECLARE
const_type_1 CONSTANT int := 5;
const_type_2 CONSTANT int := 11;
i int := 0;
row_count int;
BEGIN
CREATE TABLE IF NOT EXISTS edge_table (
id serial PRIMARY KEY,
geom geometry,
source integer,
target integer,
km double precision,
kmh double precision DEFAULT 60,
kmh_winter double precision DEFAULT 50,
cost double precision,
cost_winter double precision,
reverse_cost double precision,
reverse_cost_winter double precision,
x1 double precision,
y1 double precision,
x2 double precision,
y2 double precision,
k_elem_id integer,
region varchar(40)
);
batch_size := 1000;
SELECT COUNT(*) FROM digiroad_liikenne_elementti INTO row_count;
WHILE i*batch_size < row_count LOOP
RAISE NOTICE 'insert: % / %', i * batch_size, row_count;
INSERT INTO edge_table (kmh, kmh_winter, k_elem_id, region)
SELECT CASE WHEN DS.dyn_arvo IS NULL THEN 60 ELSE DS.dyn_arvo END,
CASE WHEN DS.dyn_Arvo IS NULL THEN 50 ELSE DS.dyn_arvo END,
DR.k_elem_id,
DR.region
FROM (
SELECT DLE.k_elem_id,
DLE.region,
FROM digiroad_liikenne_elementti DLE
WHERE DLE.ogc_fid >= i * batch_size
AND
DLE.ogc_fid <= i * batch_size + batch_size
) AS DR
LEFT JOIN
digiroad_segmentti DS ON
DS.k_elem_id = DR.k_elem_id
AND
DS.region = DR.region
AND
DS.tyyppi = const_type_1
AND
DS.dyn_tyyppi = const_type_2;
i := i + 1;
END LOOP;
END;
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;
.
Il problema è che inizia a passare attraverso i loop abbastanza veloci, ma poi ad un certo punto rallenta fino a un gattonio. Quando rallenta, allo stesso tempo l'utilizzo del disco nel mio Task Manager di Windows 8 sale fino al 99%, quindi sospetto che questo sia correlato al problema in qualche modo.
Esecuzione dell'istruzione INSERT
da solo con un valore casuale di i
esegue molto rapidamente, quindi il problema sembra solo sorgere quando lo esegue nel ciclo all'interno di una funzione. Ecco il EXPLAIN (ANALYZE,BUFFERS)
da una tale singola esecuzione:
Insert on edge_table (cost=0.86..361121.68 rows=1031 width=23) (actual time=3405.101..3405.101 rows=0 loops=1)
Buffers: shared hit=36251 read=3660 dirtied=14
-> Nested Loop Left Join (cost=0.86..361121.68 rows=1031 width=23) (actual time=61.901..3377.609 rows=986 loops=1)
Buffers: shared hit=32279 read=3646
-> Index Scan using dle_ogc_fid_idx on digiroad_liikenne_elementti dle (cost=0.43..85.12 rows=1031 width=19) (actual time=31.918..57.309 rows=986 loops=1)
Index Cond: ((ogc_fid >= 200000) AND (ogc_fid < 201000))
Buffers: shared hit=27 read=58
-> Index Scan using ds_k_elem_id_idx on digiroad_segmentti ds (cost=0.44..350.16 rows=1 width=23) (actual time=2.861..3.337 rows=0 loops=986)
Index Cond: (k_elem_id = dle.k_elem_id)
Filter: ((tyyppi = 5::numeric) AND (dyn_tyyppi = 11::numeric) AND (vaikutussu = 3::numeric) AND ((region)::text = (dle.region)::text))
Rows Removed by Filter: 73
Buffers: shared hit=31266 read=3588
Total runtime: 3405.270 ms
.
Il mio sistema è in esecuzione PostgreSQL 9.3.5 su Windows 8 con 8 GB di RAM.
Ho sperimentato diverse dimensioni in batch, facendo la query in diversi modi e aumentando le variabili di memoria nella configurazione di Postgres, ma nulla sembra aver davvero risolto il problema.
Variabili di configurazione che sono state modificate dai loro valori predefiniti:
shared_buffers = 2048MB
work_mem = 64MB
effective_cache_size = 6000MB
.
Mi piacerebbe scoprire cosa sta facendo accadere questo e cosa potrebbe essere fatto a riguardo.
Soluzione
Durante la creazione di una nuova tabella New Table Evita il costo della scrittura Scrivi un registro avanti (wal) completamente con CREATE TABLE AS
.
Vedi @ Risposta di Kassandry per una spiegazione Come le figure wal in questo.
CREATE OR REPLACE FUNCTION insert_function()
RETURNS void AS
$func$
DECLARE
const_type_1 CONSTANT int := 5;
const_type_2 CONSTANT int := 11;
BEGIN
CREATE SEQUENCE edge_table_id_seq;
CREATE TABLE edge_table AS
SELECT nextval('edge_table_id_seq'::regclass)::int AS id
, NULL::geometry AS geom
, NULL::integer AS source
, target::integer AS target
, NULL::float8 AS km
, COALESCE(DS.dyn_arvo::float8, float8 '60') AS kmh
, COALESCE(DS.dyn_Arvo::float8, float8 '50') AS kmh_winter
, NULL::float8 AS cost
, NULL::float8 AS cost_winter
, NULL::float8 AS reverse_cost
, NULL::float8 AS reverse_cost_winter
, NULL::float8 AS x1
, NULL::float8 AS y1
, NULL::float8 AS x2
, NULL::float8 AS y2
, D.k_elem_id::integer AS k_elem_id
, D.region::varchar(40) AS region
FROM digiroad_liikenne_elementti D
LEFT JOIN digiroad_segmentti DS
ON DS.k_elem_id = D.k_elem_id
AND DS.region = D.region
AND DS.tyyppi = const_type_1
AND DS.dyn_tyyppi = const_type_2;
ALTER TABLE edge_table
ADD CONSTRAINT edge_table_pkey PRIMARY KEY(id)
, ALTER COLUMN id SET NOT NULL
, ALTER COLUMN id SET DEFAULT nextval('edge_table_id_seq'::regclass)
, ALTER COLUMN kmh SET DEFAULT 60
, ALTER COLUMN kmh_winter SET DEFAULT 50;
ALTER SEQUENCE edge_table_id_seq OWNED BY edge_table.id;
END
$func$ LANGUAGE plpgsql;
.
.Oltre a evitare il tempo per l'elaborazione del mittente dell'archivio o del wal I dati WAL, facendo ciò creerà effettivamente determinati comandi più velocemente, perché sono progettati per non scrivere WAL per tutto se
wal_level
èminimal
. (Possono garantire la sicurezza di crash più a buon mercato facendo unfsync
alla fine che scrivendo Wal.) Si applica a quanto segue Comandi:.
CREATE TABLE AS SELECT
CREATE INDEX
(e varianti comeALTER TABLE ADD PRIMARY KEY
)
ALTER TABLE SET TABLESPACE
CLUSTER
COPY FROM
, quando la tabella di destinazione è stata creata o troncata in precedenza nella stessa transazione
anche importante
- .
-
CREATE TABLE AS
rende impossibile utilizzare il tipo Pseudo-tiposerial
direttamente. Ma poiché questo è solo un "Makro", puoi fare tutto a mano invece: creare la sequenza, usarlo per generare valoriid
. Infine, impostare la colonna predefinita e rendi la colonna della sequenza. Correlato: -
L'involucro funzionante PLPGSQL è facoltativo (utile per uso ripetuto), è possibile semplicemente eseguire Plain SQL in una transazione :
BEGIN; ... COMMIT;
-
Aggiunta del
PRIMARY KEY
Dopo Inserimento dei dati è anche più veloce perché la creazione dell'indice (sottostante) in un unico pezzo è più veloce di aggiungere i valori in modo incrementale. -
Hai avuto un errore di logica nel partizionamento:
.WHERE DLE.ogc_fid >= i * batch_size AND DLE.ogc_fid <= i * batch_size + batch_size
L'ultima riga si sovrapponga alla partizione successiva, la riga sarebbe stata inserita ripetutamente, portando a una violazione unica nel PK. L'uso di
<
invece di<=
firerebbe - ma ho rimosso il partizionamento del tutto. -
Se lo si esegue ripetutamente, un INDICE MULTICOLUMN su
digiroad_segmentti (k_elem_id, tyyppi, dyn_tyyppi, region)
potrebbe pagare, a seconda della distribuzione dei dati.
cose minori
- .
- Non citare il nome della lingua
plpgsql
Nome, è un identificatore. - Sarebbe inutile segnare una funzione senza parametri come
STRICT
. -
VOLATILE
è il predefinito e il semplice rumore. -
Utilizzare
COALESCE
per fornire un valore predefinito per valori nulli. -
Alcune delle colonne
double precision
(float8
) potrebbero funzionare meglio comeinteger
poiché è stato principalmente avuto generatoDicetagCode nelle tue vecchie tabelle, che può probabilmente essere sostituito con ilnumeric (9,0)
in meno semplice. -
La colonna
integer
sembra un candidato per la normalizzazione (a meno che le regioni siano per lo più uniche?) Creare una tabella regione e basta utilizzareregion varchar(40)
come colonna FK nella tabella principale.
Altri suggerimenti
Se si modificate solo le variabili shared_buffers
, work_mem
e effective_cache_size
Config Variables, probabilmente stai ancora in esecuzione con checkpoint_segments=3
.
In questo caso hai solo tre segmenti WAL e, in quanto tale, è necessario riciclarli continuamente, costringendo a scrivere i file di dati ogni volta, che causa un'enorme quantità di attività I / O e può certamente rallentare la macchina a un gattonio. È possibile controllare il comportamento checkpointing guardando nel registro e cerca la frase checkpoints are occurring too frequently
. Puoi anche guardare ciò che stanno facendo abilitando log_checkpoints=on
nel tuo postgresql.conf
Consiglierei di cambiare il checkpoint_segments
a qualcosa di più grande, come 40 e il checkpoint_completion_target
a 0.9 per cercare di appianare il comportamento che stai descrivendo.
Le impostazioni sono ulteriormente descritte qui nella documentazione PostgreSQL per 9.3 nel Scrivi la sezione Avanti log .=)