Mau desempenho para uma consulta de junção grande no PostgreSQL 8.4.4, apesar de memória suficiente para cache inteiramente

6

Pergunta

Como posso tornar a consulta descrita neste post mais rápida, em particular fazendo o PostgreSQL usando a RAM disponível? - Note que eu tentei configurar o effective_cache_size e shared_buffers apropriadamente. Veja abaixo.

Plano de fundo

Eu tenho que juntar regularmente uma tabela de ~ 260 milhões de linhas (coreg_master) com novos dados que chegam. Eu particionei a tabela para permitir que cada partição se encaixasse na RAM. Eu tenho índices apropriados configurados também, é claro. No entanto, ao unir as tabelas particionadas separadamente contra as outras (muito menores) tabelas, ele está fazendo IO totalmente aleatório no disco. Isso se deve a uma varredura de índice de loop aninhado na tabela grande, o que é muito lento, já que não temos uma grande configuração de disco.

Eu esperaria que ele usasse toda a RAM disponível para armazenar em cache a grande tabela particionada, o que eu entendo que deveria ser feito pelo próprio kernel Linux / sistema de arquivos. Mas ainda não carrega a tabela na memória RAM, embora ela se encaixe. Eu acho que é porque o padrão de acesso não é seqüencial e, portanto, não aciona o cache? Eu não faço ideia. Plano de consulta e parâmetros de configuração abaixo.

Estrutura da tabela

Esta é uma das partições do coreg_master, minha grande mesa. As tabelas particionadas são nomeadas coreg_a, coreg_b, etc.

\d coreg_a
                                   Table "public.coreg_a"
   Column    |       Type        |                         Modifiers                         
-------------+-------------------+-----------------------------------------------------------
 id          | integer           | not null default nextval('coreg_master_id_seq'::regclass)
 first_name  | character varying | 
 last_name   | character varying | 
 phone       | character varying | 
 city        | character varying | 
 zip         | integer           | 
 address     | character varying | 
 dob         | date              | 
 ip          | character varying | 
 source      | character varying | 
 gender      | character varying | 
 state       | character varying | 
 record_date | date              | 
 email       | character varying | 
Indexes:
    "coreg_a_name" btree (lower(first_name::text), lower(last_name::text))
Check constraints:
    "coreg_a_first_name_check" CHECK (first_name::text >= 'a'::text AND first_name::text < 'b'::text)
Inherits: coreg_master

Segue-se uma partição da tabela appendable_24, um exemplo da tabela a ser juntada ao coreg_master. Ele também é particionado da mesma forma que o coreg_master, então, na verdade, o coreg_a é associado ao appendable_24_a, etc, um de cada vez.

\d appendable_24_a
       Table "public.appendable_24_a"
   Column   |       Type        | Modifiers 
------------+-------------------+-----------
 line_num   | integer           | not null
 first_name | character varying | 
 last_name  | character varying | 
 address    | character varying | 
 state      | character varying | 
 zip        | integer           | 
Indexes:
    "appendable_24_a_name_index" btree (lower(first_name::text), lower(last_name::text))
Check constraints:
    "appendable_24_a_first_name_check" CHECK (first_name::text >= 'a'::text AND first_name::text < 'b'::text)
Inherits: appendable_24

Consulta e amp; EXPLAIN ANALYZE

Abaixo está a saída de explain analyze para a menor das junções (as tabelas são particionadas de acordo com a primeira letra da coluna first_name), já que não leva mais tempo. No entanto, o plano de consulta é o mesmo para todas as junções em cada partição, por isso deve ser representativo das junções maiores também (observe, eu fiz ANALYZE das tabelas e o tempo total foi realmente 20 segundos, mas mais rápido aqui como o resultado foi armazenado em cache):

explain analyze SELECT
          coreg_x.phone,
          coreg_x.email,
          coreg_x.record_date,
          appendable_24_x.line_num
        FROM appendable_24_x INNER JOIN coreg_x ON
          lower(appendable_24_x.first_name) = lower(coreg_x.first_name) AND
          lower(appendable_24_x.last_name) = lower(coreg_x.last_name) AND
          (coreg_x.phone IS NOT NULL OR coreg_x.email IS NOT NULL) AND
          similarity(lower(appendable_24_x.address), lower(coreg_x.address)) > 0.7
      ; 
                                                                                       QUE
RY PLAN 

------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
-----
 Nested Loop  (cost=0.01..640.49 rows=875 width=39) (actual time=9.990..53.839 rows=29 loo
ps=1)
   Join Filter: (similarity(lower((appendable_24_x.address)::text), lower((coreg_x.address
)::text)) > 0.7::double precision)
   ->  Seq Scan on appendable_24_x  (cost=0.00..1.80 rows=80 width=34) (actual time=0.009.
.0.111 rows=80 loops=1)
   ->  Index Scan using coreg_x_name on coreg_x  (cost=0.01..7.95 rows=1 width=64) (actual
 time=0.024..0.137 rows=44 loops=80)
         Index Cond: ((lower((coreg_x.first_name)::text) = lower((appendable_24_x.first_na
me)::text)) AND (lower((coreg_x.last_name)::text) = lower((appendable_24_x.last_name)::tex
t)))
         Filter: ((coreg_x.phone IS NOT NULL) OR (coreg_x.email IS NOT NULL))
 Total runtime: 53.950 ms
(7 rows)

Algumas estatísticas, parâmetros de configuração e outros dados

  • Versão do PostgreSQL: 8.4.4
  • OS: CentOS release 5.5 (Final)
  • Sistema de arquivos: ext3
  • RAM total disponível: 8 GB
  • shared_buffers = 2 GB
  • effective_cache_size = 7200MB
  • configuração completa do tempo de execução por meio do show all : link
  • maior tamanho da tabela de coreg_j particionada (~ 4900MB
  • número correspondente de linhas: ~ 32 milhões
  • tamanho do índice correspondente (first_name, last_name): ~ 1000MB
  • tamanho da segunda tabela particionada (appendable_24_j): ~ 1800kB
  • linhas em appendable_24_j: ~ 25.000
por ehsanul 15.09.2010 / 04:59

1 resposta

2

Há apenas memória RAM suficiente para armazenar em cache uma fração do seu banco de dados e, conforme o plano de consulta que você postou mostra, os dados / partes acessados anteriormente do índice em questão são realmente armazenados em cache. (O Postgres não armazena em cache os resultados da consulta).

O tempo de execução de 53 ms não é muito ruim, e não tenho certeza se 20 segundos em dados não armazenados em cache significa que o PG escolheu um plano de consulta incorreto. Afinal, apenas o índice em questão é de 1 GB, mas seria interessante ver a saída de análise para uma consulta lenta.

Você pode tentar ajustar os custos do planejador para ver se isso tem algum efeito no pior desempenho, se esse for o seu problema.

Você também pode querer aumentar seu maintentance_work_mem um pouco, mesmo que isso não esteja relacionado de forma alguma.

    
por 19.09.2010 / 20:45