Question

J'ai un tableau d'environ 3,1 millions de lignes avec la définition et les index suivants :

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

Une autre table de 8,6 millions de lignes contient les attributs des lignes de la première table, les tables peuvent être jointes avec k_elem_id ET 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);

J'essaie d'insérer les lignes du premier tableau (avec quelques modifications) dans un nouveau tableau :

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

Étant donné que l'exécution d'une seule instruction d'insertion prendrait beaucoup de temps et que je ne serais pas en mesure de voir si l'instruction est bloquée ou quelque chose du genre, j'ai décidé de le faire en morceaux plus petits à l'intérieur d'une boucle dans une fonction.

La fonction ressemble à ceci :

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;

Le problème est qu'il commence par parcourir les boucles assez rapidement, mais ralentit ensuite à un moment donné.Lorsqu'il ralentit, l'utilisation du disque dans mon gestionnaire de tâches de Windows 8 augmente en même temps jusqu'à 99 %, je soupçonne donc que cela est lié au problème d'une manière ou d'une autre.

Exécuter le INSERT instruction seule avec une valeur aléatoire de i s'exécute très rapidement, le problème semble donc se poser uniquement lors de son exécution dans la boucle à l'intérieur d'une fonction.Voici la EXPLAIN (ANALYZE,BUFFERS) à partir d'une seule de ces exécutions :

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

Mon système exécute PostgreSQL 9.3.5 sur Windows 8 avec 8 Go de RAM.

J'ai expérimenté différentes tailles de lots, en effectuant la requête de différentes manières et en augmentant les variables de mémoire dans la configuration de Postgres, mais rien ne semble avoir vraiment résolu le problème.

Variables de configuration qui ont été modifiées par rapport à leurs valeurs par défaut :

shared_buffers = 2048MB
work_mem = 64MB
effective_cache_size = 6000MB

J'aimerais savoir ce qui cause cela et ce qui pourrait être fait pour y remédier.

Était-ce utile?

La solution

Lors de la création d'un nouveau tableau éviter le coût de l'écriture Journal d'écriture anticipée (WAL) complètement avec CREATE TABLE AS.
Voir @ Réponse de Kassandry pour une explication de la manière dont WAL entre en ligne de compte.

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;

La documentation:

En plus d'éviter le temps pour que le Archiver ou le WAL expédateur traitent les données WAL, ce qui fera en fait certaines commandes plus rapidement, car ils sont conçus pour ne pas écrire du WAL du tout si wal_level est minimal.(Ils peuvent garantir la sécurité en cas d'accident à moindre coût en effectuant une fsync À la fin qu'en écrivant le Wal.) Cela s'applique aux commandes suivantes:

  • CREATE TABLE AS SELECT

  • CREATE INDEX (et des variantes telles que ALTER TABLE ADD PRIMARY KEY)

  • ALTER TABLE SET TABLESPACE

  • CLUSTER

  • COPY FROM, lorsque la table cible a été créée ou tronquée plus tôt dans la même transaction

Il est également important

  • CREATE TABLE AS rend impossible l'utilisation du pseudo-type serial directement.Mais comme il ne s'agit que d'un "makro", vous pouvez tout faire à la main :Créez la séquence, utilisez-la pour générer id valeurs.Enfin, définissez la colonne par défaut et faites en sorte que la colonne soit propriétaire de la séquence.En rapport:

  • Le wrapper de fonction plpgsql est facultatif (pratique pour une utilisation répétée), vous pouvez simplement exécuter SQL simple dans une transaction: BEGIN; ... COMMIT;

  • Ajout du PRIMARY KEY après l'insertion des données est également plus rapide car la création de l'index (sous-jacent) en un seul morceau est plus rapide que l'ajout de valeurs de manière incrémentielle.

  • Tu avais un erreur logique dans votre partitionnement :

    WHERE DLE.ogc_fid >= i * batch_size
    AND   DLE.ogc_fid <= i * batch_size + batch_size
    

    La dernière ligne chevaucherait la partition suivante, la ligne serait insérée à plusieurs reprises, conduisant à une violation unique du PK.L'utilisation de < au lieu de <= résoudrait ce problème - mais j'ai complètement supprimé le partitionnement.

  • Si vous exécutez ceci à plusieurs reprises, un index multicolonne sur digiroad_segmentti (k_elem_id, tyyppi, dyn_tyyppi, region) pourrait payer, en fonction de la distribution des données.

Des choses mineures

  • Ne citez pas la langue plpgsql le nom, c'est un identifiant.
  • Il serait inutile de marquer une fonction sans paramètres comme STRICT.
  • VOLATILE est la valeur par défaut et juste du bruit.
  • Utiliser COALESCE pour fournir une valeur par défaut pour les valeurs NULL.

  • Certains de vos double precision (float8) les colonnes pourraient mieux fonctionner car integer puisque tu avais surtout numeric (9,0) dans vos anciennes tables, qui peuvent probablement être remplacées par des tables simples moins chères integer.

  • La colonne region varchar(40) ressemble à un candidat à la normalisation (à moins que les régions ne soient pour la plupart uniques ?) Créez une table de régions et utilisez simplement region_id comme colonne FK dans la table principale.

Autres conseils

Si vous aviez seulement changé le shared_buffers,work_mem, et effective_cache_size variables de configuration, alors vous utilisez probablement toujours checkpoint_segments=3.

Dans ce cas, vous ne disposez que de trois segments WAL et, en tant que tels, vous devez les recycler en permanence, forçant à chaque fois les écritures dans les fichiers de données, ce qui entraîne une énorme quantité d'activité d'E/S et peut certainement ralentir considérablement votre machine.Vous pouvez vérifier le comportement des points de contrôle en consultant le journal et en recherchant la phrase checkpoints are occurring too frequently.Vous pouvez également voir ce qu'ils font en activant log_checkpoints=on dans votre postgresql.conf

Je recommanderais de changer votre checkpoint_segments à quelque chose de plus grand, comme 40, et le checkpoint_completion_target à 0,9 pour essayer d'atténuer le comportement que vous décrivez.

Les paramètres sont décrits plus en détail ici dans la documentation PostgreSQL pour 9.3 dans le Journal d'écriture anticipée section.=)

Licencié sous: CC-BY-SA avec attribution
Non affilié à dba.stackexchange
scroll top