Banco de dados do Messenger (parte 2): dividimos "lucro"

Projetamos com sucesso a estrutura do nosso banco de dados PostgreSQL para armazenar correspondência, um ano se passou, os usuários estão preenchendo-o ativamente, agora ele possui milhões de registros e ... algo começou a desacelerar.



O fato é que , à medida que a tabela cresce, a “profundidade” dos índices também aumenta - embora logaritmicamente. Porém, com o tempo, isso força o servidor a processar muitas páginas de dados para executar as mesmas tarefas de leitura / gravação do que no início.

É aqui que o particionamento vem em socorro.

Observo que isso não será sobre sharding, ou seja, a distribuição de dados entre diferentes bancos de dados ou servidores. Porque, mesmo dividindo os dados em vários servidores, você não pode se livrar do problema de "aumento" de índices ao longo do tempo. É claro que, se você puder contratar um novo servidor todos os dias, seus problemas não estarão mais no plano de um banco de dados específico.

Consideraremos scripts não específicos para implementar o particionamento “no hardware”, mas a abordagem em si - o que e como “cortar em fatias” e o que esse desejo leva.

Conceito


Mais uma vez, definimos nosso objetivo: queremos garantir que hoje, amanhã e depois de um ano, o número de dados do PostgreSQL lidos durante qualquer operação de leitura / gravação permaneça aproximadamente o mesmo.

Para qualquer dado cronologicamente acumulado (mensagens, documentos, logs, arquivos, ...), a escolha natural como chave de partição é a data / hora do evento . No nosso caso, esse evento é o momento em que a mensagem foi enviada .

Observe que os usuários quase sempre trabalham apenas com os dados "mais recentes" - eles lêem as mensagens mais recentes, analisam os registros mais recentes ... Não, é claro, eles podem voltar mais no tempo, apenas raramente.

A partir dessas restrições, torna-se óbvio que as seções "diárias" serão a melhor solução para as mensagens - afinal, nosso usuário quase sempre lerá o que lhe chegou "hoje" ou "ontem".

Se escrevermos e lemos quase apenas uma seção durante o dia, isso nos dará um uso ainda mais eficiente da memória e do disco - já que todos os índices de seção se encaixam facilmente na RAM, ao contrário das seções "grande e negrito" de toda a tabela.

passo a passo


Em geral, tudo isso parece um lucro sólido. E é possível, mas por isso teremos que nos esforçar bastante - porque a decisão de particionar uma das entidades leva à necessidade de "cortar" e associá-la .

Mensagem, suas propriedades e projeções


Como decidimos cortar as mensagens por datas, é razoável dividir as propriedades das entidades (arquivos anexados, listas de discussão) e também pela data da mensagem , dependendo delas.

Como uma de nossas tarefas típicas é apenas visualizar os registros de mensagens (não lidos, recebidos, todos), também é lógico atraí-los para o particionamento pelas datas das mensagens.


Adicione a chave da partição (data da mensagem) a todas as tabelas: destinatários, arquivo, registros. Você não pode adicionar à própria mensagem, mas use o DateTime existente.

Temas


Como o tópico é um em várias mensagens, não será possível "cortá-lo" no mesmo modelo; é preciso confiar em outra coisa. No nosso caso, a data da primeira mensagem em correspondência é ideal - ou seja, o momento da criação do próprio tópico.


Adicione a chave da partição (data do tópico) a todas as tabelas: tópico, participante.

Mas agora temos dois problemas ao mesmo tempo:

  • em qual seção procurar postagens sobre o tópico?
  • em qual seção procurar um tópico de uma mensagem?

É claro que você pode continuar pesquisando em todas as seções, mas será muito triste e negará todos os nossos ganhos. Portanto, para saber exatamente onde procurar, criaremos links / indicadores lógicos para as seções:

  • na mensagem, adicione um campo com a data do tópico
  • adicione ao tópico um conjunto de datas da mensagem para essa correspondência (você pode usar uma tabela separada ou uma matriz de datas)



Como haverá poucas modificações na lista de datas das mensagens para cada correspondência individual (afinal, quase todas as mensagens se enquadram nos próximos 1-2 dias), vou me debruçar sobre essa opção.

No total, a estrutura de nossa base assumiu a seguinte forma, levando em consideração o particionamento:

Tabelas: RU, se você não gosta de cirílico, é melhor não olhar para os nomes das tabelas / campos
--     CREATE TABLE "_YYYYMMDD"( "" uuid PRIMARY KEY , "" uuid , "" date , "" uuid , "" --    timestamp , "" text ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid , "" uuid , PRIMARY KEY("", "") ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid PRIMARY KEY , "" uuid , "BLOB" uuid , "" text ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid , "" smallint , "" timestamp , "" uuid , PRIMARY KEY("", "", "") ); CREATE INDEX ON "_YYYYMMDD"("", "", "" DESC); --     CREATE TABLE "_YYYYMMDD"( "" date , "" uuid PRIMARY KEY , "" uuid , "" text ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid , "" uuid , PRIMARY KEY("", "") ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid PRIMARY KEY , "" date ); 


Economize um centavo bonito


Bem, se não usarmos a opção de particionamento clássico com base na distribuição de valores de campo (por meio de gatilhos e herança ou PARTITION BY), mas "manualmente" no nível do aplicativo, podemos ver que o valor da chave de particionamento já está armazenado no nome da própria tabela.

Portanto, se você está preocupado com a quantidade de dados armazenados , pode se livrar desses campos "extras" e consultar tabelas específicas. É verdade que todas as amostras de várias seções neste caso terão que ser enviadas para o lado do aplicativo.

Source: https://habr.com/ru/post/pt483170/


All Articles