Was bewirkt, dass große EINFÜGUNGEN langsamer werden und die Festplattennutzung explodiert?
Frage
Ich habe eine Tabelle mit ungefähr 3,1 Millionen Zeilen mit der folgenden Definition und den folgenden Indizes:
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");
Eine weitere Tabelle mit 8,6 Millionen Zeilen enthält Attribute für die Zeilen der ersten Tabelle, die Tabellen können mit verknüpft werden k_elem_id
UND 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);
Ich versuche, die Zeilen der ersten Tabelle (mit einigen Änderungen) in eine neue Tabelle einzufügen:
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)
);
Da das Ausführen einer einzelnen Einfügeanweisung lange dauern würde und ich nicht sehen könnte, ob die Anweisung feststeckt oder so, habe ich beschlossen, dies in kleineren Abschnitten innerhalb einer Schleife in einer Funktion zu tun.
Die Funktion sieht so aus:
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;
Das Problem ist, dass es ziemlich schnell anfängt, die Schleifen zu durchlaufen, sich dann aber irgendwann zu einem Kriechen verlangsamt.Wenn es langsamer wird, steigt gleichzeitig die Festplattennutzung in meinem Windows 8 Task-Manager auf 99%, sodass ich vermute, dass dies irgendwie mit dem Problem zusammenhängt.
Ausführen des INSERT
aussage für sich allein mit einem zufälligen Wert von i
wird sehr schnell ausgeführt, sodass das Problem anscheinend nur auftritt, wenn es in der Schleife innerhalb einer Funktion ausgeführt wird.Hier ist der EXPLAIN (ANALYZE,BUFFERS)
aus einer solchen Einzelausführung:
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
Auf meinem System wird PostgreSQL 9.3.5 unter Windows 8 mit 8 GB RAM ausgeführt.
Ich habe mit verschiedenen Stapelgrößen experimentiert, die Abfrage auf unterschiedliche Weise durchgeführt und die Speichervariablen in der Postgres-Konfiguration erhöht, aber nichts scheint das Problem wirklich gelöst zu haben.
Konfigurationsvariablen, die von ihren Standardwerten geändert wurden:
shared_buffers = 2048MB
work_mem = 64MB
effective_cache_size = 6000MB
Ich würde gerne herausfinden, was das verursacht und was dagegen getan werden könnte.
Lösung
Beim Erstellen eines neuer Tisch vermeiden Sie die Kosten für das Schreiben Vorausschreibprotokoll (WAL) komplett mit CREATE TABLE AS
.
Sehen @Kassandrys Antwort für eine Erklärung, wie WAL dazu beiträgt.
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;
Abgesehen davon, dass die Zeit für die Verarbeitung durch den Archivierer oder WAL-Absender vermieden wird die WAL-Daten werden dadurch bestimmte Befehle tatsächlich schneller machen, weil sie so konzipiert sind, dass sie überhaupt kein WAL schreiben, wenn
wal_level
isminimal
.(Sie können die Crashsicherheit billiger garantieren, indem sie einefsync
am Ende als durch Schreiben von WAL.) Dies gilt für Folgendes Menübefehle:
CREATE TABLE AS SELECT
CREATE INDEX
(und Varianten wieALTER TABLE ADD PRIMARY KEY
)
ALTER TABLE SET TABLESPACE
CLUSTER
COPY FROM
, wenn die Zieltabelle früher in derselben Transaktion erstellt oder abgeschnitten wurde
Auch wichtig
CREATE TABLE AS
macht es unmöglich, den Pseudotyp zu verwendenserial
direkt.Aber da das nur ein "Makro" ist, können Sie stattdessen alles von Hand machen:Erstellen Sie die Sequenz, verwenden Sie sie zum Generierenid
Wertvorstellungen.Legen Sie abschließend die Spaltenstandardeinstellung fest und legen Sie fest, dass die Spalte die Sequenz besitzt.Zusammenhang:Der plpgsql-Funktions-Wrapper ist optional (praktisch für die wiederholte Verwendung), Sie können einfach ausführen einfaches SQL in einer Transaktion:
BEGIN; ... COMMIT;
Hinzufügen der
PRIMARY KEY
nach das Einfügen der Daten ist auch schneller, da das Erstellen des (zugrunde liegenden) Index in einem Stück schneller ist als das schrittweise Hinzufügen von Werten.Du hattest eine logischer Fehler in Ihrer Partitionierung:
WHERE DLE.ogc_fid >= i * batch_size AND DLE.ogc_fid <= i * batch_size + batch_size
Die letzte Zeile würde sich mit der nächsten Partition überlappen, die Zeile würde wiederholt eingefügt, was zu einer eindeutigen Verletzung in der PK führt.Die Verwendung von
<
statt<=
würde das beheben - aber ich habe die Partitionierung komplett entfernt.Wenn Sie dies wiederholt ausführen, wird ein mehrspaltiger Index auf
digiroad_segmentti (k_elem_id, tyyppi, dyn_tyyppi, region)
könnte sich je nach Datenverteilung auszahlen.
Kleinigkeiten
- Zitiere die Sprache nicht
plpgsql
name, es ist eine Kennung. - Es wäre sinnlos, eine Funktion ohne Parameter als zu markieren
STRICT
. VOLATILE
ist die Standardeinstellung und nur Rauschen.Verwenden
COALESCE
um einen Standardwert für Nullwerte bereitzustellen.Einige Ihrer
double precision
(float8
) spalten könnten besser funktionieren alsinteger
da du meistens hattestnumeric (9,0)
in Ihren alten Tischen, die wahrscheinlich durch die billigere Ebene ersetzt werden könneninteger
.Spalte
region varchar(40)
sieht aus wie ein Kandidat für die Normalisierung (es sei denn, Regionen sind größtenteils einzigartig?) Erstellen Sie eine Regionstabelle und verwenden Sie einfachregion_id
als FK-Spalte in der Haupttabelle.
Andere Tipps
Wenn Sie nur das geändert haben shared_buffers
,work_mem
, und effective_cache_size
konfigurationsvariablen, dann laufen Sie wahrscheinlich noch mit checkpoint_segments=3
.
In diesem Fall haben Sie nur drei WAL-Segmente und müssen diese daher kontinuierlich recyceln, wodurch jedes Mal Schreibvorgänge in die Datendateien erzwungen werden, was zu einer enormen Menge an E / A-Aktivität führt und Ihren Computer mit Sicherheit zum Crawlen verlangsamen kann.Sie können das Checkpointing-Verhalten überprüfen, indem Sie im Protokoll nach der Phrase suchen checkpoints are occurring too frequently
.Sie können auch sehen, was sie tun, indem Sie aktivieren log_checkpoints=on
in Ihrem postgresql.konf
Ich würde empfehlen, Ihre zu ändern checkpoint_segments
zu etwas Größerem, wie 40, und die checkpoint_completion_target
auf 0,9, um zu versuchen, das von Ihnen beschriebene Verhalten zu glätten.
Die Einstellungen werden hier in der PostgreSQL-Dokumentation für 9.3 in der Protokoll vorausschreiben Abschnitt.=)