Estrutura simples de banco de dados (para um fórum online):
CREATE TABLE users (
id integer NOT NULL PRIMARY KEY,
username text
);
CREATE INDEX ON users (username);
CREATE TABLE posts (
id integer NOT NULL PRIMARY KEY,
thread_id integer NOT NULL REFERENCES threads (id),
user_id integer NOT NULL REFERENCES users (id),
date timestamp without time zone NOT NULL,
content text
);
CREATE INDEX ON posts (thread_id);
CREATE INDEX ON posts (user_id);
Cerca de 80 users
mil entradas e 2,6 milhões de entradas em posts
tabelas. Essa consulta simples para obter os 100 principais usuários por suas postagens leva 2,4 segundos :
EXPLAIN ANALYZE SELECT u.id, u.username, COUNT(p.id) AS PostCount FROM users u
INNER JOIN posts p on p.user_id = u.id
WHERE u.username IS NOT NULL
GROUP BY u.id
ORDER BY PostCount DESC LIMIT 100;
Limit (cost=316926.14..316926.39 rows=100 width=20) (actual time=2326.812..2326.830 rows=100 loops=1)
-> Sort (cost=316926.14..317014.83 rows=35476 width=20) (actual time=2326.809..2326.820 rows=100 loops=1)
Sort Key: (count(p.id)) DESC
Sort Method: top-N heapsort Memory: 32kB
-> HashAggregate (cost=315215.51..315570.27 rows=35476 width=20) (actual time=2311.296..2321.739 rows=34608 loops=1)
Group Key: u.id
-> Hash Join (cost=1176.89..308201.88 rows=1402727 width=16) (actual time=16.538..1784.546 rows=1910831 loops=1)
Hash Cond: (p.user_id = u.id)
-> Seq Scan on posts p (cost=0.00..286185.34 rows=1816634 width=8) (actual time=0.103..1144.681 rows=2173916 loops=1)
-> Hash (cost=733.44..733.44 rows=35476 width=12) (actual time=15.763..15.763 rows=34609 loops=1)
Buckets: 65536 Batches: 1 Memory Usage: 2021kB
-> Seq Scan on users u (cost=0.00..733.44 rows=35476 width=12) (actual time=0.033..6.521 rows=34609 loops=1)
Filter: (username IS NOT NULL)
Rows Removed by Filter: 11335
Execution time: 2301.357 ms
Com set enable_seqscan = false
ainda pior:
Limit (cost=1160881.74..1160881.99 rows=100 width=20) (actual time=2758.086..2758.107 rows=100 loops=1)
-> Sort (cost=1160881.74..1160970.43 rows=35476 width=20) (actual time=2758.084..2758.098 rows=100 loops=1)
Sort Key: (count(p.id)) DESC
Sort Method: top-N heapsort Memory: 32kB
-> GroupAggregate (cost=0.79..1159525.87 rows=35476 width=20) (actual time=0.095..2749.859 rows=34608 loops=1)
Group Key: u.id
-> Merge Join (cost=0.79..1152157.48 rows=1402727 width=16) (actual time=0.036..2537.064 rows=1910831 loops=1)
Merge Cond: (u.id = p.user_id)
-> Index Scan using users_pkey on users u (cost=0.29..2404.83 rows=35476 width=12) (actual time=0.016..41.163 rows=34609 loops=1)
Filter: (username IS NOT NULL)
Rows Removed by Filter: 11335
-> Index Scan using posts_user_id_index on posts p (cost=0.43..1131472.19 rows=1816634 width=8) (actual time=0.012..2191.856 rows=2173916 loops=1)
Planning time: 1.281 ms
Execution time: 2758.187 ms
Agrupar por username
está ausente no Postgres, porque não é necessário (o SQL Server diz que preciso agrupar username
se quiser selecionar o nome de usuário). Agrupar com username
adiciona um pouco de ms ao tempo de execução no Postgres ou não faz nada.
Para fins científicos, instalei o Microsoft SQL Server no mesmo servidor (que executa o archlinux, 8 núcleos xeon, 24 gb de ram, ssd) e migrei todos os dados do Postgres - a mesma estrutura de tabela, os mesmos índices e os mesmos dados. A mesma consulta para obter os 100 melhores pôsteres é executada em 0,3 segundos :
SELECT TOP 100 u.id, u.username, COUNT(p.id) AS PostCount FROM dbo.users u
INNER JOIN dbo.posts p on p.user_id = u.id
WHERE u.username IS NOT NULL
GROUP BY u.id, u.username
ORDER BY PostCount DESC
Produz os mesmos resultados dos mesmos dados, mas é 8 vezes mais rápido. E é a versão beta do MS SQL no Linux, eu acho que rodando em seu sistema operacional "doméstico" - Windows Server - pode ser ainda mais rápido.
Minha consulta ao PostgreSQL está totalmente errada ou o PostgreSQL está lento?
informação adicional
A versão é quase a mais recente (9.6.1, atualmente a mais recente é a 9.6.2, o ArchLinux apenas possui pacotes desatualizados e é muito lento para atualizar). Config:
max_connections = 75
shared_buffers = 3584MB
effective_cache_size = 10752MB
work_mem = 24466kB
maintenance_work_mem = 896MB
dynamic_shared_memory_type = posix
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
EXPLAIN ANALYZE
saídas: https://pastebin.com/HxucRgnk
Tentei todos os índices, usados mesmo GIN e GIST, a maneira mais rápida para o PostgreSQL (e o Google confirma com muitas linhas) usando varredura seqüencial.
MS SQL Server 14.0.405.200-1, padrão conf.
Eu uso isso em uma API (com seleção simples sem análise) e, chamando esse ponto de extremidade da API com o Chrome, ele diz que leva 2500 ms + -, adicione 50 ms de sobrecarga de HTTP e sobrecarga do servidor da Web (API e SQL executadas no mesmo servidor) - é o mesmo. Eu não me importo com 100 ms aqui ou ali, o que me interessa são dois segundos inteiros.
explain analyze SELECT user_id, count(9) FROM posts group by user_id;
leva 700 ms. O tamanho da posts
tabela é 2154 MB.
GROUP BY u.id
para isso GROUP BY p.user_id
e tentar aquilo? Meu palpite é que o Postgres se junta primeiro e agrupa por segundo porque você está agrupando por identificador de tabela de usuários, mesmo que você precise apenas postar user_id para obter as N-linhas principais.
posts
tabela, usando uma tabela comoCREATE TABLE post_content (post_id PRIMARY KEY REFERENCES posts (id), content text);
Dessa forma, a maioria das E / S 'desperdiçadas' nesse tipo de consulta pode ser poupada. Se as postagens forem menores que isso, umVACUUM FULL
onposts
pode ajudar.