PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Correspondência de partição avançada para junção de partição


Anteriormente, eu escrevi um blog sobre junção por partição no PostgreSQL. Nesse blog, falei sobre uma técnica avançada de correspondência de partição que permitirá que a junção de partição seja usada em mais casos. Neste blog vamos discutir esta técnica em detalhes.

Para recapitular, a técnica básica de correspondência de partição permite que uma junção entre duas tabelas particionadas seja realizada usando a técnica de junção por partição se as duas tabelas particionadas tiverem limites de partição exatamente correspondentes, por exemplo tabelas particionadas prt1 e prt2 descritas abaixo
psql> \d+ prt1
... [output clipped]
Partition key: RANGE (a)
Partitions: prt1_p1 FOR VALUES FROM (0) TO (5000),
prt1_p2 FOR VALUES FROM (5000) TO (15000),
prt1_p3 FOR VALUES FROM (15000) TO (30000)

e
psql>\d+ prt2
... [ output clipped ]
Partition key: RANGE (b)
Partitions: prt2_p1 FOR VALUES FROM (0) TO (5000),
prt2_p2 FOR VALUES FROM (5000) TO (15000),
prt2_p3 FOR VALUES FROM (15000) TO (30000)

Uma junção entre prt1 e prt2 em sua chave de partição (a) é dividida em junções entre suas partições correspondentes, ou seja, prt1_p1 junta prt2_p1, prt1_p2 junta prt2_p2 e prt1_p3 junta prt2_p3. Os resultados dessas três junções formam o resultado da junção entre prt1 e prt2. Isso tem muitas vantagens, conforme discutido no meu blog anterior. No entanto, a correspondência de partição básica não pode unir duas tabelas particionadas com limites de partição diferentes. No exemplo acima, se prt1 tiver uma partição extra prt1_p4 FOR VALUES FROM (30000) TO (50000), a correspondência de partição básica não ajudaria a converter uma junção entre prt1 e prt2 em uma junção de partição, pois eles não têm partição exatamente correspondente limites.

Muitos aplicativos usam partições para segregar dados usados ​​ativamente e dados obsoletos, uma técnica que discuti em outro blog. Os dados obsoletos são eventualmente removidos pela eliminação de partições. Novas partições são criadas para acomodar dados novos. Uma junção entre duas dessas tabelas particionadas usará principalmente a junção por partição, pois na maioria das vezes elas terão partições correspondentes. Mas quando uma partição ativa é adicionada a uma dessas tabelas ou uma antiga é excluída, seus limites de partição não corresponderão até que a outra tabela também passe por uma operação semelhante. Durante esse intervalo, uma junção entre essas duas tabelas não usará a junção por partição e pode demorar muito mais para ser executada. Não queremos que uma junção que atinja o banco de dados durante essa pequena duração tenha um desempenho ruim, pois não pode usar a junção de partição. O algoritmo avançado de correspondência de partição ajuda neste e em casos mais complicados em que os limites de partição não correspondem exatamente.

Algoritmo avançado de correspondência de partição


A técnica avançada de correspondência de partição encontra partições correspondentes de duas tabelas particionadas, mesmo quando seus limites de partição não correspondem exatamente. Ele encontra partições correspondentes comparando os limites de ambas as tabelas em sua ordem de classificação semelhante ao algoritmo de junção de mesclagem. Quaisquer duas partições, uma de cada tabela particionada, cujos limites correspondam exatamente ou se sobreponham, são consideradas como parceiros de junção, pois podem conter linhas de junção. Continuando com o exemplo acima, digamos que uma nova partição ativa prt2_p4 seja adicionada ao prt4. As tabelas particionadas agora se parecem com:
psql>\d+ prt1
... [output clipped]
Partition key: RANGE (a)
Partitions: prt1_p1 FOR VALUES FROM (0) TO (5000),
prt1_p2 FOR VALUES FROM (5000) TO (15000),
prt1_p3 FOR VALUES FROM (15000) TO (30000)

e
psql>\d+ prt2
... [ output clipped ]
Partition key: RANGE (b)
Partitions: prt2_p1 FOR VALUES FROM (0) TO (5000),
prt2_p2 FOR VALUES FROM (5000) TO (15000),
prt2_p3 FOR VALUES FROM (15000) TO (30000),
prt2_p4 FOR VALUES FROM (30000) TO (50000)

É fácil ver que os limites de partição de prt1_p1 e prt2_p1, prt1_p2 e prt2_p2 e prt1_p3 e prt2_p3 correspondem respectivamente. Mas, ao contrário da correspondência de partição básica, a correspondência de partição avançada saberá que prt2_p4 não possui nenhuma partição correspondente em prt1. Se a junção entre prt1 e prt2 for uma junção INNER ou quando prt2 for uma relação INNER na junção, o resultado da junção não terá nenhuma linha de prt2_p4. Habilitado com informações detalhadas sobre as partições correspondentes e partições que não correspondem, contra apenas se os limites da partição correspondem ou não, o otimizador de consulta pode decidir se deve usar a junção de partição ou não. Nesse caso, ele escolherá executar a junção como junção entre as partições correspondentes, deixando de lado o prt2_p4. Mas isso não é muito parecido com uma correspondência de partição "avançada". Vamos ver um caso um pouco mais complicado usando tabelas particionadas por lista desta vez:
psql>\d+ plt1
Partition key: LIST (c)
Partitions: plt1_p1 FOR VALUES IN ('0001', '0003'),
plt1_p2 FOR VALUES IN ('0004', '0006'),
plt1_p3 FOR VALUES IN ('0008', '0009')

e
psql>\d+ plt2
Partition key: LIST (c)
Partitions: plt2_p1 FOR VALUES IN ('0002', '0003'),
plt2_p2 FOR VALUES IN ('0004', '0006'),
plt2_p3 FOR VALUES IN ('0007', '0009')

Observe que há exatamente três partições em ambas as relações, mas as listas de valores de partição diferem. A lista correspondente à partição plt1_p2 corresponde exatamente à de plt2_p2. Fora isso, não há duas partições, uma de cada lado, com listas exatamente correspondentes. O algoritmo avançado de correspondência de partições deduz que plt1_p1 e plt2_p1 têm listas sobrepostas e suas listas não se sobrepõem a nenhuma outra partição da outra relação. Da mesma forma para plt1_p3 e plt2_p3. O otimizador de consulta então vê que a junção entre plt1 e plt2 pode ser executada como junção de partição unindo as partições correspondentes, ou seja, plt1_p1 e plt2_p1, plt1_p2 e plt2_p2, e plt1_p3 e plt2_p3, respectivamente. O algoritmo pode encontrar partições correspondentes em conjuntos de listas vinculados a partições ainda mais complexos, bem como tabelas particionadas por intervalo. Mas não vamos cobri-los por uma questão de brevidade. Leitores interessados ​​e mais ousados ​​podem dar uma olhada no commit. Ele também possui muitos casos de teste, que mostram vários cenários em que o algoritmo avançado de correspondência de partição é usado.

Limitações

Uniões externas com partições correspondentes ausentes no lado interno


As junções externas representam um problema particular no mundo PostgreSQL. Considere prt2 LEFT JOIN prt1, no exemplo acima, onde prt2 é uma relação OUTER. prt2_p4 não tem um parceiro de junção em prt1 e, no entanto, as linhas nessa partição devem fazer parte do resultado da junção, pois pertencem à relação externa. No PostgreSQL, quando o lado INNER de uma junção está vazio, ele é representado por uma relação "dummy" que não emite linhas, mas ainda conhece o esquema dessa relação. Normalmente, uma relação "fictícia" surge de uma relação não fictícia que não emitirá nenhuma linha por causa de alguma otimização de consulta, como exclusão de restrição. O otimizador de consultas do PostgreSQL marca uma relação não fictícia como fictícia e o executor procede normalmente ao executar tal junção. Mas quando não há partição interna correspondente para uma partição externa, não há "entidade existente" que possa ser marcada como "fictícia". Por exemplo, neste caso não há prt1_p4 que possa representar uma partição interna fictícia que une o prt2_p4 externo. No momento, o PostgreSQL não tem como "criar" essas relações "fictícias" durante o planejamento. Portanto, o otimizador de consulta não usa junção de partição nesse caso.

Idealmente, tal junção com interior vazio requer apenas o esquema da relação interna e não uma relação inteira. Esse esquema pode ser derivado da própria tabela particionada. Tudo o que precisa é a capacidade de produzir a linha de junção usando as colunas de uma linha no lado externo unidas por valores NULL para as colunas do lado interno. Uma vez que tenhamos essa capacidade no PostgreSQL, o otimizador de consultas poderá usar a junção por partição mesmo nesses casos.

Deixe-me enfatizar que as junções externas onde não há partições ausentes na junção interna usam junção por partição.

Várias partições correspondentes


Quando as tabelas são particionadas de forma que várias partições de um lado correspondam a uma ou mais partições do outro lado, a junção por partição não pode ser usada, pois não há como induzir uma relação "Anexar" durante o tempo de planejamento que representa duas ou mais partições juntas. Esperamos remover essa limitação também em algum momento e permitir que a junção por partição também seja usada nesses casos.

Tabelas particionadas por hash


Os limites de partição de duas tabelas particionadas por hash usando o mesmo módulo sempre correspondem. Quando o módulo é diferente, uma linha de uma determinada partição de uma tabela pode ter seus parceiros de junção em muitas das partições da outra, portanto, uma determinada partição de um lado corresponde a várias partições da outra tabela, tornando a junção por partição ineficaz.

Quando o algoritmo avançado de correspondência de partições falha em encontrar partições correspondentes ou a junção de partição não pode ser usada devido às limitações acima, o PostgreSQL retorna para unir as tabelas particionadas como tabelas regulares.

Tempo de correspondência de partição avançada


Simon trouxe um ponto interessante ao comentar sobre o recurso. As partições de uma tabela particionada não mudam com frequência, portanto, o resultado da correspondência de partição avançada deve permanecer o mesmo por mais tempo. Calculá-lo toda vez que uma consulta envolvendo essas tabelas é executada é desnecessária. Em vez disso, podemos salvar o conjunto de partições correspondentes em algum catálogo e atualizá-lo sempre que as partições forem alteradas. Isso é um pouco trabalhoso, mas vale a pena o tempo gasto na correspondência da partição para cada consulta.

Mesmo com todas essas limitações, o que temos hoje é uma solução muito útil que atende a maioria dos casos práticos. Escusado será dizer que este recurso funciona perfeitamente com FDW join push down melhorando os recursos de sharding que o PostgreSQL já possui!