검색 문자열이 길어질수록 트라이 그램 검색이 훨씬 느려집니다.


16

Postgres 9.1 데이터베이스 table1에는 ~ 1.5M 개의 행과 열 label(이 질문을 위해 단순화 된 이름) 이있는 테이블 이 있습니다.

기능적인 trigram-index가 있습니다 lower(unaccent(label))( 색인에서 unaccent()사용할 수 있도록 불변이되었습니다).

다음 쿼리는 매우 빠릅니다.

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')));
 count 
-------
     1
(1 row)

Time: 394,295 ms

그러나 다음 쿼리는 느립니다.

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword and some more%')));
 count 
-------
     1
(1 row)

Time: 1405,749 ms

검색이 더 엄격하더라도 더 많은 단어를 추가하면 속도가 더 느려집니다.

첫 번째 단어에 대한 하위 쿼리를 실행 한 다음 전체 검색 문자열로 쿼리를 실행하는 간단한 트릭을 시도했지만 쿼리 계획자가 내 기계 작업을 통해 보았습니다.

EXPLAIN ANALYZE
SELECT * FROM (
   SELECT id, title, label from table1
   WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
   ) t1
WHERE lower(unaccent(label)) like lower(unaccent('%someword and some more%'));
table1의 비트 맵 힙 스캔 (비용 = 16216.01..16220.04 행 = 1 너비 = 212) (실제 시간 = 1824.017..1824.019 행 = 1 루프 = 1)
  Cond를 다시 확인하십시오 : ((lower (unaccent ((label) :: text)) ~~ '% someword %':: text) AND (lower (unaccent ((label) :: text)) ~~ '% someword 그리고 더 많은 %'::본문))
  -> table1_label_hun_gin_trgm의 비트 맵 인덱스 스캔 (비용 = 0.00..16216.01 행 = 1 너비 = 0) (실제 시간 = 1823.900..1823.900 행 = 1 루프 = 1)
        인덱스 조건 : ((낮은 (unccent ((label) :: text)) ~~ '% someword %':: text) AND (낮은 (unaccent ((label) :: text)) ~~ '% someword 및 더 많은 것 %'::본문))
총 런타임 : 1824.064ms

내 궁극적 인 문제는 검색 문자열이 웹 인터페이스에서 제공되어 상당히 긴 문자열을 보낼 수 있으므로 상당히 느리고 DOS 벡터를 구성 할 수 있다는 것입니다.

그래서 내 질문은 :

  • 쿼리 속도를 높이는 방법?
  • 더 빨리 하위 쿼리로 나누는 방법이 있습니까?
  • 아마도 Postgres의 이후 버전이 더 좋을까요? (9.4를 시도했는데 더 빨리 보이지 않습니다. 여전히 같은 효과입니다. 이후 버전일까요?)
  • 다른 인덱싱 전략이 필요할 수 있습니까?

1
unaccent()추가 모듈에서도 제공되며 Postgres는 기본적으로 함수에 대한 인덱스를 지원 하지 않으므로 언급 하지 않아야 합니다 IMMUTABLE. 당신은 무언가를 변경 했어야하고 당신은 당신의 질문에서 정확히 무엇을했는지 언급해야합니다. 내 조언 : stackoverflow.com/a/11007216/939860 . 또한 트라이 그램 인덱스는 대소 문자를 구분하지 않고 일치하는 기능을 지원합니다. WHERE f_unaccent(label) ILIKE f_unaccent('%someword%')일치하는 색인을 사용하여 다음을 단순화 할 수 있습니다 . 세부 사항 : stackoverflow.com/a/28636000/939860 .
Erwin Brandstetter

나는 단순히 unaccent불변 이라고 선언했다 . 나는 이것을 질문에 추가했다.
P.Péter

unaccent모듈 을 업데이트하면 해킹을 덮어 씁니다 . 대신 함수 래퍼를 제안하는 이유 중 하나입니다.
Erwin Brandstetter

답변:


34

PostgreSQL 9.6에는 새로운 버전의 pg_trgm, 1.2가 추가 될 예정입니다. 약간의 노력으로 PostgreSQL 9.4에서이 새 버전을 사용할 수 있습니다 (패치를 적용하고 확장 모듈을 직접 컴파일하여 설치해야 함).

가장 오래된 버전은 쿼리에서 각 트라이 그램을 검색하여 통합하고 필터를 적용하는 것입니다. 새 버전은 쿼리에서 가장 희귀 한 트라이 그램을 선택하고 그 중 하나만 검색 한 다음 나머지를 필터링하는 것입니다.

이를 수행하는 기계는 9.1에 존재하지 않습니다. 9.4에서 기계가 추가되었지만 pg_trgm은 당시 기계를 사용하도록 조정되지 않았습니다.

악의적 인 사람은 일반적인 삼각법 만있는 쿼리를 작성할 수 있으므로 여전히 DOS 문제가 발생할 수 있습니다. '% and %'또는 심지어 '% a %'


pg_trgm 1.2로 업그레이드 할 수없는 경우 플래너를 속이는 다른 방법은 다음과 같습니다.

WHERE (lower(unaccent(label)) like lower(unaccent('%someword%'))) 
AND   (lower(unaccent(label||'')) like 
      lower(unaccent('%someword and some more%')));

빈 문자열을 레이블로 연결하면 planner가 where 절의 해당 부분에서 인덱스를 사용할 수 없다고 생각하게합니다. 따라서 % someword %에서만 인덱스를 사용하고 해당 행에만 필터를 적용합니다.


또한 항상 전체 단어를 검색하는 경우 함수를 사용하여 문자열을 단어 배열로 토큰 화하고 해당 배열 반환 함수에서 pg_trgm이 아닌 일반 내장 GIN 색인을 사용할 수 있습니다.


13
당신이 패치를 작성했다고 언급 할 가치가 있습니다. 예비 성능 테스트는 인상적입니다. 이것은 실제로 더 많은 공감대를 가질 가치가 있습니다 (현재 버전에 대한 설명과 해결 방법).
Erwin Brandstetter

9.1에는 없었던 패치를 구현하는 데 사용한 기계에 대한 참조에 더 관심이 있습니다. 그러나 Erwin의 나쁜 엉덩이 대답에 동의합니다.
Evan Carroll

3

쿼리 플래너를 사기위한 방법을 찾았습니다. 아주 간단한 해킹입니다.

SELECT *
FROM (
   select id, title, label
   from   table1
   where  lower(unaccent(label)) like lower(unaccent('%someword%'))
   ) t1
WHERE lower(lower(unaccent(label))) like lower(unaccent('%someword and more%'))

EXPLAIN 산출:

table1의 비트 맵 힙 스캔 (비용 = 6749.11..7332.71 rows = 1 width = 212) (실제 시간 = 256.607..256.609 rows = 1 루프 = 1)
  Cond를 다시 확인하십시오 : (lower (unccent ((label_hun) :: text)) ~~ '% someword %':: text)
  필터 : (낮은 (낮은 (unccent ((label) :: text))) ~~ '% someword and some more %':: text)
  -> table1_label_hun_gin_trgm의 비트 맵 인덱스 스캔 (비용 = 0.00..6749.11 행 = 147 폭 = 0) (실제 시간 = 256.499..256.499 행 = 1 루프 = 1)
        인덱스 조건 : (낮은 (unccent ((label) :: text)) ~~ '% someword %':: text)
총 런타임 : 256.653ms

에 대한 색인이 없으므로 lower(lower(unaccent(label)))순차적 스캔을 생성하므로 간단한 필터로 바뀝니다. 또한 간단한 AND도 동일한 작업을 수행합니다.

SELECT id, title, label
FROM table1
WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
AND   lower(lower(unaccent(label))) like lower(unaccent('%someword and more%'))

물론 이것은 인덱스 스캔에 사용 된 컷 아웃 부분이 매우 일반적인 경우 휴리스틱으로 잘 작동하지 않을 수 있습니다 . 그러나 우리 데이터베이스에는 약 10-15자를 사용하면 실제로 그렇게 많은 반복이 없습니다.

남은 두 가지 작은 질문이 있습니다.

  • postgres가 이와 같은 것이 유익하다는 것을 알 수없는 이유는 무엇입니까?
  • postgres는 0..256.499 시간 범위에서 무엇을합니까 (출력 분석 참조)?

1
0에서 256.499 사이의 시간 범위에서 비트 맵을 작성합니다. 256.499에서 첫 번째 출력 인 비트 맵이 생성됩니다. 단일 출력 (단일 완료된 비트 맵) 만 생성하므로 마지막 출력이기도합니다.
jjanes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.