Estou tentando obter o recurso de pesquisa de texto completo do postgres funcional.
Tenho duas tabelas, uma que criei só para teste, e a real que quero poder pesquisar:
Tabela de teste:
webarchive=# \d test_sites
Table "public.test_sites"
Column | Type | Modifiers
-------------+----------+---------------------------------------------------------
id | integer | not null default nextval('test_sites_id_seq'::regclass)
content | text |
tsv_content | tsvector |
Indexes:
"test_sites_pkey" PRIMARY KEY, btree (id)
"idx_test_web_pages_content" gin (tsv_content)
Triggers:
web_pages_testing_content_change_trigger AFTER INSERT OR UPDATE ON test_sites FOR EACH ROW EXECUTE PROCEDURE web_pages_testing_content_update_func()
Tabela "Real":
webarchive=# \d web_pages
Table "public.web_pages"
Column | Type | Modifiers
--------------+-----------------------------+--------------------------------------------------------
id | integer | not null default nextval('web_pages_id_seq'::regclass)
state | dlstate_enum | not null
errno | integer |
url | text | not null
starturl | text | not null
netloc | text | not null
file | integer |
priority | integer | not null
distance | integer | not null
is_text | boolean |
limit_netloc | boolean |
title | citext |
mimetype | text |
type | itemtype_enum |
raw_content | text |
content | text |
fetchtime | timestamp without time zone |
addtime | timestamp without time zone |
tsv_content | tsvector |
Indexes:
"web_pages_pkey" PRIMARY KEY, btree (id)
"ix_web_pages_url" UNIQUE, btree (url)
"idx_web_pages_content" gin (tsv_content)
"idx_web_pages_title" gin (to_tsvector('english'::regconfig, title::text))
"ix_web_pages_distance" btree (distance)
"ix_web_pages_distance_filtered" btree (priority) WHERE state = 'new'::dlstate_enum AND distance < 1000000
"ix_web_pages_priority" btree (priority)
"ix_web_pages_type" btree (type)
"ix_web_pages_url_ops" btree (url text_pattern_ops)
Foreign-key constraints:
"web_pages_file_fkey" FOREIGN KEY (file) REFERENCES web_files(id)
Triggers:
web_pages_content_change_trigger AFTER INSERT OR UPDATE ON web_pages FOR EACH ROW EXECUTE PROCEDURE web_pages_content_update_func()
Bits extras à parte, ambos têm uma content
coluna e uma tsv_content
coluna com um gin()
índice. Existe um gatilho que atualiza a tsv_content
coluna toda vez que a content
coluna é modificada.
Observe que o outro gin
índice funciona bem e, na verdade, inicialmente também tinha um gin (to_tsvector('english'::regconfig, content::text))
índice na coluna de conteúdo, em vez da segunda coluna, mas depois de esperar que esse índice fosse reconstruído algumas vezes nos testes, decidi usar uma coluna separada para pré-armazene os valores do tsvector.
A execução de uma consulta na tabela de teste usa o índice como eu esperaria:
webarchive=# EXPLAIN ANALYZE SELECT
test_sites.id,
test_sites.content,
ts_rank_cd(test_sites.tsv_content, to_tsquery($$testing$$)) AS ts_rank_cd_1
FROM
test_sites
WHERE
test_sites.tsv_content @@ to_tsquery($$testing$$);
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on test_sites (cost=16.45..114.96 rows=25 width=669) (actual time=0.175..3.720 rows=143 loops=1)
Recheck Cond: (tsv_content @@ to_tsquery('testing'::text))
Heap Blocks: exact=117
-> Bitmap Index Scan on idx_test_web_pages_content (cost=0.00..16.44 rows=25 width=0) (actual time=0.109..0.109 rows=143 loops=1)
Index Cond: (tsv_content @@ to_tsquery('testing'::text))
Planning time: 0.414 ms
Execution time: 3.800 ms
(7 rows)
No entanto, a mesma consulta exata na tabela real nunca parece resultar em nada além de uma simples varredura sequencial:
webarchive=# EXPLAIN ANALYZE SELECT
web_pages.id,
web_pages.content,
ts_rank_cd(web_pages.tsv_content, to_tsquery($$testing$$)) AS ts_rank_cd_1
FROM
web_pages
WHERE
web_pages.tsv_content @@ to_tsquery($$testing$$);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Seq Scan on web_pages (cost=0.00..4406819.80 rows=19751 width=505) (actual time=0.343..142325.954 rows=134949 loops=1)
Filter: (tsv_content @@ to_tsquery('testing'::text))
Rows Removed by Filter: 12764373
Planning time: 0.436 ms
Execution time: 142341.489 ms
(5 rows)
Aumentei minha memória de trabalho para 3 GB para ver se esse era o problema, e não é.
Além disso, deve-se observar que essas tabelas são bastante grandes - ~ 150 GB de texto em 4 milhões de linhas (com 8 milhões de linhas adicionais em que content
/ tsv_content
é NULL
).
A test_sites
tabela tem 1/1000 das linhas de web_pages
, pois é um pouco proibitivo experimentar quando cada consulta leva vários minutos.
Estou usando o postgresql 9.5 (sim, eu mesmo compilei, queria ON CONFLICT
). Não parece haver uma etiqueta para isso ainda.
Eu li os problemas em aberto com o 9.5 e não consigo ver isso como resultado de nenhum deles.
Recém-saído de uma reconstrução completa do índice, o problema ainda existe:
webarchive=# ANALYZE web_pages ;
ANALYZE
webarchive=# EXPLAIN ANALYZE SELECT
web_pages.id,
web_pages.content,
ts_rank_cd(web_pages.tsv_content, to_tsquery($$testing$$)) AS ts_rank_cd_1
FROM
web_pages
WHERE
web_pages.tsv_content @@ to_tsquery($$testing$$);
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Seq Scan on web_pages (cost=10000000000.00..10005252343.30 rows=25109 width=561) (actual time=7.114..146444.168 rows=134949 loops=1)
Filter: (tsv_content @@ to_tsquery('testing'::text))
Rows Removed by Filter: 13137318
Planning time: 0.521 ms
Execution time: 146465.188 ms
(5 rows)
Observe que eu literalmente acabei ANALYZE
de editar e o seqscan está desativado.
Bem, gastei um pouco de tempo criando espaço extra no disco com o banco de dados, movendo alguns outros bancos de dados para outro SSD.
Em seguida, percorri
VACUUM ANALYZE
todo o banco de dados e agora, aparentemente, percebi que tenho o arquivo index.Anteriormente, eu havia analisado e aspirado apenas esta tabela , mas aparentemente, de alguma forma, fez diferença fazê-lo em geral e não em uma tabela específica. Vai saber.
Aproveitei também para rodar um
VACUUM FULL;
agora que tenho espaço suficiente para o processamento. Eu tive um pouco de rotatividade de linhas na tabela enquanto experimentei durante o desenvolvimento e gostaria de tentar consolidar qualquer fragmentação de arquivo resultante disso.