Pregunta

Tengo una tabla de alrededor de 3,1 millones de filas con la siguiente definición y los índices:

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

Otra tabla con 8,6 millones de filas que contiene los atributos de las filas de la primera tabla, las tablas se pueden unir con k_elem_id Y 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);

Estoy tratando de insertar las filas de la primera tabla (con algunas modificaciones) en una nueva tabla:

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

Desde que se ejecuta una sola instrucción insert tomaría mucho tiempo y yo no sería capaz de ver si la instrucción está atascado o algo, me he decidido a hacerlo en fragmentos más pequeños dentro de un bucle en una función.

La función se parece a esto:

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;

El problema es que se empieza a ir a través de los bucles bastante rápido, pero en algún punto desacelera a paso de tortuga.Cuando se ralentiza, al mismo tiempo, el uso de Disco en mi Windows 8 el Administrador de Tareas se eleva hasta el 99%, por lo que sospecho que esto está relacionado con el problema de alguna manera.

Ejecución de la INSERT declaración sobre su propia con algún valor aleatorio de i se ejecuta muy rápidamente, por lo que el problema parece surgir cuando se ejecuta en el bucle dentro de una función.Aquí está el EXPLAIN (ANALYZE,BUFFERS) de uno de esos único de ejecución:

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

Mi sistema está ejecutando PostgreSQL 9.3.5 en Windows 8 con 8 gb de RAM.

He experimentado con diferentes tamaños de lote, haciendo la consulta en diferentes formas y el aumento de la memoria de variables en la configuración de Postgres, pero nada parece realmente han resuelto el problema.

Las variables de configuración que han cambiado desde sus valores por defecto:

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

Me gustaría averiguar cuál es la causa de que esto suceda y qué se podría hacer al respecto.

¿Fue útil?

Solución

A la hora de crear un nueva tabla evitar el costo de la escritura Escribir por Delante de Registro (WAL) completamente con CREATE TABLE AS.
Ver @Kassandry la respuesta para una explicación de cómo WAL cifras en esto.

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 documentación:

Además de evitar el tiempo para el archivador o WAL remitente proceso el WAL datos, haciendo esto va a hacer que ciertos comandos más rápido, porque ellos no están diseñados para escribir WAL si wal_level es minimal.(Pueden garantizar la seguridad en choques más barato haciendo un fsync al final que por la redacción de WAL.) Esto se aplica a los siguientes comandos:

  • CREATE TABLE AS SELECT

  • CREATE INDEX (y variantes tales como ALTER TABLE ADD PRIMARY KEY)

  • ALTER TABLE SET TABLESPACE

  • CLUSTER

  • COPY FROM, cuando la tabla de destino ha sido creado o trunca anteriormente en la misma transacción

También es importante

  • CREATE TABLE AS hace imposible el uso de la pseudo-tipo de serial directamente.Pero ya que es sólo un "makro", usted puede hacer todo a mano en su lugar:Crear la secuencia, usarlo para generar id valores.Finalmente, el conjunto de la columna por defecto y hacer que la columna propio de la secuencia.Relacionado con:

  • El plpgsql función de contenedor es opcional (útil para el uso repetido), sólo podía ejecutar llanura de SQL en una transacción: BEGIN; ... COMMIT;

  • La adición de la PRIMARY KEY después de la inserción de los datos es más rápido debido a la creación de la (subyacente) índice de una sola pieza es más rápido que la adición de los valores de forma incremental.

  • Tenía un error de lógica en su partición:

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

    La última fila se superpondrían con la siguiente partición, la fila se insertará en repetidas ocasiones,conducen a una única infracción en el PK.El uso de < en lugar de <= iba a arreglar eso - pero he eliminado la partición por completo.

  • Si ejecuta este repetidamente, una índice de varias columnas en digiroad_segmentti (k_elem_id, tyyppi, dyn_tyyppi, region) podría pagar, en función de la distribución de los datos.

Las cosas de menor importancia

  • No citar el idioma plpgsql nombre, es un identificador.
  • Sería inútil marcar una función sin parámetros como STRICT.
  • VOLATILE es el valor predeterminado y sólo ruido.
  • Uso COALESCE para proporcionar un valor predeterminado para los valores NULOS.

  • Algunos de sus double precision (float8 columnas podría funcionar mejor como integer ya que en su mayoría habían numeric (9,0) en sus cuadros antiguos, que probablemente pueda ser reemplazado con el más barato de la llanura integer.

  • La columna region varchar(40) se ve como un candidato para la normalización (a menos que las regiones son en su mayoría único?) Crear una región de la tabla y solo uso region_id como FK columna de la tabla principal.

Otros consejos

Si solo cambiaras el shared_buffers,work_mem, y effective_cache_size variables de configuración, entonces probablemente todavía estés ejecutando con checkpoint_segments=3.

En este caso, solo tiene tres segmentos WAL y, como tal, necesita reciclarlos continuamente, forzando la escritura en los archivos de datos cada vez, lo que provoca una gran cantidad de actividad de E/S y ciertamente puede ralentizar su máquina.Puede verificar el comportamiento de los puntos de control mirando el registro y buscando la frase checkpoints are occurring too frequently.También puedes ver lo que están haciendo habilitando log_checkpoints=on en tu postgresql.conf

Yo recomendaría cambiar tu checkpoint_segments a algo más grande, como 40, y el checkpoint_completion_target a 0,9 para intentar suavizar el comportamiento que estás describiendo.

La configuración se describe con más detalle aquí en la documentación de PostgreSQL para 9.3 en el Registro de escritura anticipada sección.=)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a dba.stackexchange
scroll top