Eu estou tentando criar índices parciais para uma tabela estática grande (1.2TB) no Postgres 9.4.
Como meus dados são completamente estáticos, posso inserir todos os dados e criar todos os índices.
Nesta tabela de 1,2 TB, tenho uma coluna chamada run_id
que divide os dados de maneira limpa. Obtivemos um ótimo desempenho ao criar índices que cobrem uma variedade de run_id
s. Aqui está um exemplo:
CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;
Esses índices parciais nos dão a velocidade de consulta desejada. Infelizmente, a criação de cada índice parcial leva cerca de 70 minutos.
Parece que estamos com CPU limitada ( top
está mostrando 100% para o processo).
Existe algo que eu possa fazer para acelerar a criação de nossos índices parciais?
Especificações do sistema:
- 18 core Xeon
- 192GB RAM
- 12 SSDs em RAID
- Os autovacuums estão desativados
- maintenance_work_mem: 64GB (Muito alto?)
Especificações da tabela:
- Tamanho: 1,26 TB
- Número de linhas: 10.537 bilhões
- Tamanho típico do índice: 3,2 GB (existe uma variação de ~ 0,5 GB)
Definição da tabela:
CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))
(Não leia muito os nomes das colunas - eu os ofusquei um pouco.)
Informações de fundo:
- Temos uma equipe separada no local que consome esses dados, mas na verdade existem apenas um ou dois usuários. (Todos esses dados são gerados por meio de uma simulação.) Os usuários só começam a analisar os dados quando as inserções são concluídas e os índices são completamente construídos. Nossa principal preocupação é reduzir o tempo necessário para gerar dados utilizáveis e, no momento, o gargalo é o tempo de criação do índice.
- A velocidade da consulta foi completamente adequada ao usar parciais. Na verdade, acho que poderíamos aumentar o número de execuções que cada índice cobre e ainda manter um desempenho de consulta suficientemente bom.
- Meu palpite é que teremos que particionar a tabela. Estamos tentando esgotar todas as outras opções antes de seguir esse caminho.
run_id
? Distribuído uniformemente? Tamanho do índice resultante no disco? Os dados são estáticos, ok. Mas você é o único usuário?completely static
, então o que você quer dizer com issoWe have a separate team onsite that consumes this data
? Você apenas indexa o intervalorun_id >= 266 AND run_id <= 270
ou a tabela inteira? Qual é a expectativa de vida de cada índice / quantas consultas o usarão? Para quantos valores diferentesrun_id
? Soa como ~ 15 milhões. linhas porrun_id
, o que tornaria cerca de 800 valores diferentes pararun_id
? Por queobj_type_set
,by_s_id
,seq
não definido NOT NULL? Qual porcentagem aproximada de valores NULL para cada um?Respostas:
Índice BRIN
Disponível desde o Postgres 9.5 e provavelmente exatamente o que você está procurando. Criação de índice muito mais rápida, índice muito menor. Mas as consultas normalmente não são tão rápidas. O manual:
Leia mais, há mais.
Depesz fez um teste preliminar.
O ideal para o seu caso: Se você pode escrever linhas agrupadas em
run_id
, seu índice torna-se muito pequeno e criação muito mais barato.Você pode até indexar a tabela inteira .
Layout da tabela
Seja o que for que você faça, você pode salvar 8 bytes perdidos no preenchimento devido a requisitos de alinhamento por linha, ording colunas como esta:
Torna sua tabela 79 GB menor se nenhuma das colunas tiver valores NULL. Detalhes:
Além disso, você possui apenas três colunas que podem ser NULL. O bitmap NULL ocupa 8 bytes para 9 - 72 colunas. Se apenas uma coluna inteira for NULL, existe uma caixa de canto para um paradoxo de armazenamento: seria mais barato usar um valor fictício: 4 bytes desperdiçados, mas 8 bytes salvos por não precisar de um bitmap NULL para a linha. Mais detalhes aqui:
Índices parciais
Dependendo das suas consultas reais, pode ser mais eficiente ter esses cinco índices parciais, em vez do acima:
Execute uma transação para cada.
A remoção
run_id
como coluna de índice dessa maneira salva 8 bytes por entrada de índice - 32 em vez de 40 bytes por linha. Cada índice também é mais barato de criar, mas criar cinco em vez de apenas um leva muito mais tempo para uma tabela grande demais para permanecer no cache (como @ Jürgen e @Chris comentaram). Portanto, isso pode ou não ser útil para você.Particionamento
Com base na herança - a única opção até o Postgres 9.5.
(A nova partição declarativa no Postgres 11 ou, de preferência, 12 é mais inteligente.)
O manual:
Negrito ênfase minha. Consequentemente, estimando 1000 valores diferentes para
run_id
, você faria partições com cerca de 10 valores cada.maintenance_work_mem
Perdi que você já está se ajustando
maintenance_work_mem
na minha primeira leitura. Deixarei citações e conselhos na minha resposta para referência. Por documentação:Eu definiria apenas o valor necessário - o que depende do tamanho do índice desconhecido (para nós). E apenas localmente para a sessão de execução. Como a citação explica, uma configuração geral muito alta pode passar fome do servidor, caso contrário, o vácuo automático também pode exigir mais RAM. Além disso, não defina muito mais do que o necessário, mesmo na sessão de execução, a RAM livre pode ser bem utilizada no cache de dados.
Pode ficar assim:
Sobre
SET LOCAL
:Para medir tamanhos de objeto:
O servidor geralmente deve ser configurado razoavelmente, caso contrário, obviamente.
fonte
Talvez isso seja um excesso de engenharia. Você já tentou usar um único índice completo? Índices parciais cobrindo a tabela inteira juntos não fornecem muito ganho, se houver, para pesquisas de índice e, a partir do seu texto, deduzo que você tenha índices para todos os run_ids? Pode haver algumas vantagens em indexar varreduras com índices parciais, ainda assim eu compararia primeiro a solução simples de um índice.
Para cada criação de índice, você precisa de uma verificação completa de E / S na tabela. Portanto, a criação de vários índices parciais requer muito mais IO lendo a tabela do que para um único índice, embora a classificação se espalhe para o disco para o único índice grande. Se você insistir em índices parciais, poderá tentar construir todos (ou vários) índices ao mesmo tempo em paralelo (se a memória permitir).
Para uma estimativa aproximada de maintenance_work_mem necessária para classificar todos os run_ids, que são bigints de 8 bytes, na memória, você precisará de 10,5 * 8 GB + alguma sobrecarga.
fonte
Você também pode criar os índices em outros espaços de tabela que não o padrão. Esses espaços de tabela podem apontar para discos que não são redundantes (apenas recrie os índices se falharem) ou estão em matrizes mais rápidas.
Você também pode considerar particionar a tabela usando os mesmos critérios que seus índices parciais. Isso permitiria a mesma velocidade que o índice durante a consulta, sem realmente criar nenhum índice.
fonte