Melhor índice para função de similaridade

8

Então, eu tenho essa tabela com 6,2 milhões de registros e preciso executar consultas de pesquisa com similaridade para uma para a coluna. As consultas podem ser:

 SELECT  "lca_test".* FROM "lca_test"
 WHERE (similarity(job_title, 'sales executive') > 0.6)
 AND worksite_city = 'los angeles' 
 ORDER BY salary ASC LIMIT 50 OFFSET 0

Mais condições podem ser adicionadas no where (ano = X, local do trabalho = N, status = 'certificado', visa_class = Z).

A execução de algumas dessas consultas pode levar muito tempo, mais de 30 segundos. Às vezes mais de um minuto.

EXPLAIN ANALYZE da consulta mencionada anteriormente me dá o seguinte:

Limit  (cost=0.43..42523.04 rows=50 width=254) (actual time=9070.268..33487.734 rows=2 loops=1)
->  Index Scan using index_lca_test_on_salary on lca_test  (cost=0.43..23922368.16 rows=28129 width=254) (actual time=9070.265..33487.727 rows=2 loops=1)
>>>> Filter: (((worksite_city)::text = 'los angeles'::text) AND (similarity((job_title)::text, 'sales executive'::text) > 0.6::double precision))
>>>> Rows Removed by Filter: 6330130 Total runtime: 33487.802 ms
Total runtime: 33487.802 ms

Não consigo descobrir como indexar minha coluna para torná-la extremamente rápida.

EDIT: Aqui está a versão do postgres:

PostgreSQL 9.3.5 no x86_64-unknown-linux-gnu, compilado pelo gcc (Debian 4.7.2-5) 4.7.2, 64 bits

Aqui está a definição da tabela:

                                                         Table "public.lca_test"
         Column         |       Type        |                       Modifiers                       | Storage  | Stats target | Description
------------------------+-------------------+-------------------------------------------------------+----------+--------------+-------------
 id                     | integer           | not null default nextval('lca_test_id_seq'::regclass) | plain    |              |
 raw_id                 | integer           |                                                       | plain    |              |
 year                   | integer           |                                                       | plain    |              |
 company_id             | integer           |                                                       | plain    |              |
 visa_class             | character varying |                                                       | extended |              |
 employement_start_date | character varying |                                                       | extended |              |
 employement_end_date   | character varying |                                                       | extended |              |
 employer_name          | character varying |                                                       | extended |              |
 employer_address1      | character varying |                                                       | extended |              |
 employer_address2      | character varying |                                                       | extended |              |
 employer_city          | character varying |                                                       | extended |              |
 employer_state         | character varying |                                                       | extended |              |
 employer_postal_code   | character varying |                                                       | extended |              |
 employer_phone         | character varying |                                                       | extended |              |
 employer_phone_ext     | character varying |                                                       | extended |              |
 job_title              | character varying |                                                       | extended |              |
 soc_code               | character varying |                                                       | extended |              |
 naic_code              | character varying |                                                       | extended |              |
 prevailing_wage        | character varying |                                                       | extended |              |
 pw_unit_of_pay         | character varying |                                                       | extended |              |
 wage_unit_of_pay       | character varying |                                                       | extended |              |
 worksite_city          | character varying |                                                       | extended |              |
 worksite_state         | character varying |                                                       | extended |              |
 worksite_postal_code   | character varying |                                                       | extended |              |
 total_workers          | integer           |                                                       | plain    |              |
 case_status            | character varying |                                                       | extended |              |
 case_no                | character varying |                                                       | extended |              |
 salary                 | real              |                                                       | plain    |              |
 salary_max             | real              |                                                       | plain    |              |
 prevailing_wage_second | real              |                                                       | plain    |              |
 lawyer_id              | integer           |                                                       | plain    |              |
 citizenship            | character varying |                                                       | extended |              |
 class_of_admission     | character varying |                                                       | extended |              |
Indexes:
    "lca_test_pkey" PRIMARY KEY, btree (id)
    "index_lca_test_on_id_and_salary" btree (id, salary)
    "index_lca_test_on_id_and_salary_and_year" btree (id, salary, year)
    "index_lca_test_on_id_and_salary_and_year_and_wage_unit_of_pay" btree (id, salary, year, wage_unit_of_pay)
    "index_lca_test_on_id_and_visa_class" btree (id, visa_class)
    "index_lca_test_on_id_and_worksite_state" btree (id, worksite_state)
    "index_lca_test_on_lawyer_id" btree (lawyer_id)
    "index_lca_test_on_lawyer_id_and_company_id" btree (lawyer_id, company_id)
    "index_lca_test_on_raw_id_and_visa_and_pw_second" btree (raw_id, visa_class, prevailing_wage_second)
    "index_lca_test_on_raw_id_and_visa_class" btree (raw_id, visa_class)
    "index_lca_test_on_salary" btree (salary)
    "index_lca_test_on_visa_class" btree (visa_class)
    "index_lca_test_on_wage_unit_of_pay" btree (wage_unit_of_pay)
    "index_lca_test_on_worksite_state" btree (worksite_state)
    "index_lca_test_on_year_and_company_id" btree (year, company_id)
    "index_lca_test_on_year_and_company_id_and_case_status" btree (year, company_id, case_status)
    "index_lcas_job_title_trigram" gin (job_title gin_trgm_ops)
    "lca_test_company_id" btree (company_id)
    "lca_test_employer_name" btree (employer_name)
    "lca_test_id" btree (id)
    "lca_test_on_year_and_companyid_and_wage_unit_and_salary" btree (year, company_id, wage_unit_of_pay, salary)
Foreign-key constraints:
    "fk_rails_8a90090fe0" FOREIGN KEY (lawyer_id) REFERENCES lawyers(id)
Has OIDs: no
bl0b
fonte
Deve ser óbvio incluir pelo menos a definição da tabela (com tipos e restrições de dados exatos) e sua versão do Postgres. Considere as instruções na tag-info para postgresql-performance . Esclareça também se há sempre uma condição de igualdade ativada worksite_city.
Erwin Brandstetter
Obrigado, editei meu post para incluir essas informações /. E sim, há sempre uma condição de igualdade em worksite_city, worksite_state, yeare / ou status
bl0b

Respostas:

14

Você esqueceu de mencionar que instalou o módulo adicional pg_trgm, que fornece a similarity()função.

Operador de similaridade %

Primeiro de tudo, faça o que fizer, use o operador de similaridade em %vez da expressão (similarity(job_title, 'sales executive') > 0.6). Muito mais barato. E o suporte ao índice está vinculado aos operadores no Postgres, não às funções.

Para obter a semelhança mínima desejada de 0.6, execute:

SELECT set_limit(0.6);

A configuração permanece pelo resto da sua sessão, a menos que seja redefinida para outra coisa. Verificar com:

SELECT show_limit();

Isso é um pouco desajeitado, mas ótimo para desempenho.

Caso simples

Se você apenas desejasse as melhores correspondências na coluna job_titlepara a cadeia 'sales executive', esse seria um caso simples de pesquisa "vizinho mais próximo" e poderia ser resolvido com um índice GiST usando a classe de operador trigrama gist_trgm_ops(mas não com um índice GIN) :

CREATE INDEX trgm_idx ON lcas USING gist (job_title gist_trgm_ops);

Para incluir também uma condição de igualdade, worksite_cityvocê precisaria do módulo adicional btree_gist. Execute (uma vez por DB):

CREATE EXTENSION btree_gist;

Então:

CREATE INDEX lcas_trgm_gist_idx ON lcas USING gist (worksite_city, job_title gist_trgm_ops);

Inquerir:

SELECT set_limit(0.6);  -- once per session

SELECT *
FROM   lca_test
WHERE  job_title % 'sales executive'
AND    worksite_city = 'los angeles' 
ORDER  BY (job_title <-> 'sales executive')
LIMIT  50;

<-> sendo o operador "distância":

um menos o similarity()valor.

O Postgres também pode combinar dois índices separados, um índice btree simples worksite_citye um índice GiST separado job_title, mas o índice de várias colunas deve ser mais rápido - se você combinar as duas colunas como essa em consultas regularmente.

Seu caso

No entanto, sua consulta é classificada por salary, não por distância / semelhança, o que muda completamente a natureza do jogo. Agora podemos usar os índices GIN e GiST, e o GIN será mais rápido (ainda mais no Postgres 9.4, que melhorou bastante os índices GIN - dica!)

História semelhante para a verificação de igualdade adicional worksite_city: instale o módulo adicional btree_gin. Execute (uma vez por DB):

CREATE EXTENSION btree_gin;

Então:

CREATE INDEX lcas_trgm_gin_idx ON lcas USING gin (worksite_city, job_title gin_trgm_ops);

Inquerir:

SELECT set_limit(0.6);  -- once per session

SELECT *
FROM   lca_test
WHERE  job_title % 'sales executive'
AND    worksite_city = 'los angeles' 
ORDER  BY salary 
LIMIT  50 -- OFFSET 0

Novamente, isso também deve funcionar (com menos eficiência) com o índice mais simples que você já possui ( "index_lcas_job_title_trigram"), possivelmente em combinação com outros índices. A melhor solução depende da imagem completa.

Apartes

  • Você tem muitos índices. Tem certeza de que todos estão em uso e pagam o custo de manutenção?

  • Você tem alguns tipos de dados duvidosos:

    employement_start_date | character varying
    employement_end_date   | character varying

    Parece que esses deveriam ser date. Etc.

Respostas relacionadas:

Erwin Brandstetter
fonte
Eu "index_lcas_job_title_trigram" gin (job_title gin_trgm_ops)li em algum lugar que o gin é mais rápido que o essencial. Isso é verdade?
bl0b
11
@ bl0b, o gin não suporta similaritynada, portanto, para esse propósito, não é mais rápido.
jjanes
@ bl0b: Enquanto jjanes está certo (e essa também foi minha primeira ideia), seu caso é diferente e você pode usar um índice GIN, afinal. Eu adicionei muito mais.
Erwin Brandstetter
@ ErwinBrandstetter muito obrigado pela resposta! Pergunta rápida: você diz que o GIN é mais rápido e que eu devo instalar btree_gin. Mas, na criação do índice, você pede para executar: CREATE INDEX lcas_trgm_gin_idx ON lcas USING gist (worksite_city, job_title gist_trgm_ops);apenas um erro de digitação?
bl0b
11
@ErwinBrandstetter Passou dos 30 aos 6 segundos. Grandes melhorias! Muito obrigado!
bl0b