El mejor índice para la función de similitud

8

Entonces tengo esta tabla con 6.2 millones de registros y tengo que realizar consultas de búsqueda con similitud para una para la columna. Las consultas pueden 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

Se pueden agregar más condiciones en where (año = X, worksite_state = N, status = 'certified', visa_class = Z).

La ejecución de algunas de esas consultas puede llevar mucho tiempo, más de 30 segundos. A veces más de un minuto.

EXPLAIN ANALYZE de la consulta mencionada anteriormente me da esto:

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

No puedo entender cómo debo indexar mi columna para que sea increíblemente rápida.

EDITAR: Aquí está la versión de postgres:

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

Aquí está la definición de la tabla:

                                                         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
fuente
Debería ser obvio incluir al menos la definición de la tabla (con tipos de datos exactos y restricciones) y su versión de Postgres. Considere las instrucciones en la etiqueta-información para postgresql-performance . También aclare si siempre hay una condición de igualdad activada worksite_city.
Erwin Brandstetter
Gracias, edité mi publicación para incluir esas informaciones. Y sí, siempre hay una condición de igualdad en worksite_city, worksite_state, yeary / o status
bl0b

Respuestas:

14

Olvidó mencionar que instaló el módulo adicional pg_trgm, que proporciona la similarity()función.

Operador de similitud %

En primer lugar, haga lo que haga, use el operador de similitud en %lugar de la expresión (similarity(job_title, 'sales executive') > 0.6). Mucho mas barato. Y el soporte de índice está vinculado a operadores en Postgres, no a funciones.

Para obtener la similitud mínima deseada de 0.6, ejecute:

SELECT set_limit(0.6);

La configuración permanece para el resto de su sesión a menos que se restablezca a otra cosa. Verifícalo con:

SELECT show_limit();

Esto es un poco torpe, pero excelente para el rendimiento.

Caso simple

Si solo quisiera las mejores coincidencias en la columna job_titlepara la cadena 'ejecutivo de ventas', este sería un caso simple de búsqueda de "vecino más cercano" y podría resolverse con un índice GiST utilizando la clase de operador trigrama gist_trgm_ops(pero no con un índice GIN) :

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

Para incluir también una condición de igualdad worksite_city, necesitará el módulo adicional btree_gist. Ejecutar (una vez por DB):

CREATE EXTENSION btree_gist;

Entonces:

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

Consulta:

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;

<-> siendo el operador de "distancia":

uno menos el similarity()valor.

Postgres también puede combinar dos índices separados, un índice btree simple activado worksite_cityy un índice GiST separado activado job_title, pero el índice de varias columnas debería ser más rápido, si combina las dos columnas de esta manera en consultas regularmente.

Tu caso

Sin embargo, su consulta se ordena por salary, no por distancia / similitud, lo que cambia la naturaleza del juego por completo. Ahora podemos usar tanto el índice GIN como el GiST, y GIN será más rápido (aún más en Postgres 9.4, que ha mejorado en gran medida los índices GIN, ¡pista!)

Historia similar para la verificación de igualdad adicional en worksite_city: instalar el módulo adicional btree_gin. Ejecutar (una vez por DB):

CREATE EXTENSION btree_gin;

Entonces:

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

Consulta:

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

Nuevamente, esto también debería funcionar (menos eficientemente) con el índice más simple que ya tiene ( "index_lcas_job_title_trigram"), posiblemente en combinación con otros índices. La mejor solución depende de la imagen completa.

Aparte

  • Tienes muchos índices. ¿Está seguro de que todos están en uso y pagan su costo de mantenimiento?

  • Tienes algunos tipos de datos dudosos:

    employement_start_date | character varying
    employement_end_date   | character varying

    Parece que esos deberían ser date. Etc.

Respuestas relacionadas:

Erwin Brandstetter
fuente
"index_lcas_job_title_trigram" gin (job_title gin_trgm_ops)he leído en alguna parte que la ginebra es más rápida que la esencia. ¿Es eso cierto?
bl0b
1
@ bl0b, gin no es compatible similarityen absoluto, por lo que para ese propósito no es más rápido.
jjanes
@ bl0b: Si bien jjanes tiene razón (y esa fue también mi primera idea), su caso es diferente y, después de todo, puede usar un índice GIN. Agregué mucho más.
Erwin Brandstetter
@ErwinBrandstetter muchas gracias por la respuesta! Pregunta rápida: Dices que GIN es más rápido y que debería instalar btree_gin. Pero luego, en la creación del índice, le dice que ejecute: ¿ CREATE INDEX lcas_trgm_gin_idx ON lcas USING gist (worksite_city, job_title gist_trgm_ops);Solo un error tipográfico?
bl0b
1
@ErwinBrandstetter Pasó de 30 a 6 segundos. Grandes mejoras! ¡Muchas gracias!
bl0b