Meilleur indice pour la fonction de similarité

8

J'ai donc ce tableau avec 6,2 millions d'enregistrements et je dois effectuer des requêtes de recherche avec une similitude pour un pour la colonne. Les requêtes peuvent être:

 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

Plus de conditions peuvent être ajoutées dans le où (année = X, worksite_state = N, statut = 'certifié', visa_class = Z).

L'exécution de certaines de ces requêtes peut prendre très longtemps, plus de 30 secondes. Parfois plus d'une minute.

EXPLAIN ANALYZE de la requête mentionnée précédemment me donne ceci:

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

Je ne peux pas comprendre comment je dois indexer ma colonne pour la rendre rapide.

EDIT: Voici la version postgres:

PostgreSQL 9.3.5 sur x86_64-unknown-linux-gnu, compilé par gcc (Debian 4.7.2-5) 4.7.2, 64 bits

Voici la définition du tableau:

                                                         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
la source
Il devrait être évident d'inclure au moins la définition de la table (avec les types de données exacts et les contraintes) et votre version de Postgres. Considérez les instructions dans le tag-info pour postgresql-performance . Précisez également s'il y a toujours une condition d'égalité worksite_city.
Erwin Brandstetter
Merci, j'ai édité mon message pour inclure ces informations /. Et oui , il y a toujours une condition d'égalité sur worksite_city, worksite_state, yearet / ou status
bl0b

Réponses:

14

Vous avez oublié de mentionner que vous avez installé le module supplémentaire pg_trgm , qui fournit la similarity()fonction.

Opérateur de similarité %

Tout d'abord, quoi que vous fassiez, utilisez l'opérateur de similarité % au lieu de l'expression (similarity(job_title, 'sales executive') > 0.6). Beaucoup moins cher. Et le support d'index est lié aux opérateurs dans Postgres, pas aux fonctions.

Pour obtenir la similitude minimale souhaitée 0.6, exécutez:

SELECT set_limit(0.6);

Le paramètre reste pour le reste de votre session, sauf s'il est réinitialisé sur autre chose. Vérifier avec:

SELECT show_limit();

C'est un peu maladroit, mais idéal pour les performances.

Cas simple

Si vous vouliez simplement les meilleures correspondances dans la colonne job_titlepour la chaîne 'sales executive', ce serait un cas simple de recherche du "plus proche voisin" et pourrait être résolu avec un index GiST en utilisant la classe d'opérateur trigramme gist_trgm_ops(mais pas avec un index GIN) :

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

Pour inclure également une condition d'égalité, worksite_cityvous auriez besoin du module supplémentaire btree_gist. Exécuter (une fois par DB):

CREATE EXTENSION btree_gist;

Alors:

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

Requete:

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;

<-> étant l'opérateur "distance":

un moins la similarity()valeur.

Postgres peut également combiner deux index distincts, un index btree simple sur worksite_city activé et un index GiST distinct activé job_title, mais l'index multicolonne doit être le plus rapide - si vous combinez régulièrement les deux colonnes comme celle-ci dans les requêtes.

Ton cas

Cependant, votre requête est triée par salary , et non par distance / similitude, ce qui change complètement la nature du jeu. Maintenant, nous pouvons utiliser à la fois les index GIN et GiST, et GIN sera plus rapide (encore plus dans Postgres 9.4 qui a largement amélioré les index GIN - indice!)

Histoire similaire pour la vérification d'égalité supplémentaire worksite_city: installez le module supplémentaire btree_gin. Exécuter (une fois par DB):

CREATE EXTENSION btree_gin;

Alors:

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

Requete:

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

Encore une fois, cela devrait également fonctionner (moins efficacement) avec l'index plus simple que vous avez déjà ("index_lcas_job_title_trigram" ), éventuellement en combinaison avec d'autres index. La meilleure solution dépend de l'image complète.

À part

  • Vous avez beaucoup d'index. Êtes-vous sûr qu'ils sont tous en service et paient leurs frais d'entretien?

  • Vous avez des types de données douteux:

    employement_start_date | character varying
    employement_end_date   | character varying

    On dirait que ça devrait l'être date. Etc.

Réponses associées:

Erwin Brandstetter
la source
J'ai "index_lcas_job_title_trigram" gin (job_title gin_trgm_ops)lu quelque part que le gin est plus rapide que l'essentiel. Est-ce vrai?
bl0b
1
@ bl0b, gin ne supporte pas similaritydu tout, donc à cette fin n'est pas plus rapide.
jjanes
@ bl0b: Bien que jjanes ait raison (et c'était aussi ma première idée), votre cas est différent, et vous pouvez utiliser un index GIN après tout. J'en ai ajouté beaucoup plus.
Erwin Brandstetter
@ErwinBrandstetter merci beaucoup pour la réponse! Question rapide: Vous dites que GIN est plus rapide et que je devrais installer btree_gin. Mais ensuite, dans la création de l'index, vous dites d'exécuter: CREATE INDEX lcas_trgm_gin_idx ON lcas USING gist (worksite_city, job_title gist_trgm_ops);juste une faute de frappe?
bl0b
1
@ErwinBrandstetter est passé de 30 à 6 secondes. De grandes améliorations! Merci beaucoup!
bl0b