Estou gerenciando uma tabela com vários milhões de registros que estão sendo inseridos em tempo real. Uma parte do meu aplicativo precisa exibir as últimas N linhas inseridas, portanto, no início, apenas consultei:
select id, logdate, content from measurements order by logdate DESC limit 500;
Alguns dias depois, descobri que era mais rápido definir id
como be (para este exemplo) 10000000000 - extract(epoch from logdate)
e usá-lo como PRIMARY KEY
, então
select id, date, content from measurements limit 500;
iria naturalmente ordenar por id
, produzindo assim os registros mais recentes.
À medida que a tabela crescia, ela se tornava incontrolável, então recorri ao particionamento. Eu fiz exatamente como a documentação diz :
CREATE TABLE measurement_y2007m11 (
CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
) INHERITS (measurement);
CREATE TABLE measurement_y2007m12 (
CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
) INHERITS (measurement);
CREATE TABLE measurement_y2008m01 (
CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
) INHERITS (measurement);
e distribuí as linhas existentes em suas respectivas partições.
O problema é que estou particionando em logdate
, mas ao consultar não sei o intervalo de tempo. Quando consulto as últimas 500 linhas, não consigo saber se são da semana passada, do último mês do último trimestre. Portanto, o planejador de consulta sempre verifica todas as partições.
Não acredito que ninguém tenha lidado com esse mesmo problema antes, parece trivial e, no entanto, me deixa intrigado.
Equívoco 1: "Ordem natural"
Não há ordem natural em uma
SELECT
declaração. SemORDER BY
você obter linhas em ordem arbitrária. Geralmente, essa será a ordem mais barata na qual o Postgres pode satisfazer sua consulta, ou seja, a ordem na qual as tuplas são armazenadas fisicamente ou na qual são recuperadas após uma consulta de índice. Mas não há garantia alguma. Se sua declaração parecia funcionar, isso foi pura sorte/coincidência e pode quebrar a qualquer momento.Em vez disso, use sua primeira consulta . Se
logdate
for, de fato, do tipodate
, ou se você precisar ter certeza, deverá adicionar maisORDER BY
itens para desempate e obter uma ordem de classificação estável. Se você não se importa, anexe sua (nova) chave primária (veja abaixo):measurement-id
Se for garantido que a linha mais recente (maior ) terá o mais recentelogdate
, você pode apenasORDER BY measurement_id DESC
, mas não considere isso garantido. Em um ambiente multiusuário, uma linha com mais tardelogdate
pode ser gravada antes de outra linha com mais cedologdate
.Esta é uma das razões pelas quais sua ideia para a nova chave primária não é muito útil:
A outra razão: está fadado a falhar mais cedo ou mais tarde se
logdate
não for garantido que seja único - o que provavelmente não é.Em vez disso, use uma
serial
colunameasurement_id
como chave primária. Oubigserial
se você espera mais de 2147483647 linhas ao longo do tempo.Índice
Você afirma que fez exatamente como diz a documentação, e a documentação diz :
E mais abaixo:
A única pequena diferença: o exemplo no manual usa a forma singular mais sensata para o nome da tabela:
measurement
em vez de.measurements
Se você seguir meu conselho:
Faça aquilo:
Mais sobre por que isso provavelmente ajudaria:
Equívoco 2: "verificar todas as partições"
O planejador de consulta planejará verificar todas as partições em sequência. Porém, assim que a consulta for satisfeita (500 linhas recuperadas), ela interromperá a execução. Teste com
EXPLAIN ANALYZE
, você verá a anotação(never executed)
atrás das partições restantes.Se o planejador não for inteligente o suficiente para derivar a melhor sequência para digitalizar a partir de sua configuração (não é possível testar agora), você pode ajudar com uma
UNION ALL
consulta nas partições:Mas isso pode não ser necessário.