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

Comparando armazenamentos de dados para PostgreSQL - MVCC vs InnoDB


Um dos principais requisitos para qualquer banco de dados é alcançar a escalabilidade. Isso só pode ser alcançado se a contenção (bloqueio) for minimizada o máximo possível, se não for removida totalmente. Como leitura/gravação/atualização/exclusão são algumas das principais operações frequentes que ocorrem no banco de dados, portanto, é muito importante que essas operações ocorram simultaneamente sem serem bloqueadas. Para conseguir isso, a maioria dos principais bancos de dados emprega um modelo de simultaneidade chamado Controle de simultaneidade de várias versões, que reduz a contenção a um nível mínimo.

O que é MVCC


O controle de simultaneidade de várias versões (daqui em diante MVCC) é um algoritmo para fornecer controle de simultaneidade fino mantendo várias versões do mesmo objeto para que a operação READ e WRITE não entre em conflito. Aqui WRITE significa UPDATE e DELETE, pois o registro recém-INSERTADO será protegido de acordo com o nível de isolamento. Cada operação WRITE produz uma nova versão do objeto e cada operação de leitura simultânea lê uma versão diferente do objeto dependendo do nível de isolamento. Como ler e escrever, ambos operam em versões diferentes do mesmo objeto, portanto, nenhuma dessas operações é necessária para bloquear completamente e, portanto, ambas podem operar simultaneamente. O único caso em que a contenção ainda pode existir é quando duas transações simultâneas tentam ESCREVER o mesmo registro.

A maioria do banco de dados principal atual suporta MVCC. A intenção desse algoritmo é manter várias versões do mesmo objeto, de modo que a implementação do MVCC difere de banco de dados para banco de dados apenas em termos de como várias versões são criadas e mantidas. Assim, a operação do banco de dados correspondente e o armazenamento de dados mudam.

A abordagem mais reconhecida para implementar MVCC é a usada pelo PostgreSQL e Firebird/Interbase e outra é usada pelo InnoDB e Oracle. Nas seções subsequentes, discutiremos em detalhes como ele foi implementado no PostgreSQL e no InnoDB.

MVCC no PostgreSQL


Para suportar várias versões, o PostgreSQL mantém campos adicionais para cada objeto (Tupla na terminologia do PostgreSQL) conforme mencionado abaixo:
  1. xmin – ID da transação que inseriu ou atualizou a tupla. No caso de UPDATE, uma versão mais recente da tupla é atribuída com esse ID de transação.
  2. xmax – ID da transação que excluiu ou atualizou a tupla. No caso de UPDATE, uma versão atualmente existente da tupla recebe esse ID de transação. Em uma tupla recém-criada, o valor padrão desse campo é nulo.

O PostgreSQL armazena todos os dados em um armazenamento primário chamado HEAP (página de tamanho padrão de 8 KB). Toda a nova tupla recebe xmin como uma transação que a criou e uma tupla de versão mais antiga (que foi atualizada ou excluída) é atribuída com xmax. Há sempre um link da tupla da versão mais antiga para a nova versão. A tupla de versão mais antiga pode ser usada para recriar a tupla em caso de reversão e para ler uma versão mais antiga de uma tupla pela instrução READ, dependendo do nível de isolamento.

Considerando que existem duas tuplas, T1 (com valor 1) e T2 (com valor 2) para uma tabela, a criação de novas linhas pode ser demonstrada em 3 passos abaixo:
MVCC:Armazenamento de várias versões no PostgreSQL
Como visto na figura, inicialmente existem duas tuplas no banco de dados com valores 1 e 2.

Então, na segunda etapa, a linha T2 com valor 2 é atualizada com o valor 3. Neste ponto, uma nova versão é criada com o novo valor e ela é armazenada apenas como próxima à tupla existente na mesma área de armazenamento . Antes disso, a versão mais antiga é atribuída com xmax e aponta para a tupla da versão mais recente.

Da mesma forma, na terceira etapa, quando a linha T1 com valor 1 é excluída, a linha existente é virtualmente excluída (ou seja, apenas atribuiu xmax com a transação atual) no mesmo local. Nenhuma nova versão é criada para isso.

A seguir, vamos ver como cada operação cria várias versões e como o nível de isolamento da transação é mantido sem travamento com alguns exemplos reais. Em todos os exemplos abaixo, por padrão, o isolamento “READ COMMITTED” é usado.

INSERIR


Cada vez que um registro é inserido, ele cria uma nova tupla, que é adicionada a uma das páginas pertencentes à tabela correspondente.
Operação INSERT simultânea do PostgreSQL
Como podemos ver aqui passo a passo:
  1. A sessão-A inicia uma transação e obtém o ID de transação 495.
  2. A sessão-B inicia uma transação e obtém o ID de transação 496.
  3. Sessão-A insere uma nova tupla (é armazenada em HEAP)
  4. Agora, a nova tupla com xmin definido para o ID de transação atual 495 é adicionada.
  5. Mas o mesmo não é visível da Sessão-B, pois xmin (ou seja, 495) ainda não foi confirmado.
  6. Uma vez confirmado.
  7. Os dados ficam visíveis para ambas as sessões.

ATUALIZAÇÃO


PostgreSQL UPDATE não é uma atualização “IN-PLACE”, ou seja, não modifica o objeto existente com o novo valor necessário. Em vez disso, ele cria uma nova versão do objeto. Portanto, UPDATE envolve amplamente as etapas abaixo:
  1. Ele marca o objeto atual como excluído.
  2. Em seguida, adiciona uma nova versão do objeto.
  3. Redirecione a versão mais antiga do objeto para uma nova versão.

Portanto, mesmo que vários registros permaneçam os mesmos, o HEAP ocupa espaço como se mais um registro fosse inserido.
Operação INSERT simultânea do PostgreSQL
Como podemos ver aqui passo a passo:
  1. A Sessão-A inicia uma transação e obtém o ID de transação 497.
  2. A sessão-B inicia uma transação e obtém o ID de transação 498.
  3. A Sessão A atualiza o registro existente.
  4. Aqui, a Sessão-A vê uma versão da tupla (tupla atualizada), enquanto a Sessão-B vê outra versão (tupla mais antiga, mas xmax definido como 497). Ambas as versões de tupla são armazenadas no armazenamento HEAP (até na mesma página, dependendo da disponibilidade de espaço)
  5. Depois que a Sessão-A confirma a transação, a tupla mais antiga expira quando xmax da tupla mais antiga é confirmada.
  6. Agora, ambas as sessões veem a mesma versão do registro.

EXCLUIR


Excluir é quase como a operação UPDATE, exceto que não precisa adicionar uma nova versão. Ele apenas marca o objeto atual como DELETED conforme explicado no caso UPDATE.
Operação DELETE simultânea do PostgreSQL
  1. A Sessão-A inicia uma transação e obtém o ID de transação 499.
  2. A sessão-B inicia uma transação e obtém o ID de transação 500.
  3. Sessão-A exclui o registro existente.
  4. Aqui, a Sessão-A não vê nenhuma tupla como excluída da transação atual. Considerando que a Sessão-B vê uma versão mais antiga da tupla (com xmax como 499; a transação que excluiu este registro).
  5. Depois que a Sessão-A confirma a transação, a tupla mais antiga expira quando xmax da tupla mais antiga é confirmada.
  6. Agora, ambas as sessões não veem a tupla excluída.

Como podemos ver, nenhuma das operações remove diretamente a versão existente do objeto e, sempre que necessário, adiciona uma versão adicional do objeto.

Agora, vamos ver como a consulta SELECT é executada em uma tupla com várias versões:SELECT precisa ler todas as versões da tupla até encontrar a tupla apropriada de acordo com o nível de isolamento. Suponha que houvesse uma tupla T1, que foi atualizada e criou uma nova versão T1’ e que por sua vez criou T1’’ na atualização:
  1. A operação SELECT passará pelo armazenamento de heap para esta tabela e primeiro verificará T1. Se a transação T1 xmax for confirmada, ela será movida para a próxima versão dessa tupla.
  2. Suponha que agora a tupla T1' xmax também seja confirmada e, novamente, ela será movida para a próxima versão dessa tupla.
  3. Finalmente, ele encontra T1'' e vê que xmax não está confirmado (ou nulo) e T1'' xmin está visível para a transação atual de acordo com o nível de isolamento. Por fim, ele lerá a tupla T1''.

Como podemos ver, ele precisa percorrer todas as 3 versões da tupla para encontrar a tupla visível apropriada até que a tupla expirada seja excluída pelo coletor de lixo (VACUUM).

MVCC no InnoDB


Para oferecer suporte a várias versões, o InnoDB mantém campos adicionais para cada linha, conforme mencionado abaixo:
  1. DB_TRX_ID:ID da transação que inseriu ou atualizou a linha.
  2. DB_ROLL_PTR:também é chamado de ponteiro de rolagem e aponta para desfazer o registro de log gravado no segmento de reversão (mais sobre isso a seguir).

Assim como o PostgreSQL, o InnoDB também cria várias versões da linha como parte de toda a operação, mas o armazenamento da versão mais antiga é diferente.

No caso do InnoDB, a versão antiga da linha alterada é mantida em um espaço de tabela/armazenamento separado (chamado segmento de desfazer). Diferente do PostgreSQL, o InnoDB mantém apenas a versão mais recente das linhas na área de armazenamento principal e a versão mais antiga é mantida no segmento de desfazer. As versões de linha do segmento de desfazer são usadas para desfazer a operação em caso de reversão e para ler uma versão mais antiga de linhas pela instrução READ, dependendo do nível de isolamento.

Considerando que existem duas linhas, T1 (com valor 1) e T2 (com valor 2) para uma tabela, a criação de novas linhas pode ser demonstrada em 3 passos abaixo:
MVCC:Armazenamento de várias versões no InnoDB
Como visto na figura, inicialmente existem duas linhas no banco de dados com os valores 1 e 2.

Então, na segunda etapa, a linha T2 com valor 2 é atualizada com o valor 3. Neste ponto, uma nova versão é criada com o novo valor e substitui a versão anterior. Antes disso, a versão mais antiga é armazenada no segmento undo (observe que a versão do segmento UNDO tem apenas um valor delta). Além disso, observe que há um ponteiro da nova versão para a versão mais antiga no segmento de reversão. Ao contrário do PostgreSQL, a atualização do InnoDB é “IN-PLACE”.

Da mesma forma, na terceira etapa, quando a linha T1 com valor 1 é excluída, a linha existente é virtualmente excluída (ou seja, apenas marca um bit especial na linha) na área de armazenamento principal e uma nova versão correspondente a isso é adicionada em o segmento Desfazer. Novamente, há um ponteiro de rolagem do armazenamento principal para o segmento de desfazer.

Todas as operações se comportam da mesma forma que no caso do PostgreSQL quando vistas de fora. Apenas o armazenamento interno de várias versões difere.
Baixe o whitepaper hoje PostgreSQL Management &Automation with ClusterControlSaiba o que você precisa saber para implantar, monitorar, gerenciar e dimensionar o PostgreSQLBaixe o whitepaper

MVCC:PostgreSQL vs InnoDB


Agora, vamos analisar quais são as principais diferenças entre PostgreSQL e InnoDB em termos de implementação do MVCC:
  1. Tamanho de uma versão mais antiga


    O PostgreSQL apenas atualiza o xmax na versão mais antiga da tupla, então o tamanho da versão mais antiga permanece o mesmo do registro inserido correspondente. Isso significa que se você tiver 3 versões de uma tupla mais antiga, todas elas terão o mesmo tamanho (exceto a diferença no tamanho real dos dados, se houver, em cada atualização).

    Já no caso do InnoDB, a versão do objeto armazenado no segmento Undo é normalmente menor que o registro inserido correspondente. Isso ocorre porque apenas os valores alterados (ou seja, diferenciais) são gravados no log UNDO.
  2. Operação INSERIR


    O InnoDB precisa escrever um registro adicional no segmento UNDO mesmo para INSERT enquanto o PostgreSQL cria uma nova versão apenas no caso de UPDATE.
  3. Restaurando uma versão mais antiga em caso de reversão


    O PostgreSQL não precisa de nada específico para restaurar uma versão mais antiga em caso de reversão. Lembre-se que a versão mais antiga tem xmax igual à transação que atualizou esta tupla. Portanto, até que esse id de transação seja confirmado, ele é considerado uma tupla viva para um instantâneo simultâneo. Uma vez que a transação é revertida, a transação correspondente será automaticamente considerada ativa para todas as transações, pois será uma transação abortada.

    Considerando que, no caso do InnoDB, é explicitamente necessário reconstruir a versão mais antiga do objeto assim que a reversão ocorrer.
  4. Recuperando espaço ocupado por uma versão mais antiga


    No caso do PostgreSQL, o espaço ocupado por uma versão mais antiga pode ser considerado morto somente quando não houver um snapshot paralelo para ler esta versão. Uma vez que a versão mais antiga está morta, a operação VACUUM pode recuperar o espaço ocupado por eles. VACUUM pode ser acionado manualmente ou como uma tarefa em segundo plano, dependendo da configuração.

    Os logs do InnoDB UNDO são divididos principalmente em INSERT UNDO e UPDATE UNDO. O primeiro é descartado assim que a transação correspondente é confirmada. O segundo precisa ser preservado até que seja paralelo a qualquer outro instantâneo. O InnoDB não possui operação VACUUM explícita, mas em uma linha semelhante, possui PURGE assíncrono para descartar logs UNDO que são executados como uma tarefa em segundo plano.
  5. Impacto do vácuo atrasado


    Conforme discutido em um ponto anterior, há um enorme impacto do vácuo atrasado no caso do PostgreSQL. Isso faz com que a tabela comece a inchar e aumente o espaço de armazenamento, mesmo que os registros sejam excluídos constantemente. Também pode chegar a um ponto em que VACUUM FULL precisa ser feito, o que é uma operação muito cara.
  6. Verificação sequencial em caso de mesa inchada


    A varredura sequencial do PostgreSQL deve percorrer todas as versões mais antigas de um objeto, mesmo que todas estejam mortas (até que sejam removidas usando vácuo). Este é o problema típico e mais falado no PostgreSQL. Lembre-se de que o PostgreSQL armazena todas as versões de uma tupla no mesmo armazenamento.

    Considerando que, no caso do InnoDB, ele não precisa ler o registro Undo, a menos que seja necessário. Caso todos os registros de desfazer estejam mortos, será suficiente apenas ler todas as versões mais recentes dos objetos.
  7. Índice


    O PostgreSQL armazena o índice em um armazenamento separado que mantém um link para os dados reais no HEAP. Portanto, o PostgreSQL precisa atualizar a parte INDEX também, embora não tenha havido nenhuma alteração no INDEX. Embora mais tarde esse problema tenha sido corrigido implementando a atualização HOT (Heap Only Tuple), mas ainda tem a limitação de que, se uma nova tupla de heap não puder ser acomodada na mesma página, ela retornará ao UPDATE normal.

    O InnoDB não tem esse problema, pois usa índice clusterizado.

Conclusão


O PostgreSQL MVCC tem poucas desvantagens, especialmente em termos de armazenamento inchado, se sua carga de trabalho tiver UPDATE/DELETE frequente. Portanto, se você decidir usar o PostgreSQL, deve ter muito cuidado para configurar o VACUUM com sabedoria.

A comunidade PostgreSQL também reconheceu isso como um problema importante e eles já começaram a trabalhar na abordagem MVCC baseada em UNDO (nome provisório como ZHEAP) e podemos ver o mesmo em uma versão futura.