Pré-carregamento de Mapa

Mapas podem ser associados aos utilitários de carga. Um utilitário de carga é utilizado para buscar objetos quando eles não podem ser localizados no mapa (uma ocorrência de cache) e também para gravar alterações em um backend quando ocorre o commit de uma transação. Os carregadores também podem ser usados para pré-carregar dados em um mapa. O método preloadMap da interface do Carregador é chamado em cada mapa quando sua partição correspondente no conjunto de mapas se torna um primário. O método preloadMap não é chamado nas réplicas. Ele tenta carregar todos os dados referenciados destinados a partir do backend no mapa utilizando a sessão fornecida. O mapa relevante é identificado pelo argumento BackingMap que é transmitido ao método preloadMap.

void preloadMap(Session session, BackingMap backingMap) throws LoaderException;

Pré-carregamento no Conjunto de Mapas Particionados

Os mapas podem ser particionados em N partições. Portanto, os mapas podem ser divididos em vários servidores, com cada entrada identifica por uma chave que é armazenada apenas em um destes servidores. Mapas muito grandes podem ser mantidos em uma grade de dados porque o aplicativo não é mais limitado pelo tamanho de heap de um Java Virtual Machine (JVM) único para reter todas as entradas de um Mapa. Aplicativos que desejam o pré-carregamento com o método preloadMap da interface Loader deve identificar o subconjunto de dados que ele pré-carrega. Sempre existe um número fixo de partições. É possível determinar este número utilizando o seguinte exemplo de código:

int numPartitions = backingMap.getPartitionManager().getNumOfPartitions();
int myPartition = backingMap.getPartitionId();
Este exemplo de código mostra como um aplicativo pode identificar o subconjunto de dados para pré-carregar a partir do banco de dados. Os aplicativos sempre devem utilizar estes métodos mesmo quando o mapa não é inicialmente particionado. Estes métodos permitem flexibilidade: Se o mapa for posteriormente particionado pelos administradores, então, o utilitário de carga continua a funcionar corretamente.

O aplicativo deve emitir consultas para recuperar o subconjunto myPartition a partir do backend. Se um banco de dados for utilizado, então, pode ser mais fácil ter uma coluna com o identificador de partições para um determinado registro, a menos que haja alguma consulta natural que permita que os dados na tabela sejam particionados facilmente.

Consulte o Gravando um Utilitário de Carga com um Controlador de Pré-carregamento de Réplica para obter um exemplo de como implementar um Carregador para uma grade de dados replicada.

Desempenho

A implementação de pré-carregamento copia dados do backend para o mapa, armazenando vários objetos no mapa em uma única transação. O número ideal de registros a serem armazenados por transação depende de vários fatores, incluindo complexidade e tamanho. Por exemplo, após a transação incluir blocos de mais de 100 entradas, o benefício do desempenho diminui conforme você aumenta o número de entradas. Para determinar o número ideal, comece com 100 entradas e, em seguida, aumente o número até que não sejam mais percebidos ganhos de desempenho. Transações maiores resultam em melhor desempenho de replicação. Lembre-se, apenas o primário executa o código de pré-carregamento. Os dados pré-carregados são replicados do primário para quaisquer réplicas que estão on-line.

Pré-carregando o Conjunto de Mapas

Se o aplicativo usar um conjunto de mapas com diversos mapas, cada mapa terá seu próprio carregador. Cada utilitário de carga possui um método preload. Cada mapa é carregado de forma serial pela grade de dados. Pode ser mais eficiente pré-carregar todos os mapas designando um único mapa como o mapa de pré-carregamento. Esse processo é uma convenção do aplicativo. Por exemplo, dois mapas, department e employee, podem utilizar o Utilitário de Carga de department para pré-carregar os mapas department e employee. Isto assegura que, transacionalmente, se um aplicativo desejar um departamento, os funcionários desse departamento estarão no cache. Quando o Utilitário de Carga do departamento pré-carregar um departamento do backend, ele também buscará os funcionários para esse departamento. O objeto department e seus objetos employee associados são, então, incluídos no mapa utilizando uma transação única.

Pré-carregamento recuperável

Alguns clientes têm conjuntos de dados muito grandes que precisam ser armazenados em cache. O pré-carregamento desses dados pode consumir muito tempo. Às vezes, o pré-carregamento deve ser concluído antes de o aplicativo ficar online. É possível beneficiar-se ao tornar o pré-carregamento recuperável. Suponha que haja um milhão de registros para pré-carregar. O primário está pré-carregando estes registros e falha no registro de número 800.000. Normalmente, a réplica escolhida para ser o novo primário limpa qualquer estado replicado e começa do início. O eXtreme Scale pode usar uma interface ReplicaPreloadController. O carregador para o aplicativo também precisa implementar a interface ReplicaPreloadController. Este exemplo inclui um método único no Utilitário de Carga: Status checkPreloadStatus(Session session, BackingMap bmap);. Este método é chamado pelo tempo de execução do eXtreme Scale antes do método preload da interface do Utilitário de Carga ser chamada normalmente. O eXtreme Scale testa o resultado deste método (Status) para determinar seu comportamento sempre que uma réplica é promovida para um primário.

Tabela 1. Valor de Status e Resposta
Valor do Status Retornado Resposta do eXtreme Scale
Status.PRELOADED_ALREADY O eXtreme Scale não chama o método preload porque este valor do status indica que o mapa foi totalmente pré-carregado.
Status.FULL_PRELOAD_NEEDED O eXtreme Scale limpa o mapa e chama o método preload normalmente.
Status.PARTIAL_PRELOAD_NEEDED O eXtreme Scale deixa o mapa no estado em que se encontra e chama o pré-carregamento. Essa estratégia permite que o carregador do aplicativo continue o pré-carregamento desse ponto em diante.

Claramente, enquanto um primário está pré-carregando o mapa, ele deve deixar algum estado em um mapa no MapSet que está sendo replicado de forma que a réplica determine qual status retornar. É possível usar um mapa extra denominado, por exemplo, RecoveryMap. Este mapa RecoveryMap deve fazer parte do mesmo mapa MapSet que está sendo pré-carregado para assegurar que o mapa seja replicado de forma consistente com os dados que estão sendo pré-carregados. A seguir, está uma implementação sugerida.

À medida que ocorre a confirmação de pré-carregamento cada bloco de registros, o processo também atualiza um contador ou valor no mapa RecoveryMap como parte de tal transação. Os dados pré-carregados e os dados do mapa RecoveryMap são replicados atomicamente para as réplicas. Quando a réplica é promovida para o primário, ela pode verificar o mapa RecoveryMap para saber o que aconteceu.

O mapa RecoveryMap pode conter uma única entrada com a chave de estado. Se nenhum objeto existir para esta chave, será necessário um pré-carregamento (checkPreloadStatus returns FULL_PRELOAD_NEEDED) integral. Se um objeto existir para esta chave de estado e o valor for COMPLETE, o pré-carregamento é concluído e o método checkPreloadStatus retorna PRELOADED_ALREADY. Caso contrário, o objeto de valor indica onde o pré-carregamento reinicia e o método checkPreloadStatus retorna PARTIAL_PRELOAD_NEEDED. O utilitário de carga pode armazenar o ponto de recuperação em uma variável de instância para o utilitário de carga para que, quando o pré-carregamento for chamado, ele saiba o ponto de partida. O mapa RecoveryMap também pode conter uma entrada por mapa se cada mapa for pré-carregado de maneira independente.

Manipulando a recuperação no modo de replicação síncrono com um Utilitário de Carga

O tempo de execução doeXtreme Scale é projetado para não perder dados com commit quando o primário falha. A seção a seguir mostra os algoritmos utilizados. Estes algoritmos se aplicam apenas quando um grupo de replicação utiliza a replicação síncrona. Um utilitário de carga é opcional.

O tempo de execução do eXtreme Scale pode ser configurado para replicar todas as alterações a partir de um primário para as réplicas de maneira síncrona. Quando uma réplica síncrona é posicionada ela recebe uma cópia dos dados existentes no shard primário. Durante este período, o primário continua a receber transações e copiá-las assincronamente para a réplica. A réplica não é considerada como estando on-line neste período.

Depois de a réplica capturar o primário, ela entre no modo peer e começa a replicação síncrona. Cada transação consolidada no primário é enviada às réplicas síncronas e o primário aguarda por uma resposta de cada réplica. Uma sequência de consolidação síncrona com um utilitário de carga no primário se parece com o conjunto e etapas a seguir:

Tabela 2. Sequência de Commit no Primário
Etapa com o Utilitário de Carga Etapa sem o Utilitário de Carga
Obter bloqueios para entradas igual
Limpar alterações no utilitário de carga no-op
Salvar alterações no cache igual
Enviar alterações para réplicas e esperar confirmação igual
Confirmar para o utilitário de carga por meio do Plug-in TransactionCallback commit do plug-in chamado, mas não faz nada
Liberar bloqueios para entradas igual

Observe que as alterações são enviadas para a réplica antes de serem confirmadas para o utilitário de carga. Para determinar quando ocorre o commit das alterações na réplica, revise esta sequência: No momento da inicialização, inicialize as listas tx no primário, conforme abaixo.

CommitedTx = {}, RolledBackTx = {}

Durante o processamento de confirmação síncrona, utilize a seguinte sequência:

Tabela 3. Processamento de Commit Síncrono
Etapa com o Utilitário de Carga Etapa sem o Utilitário de Carga
Obter bloqueios para entradas igual
Limpar alterações no utilitário de carga no-op
Salvar alterações no cache igual
Enviar alterações com uma transação confirmada, efetuar rollback da transação para a réplica e esperar confirmação igual
Limpar lista de transações confirmadas e de transações que receberam rollback igual
Confirmar o utilitário de carga por meio do plug-in TransactionCallBack A confirmação do plug-in TransactionCallBack ainda é chamada mas, geralmente, não faz nada
Se a confirmação for bem-sucedida, inclua a transação nas transações confirmadas; caso contrário, inclua nas transações que receberam rollback no-op
Liberar bloqueios para entradas igual

Para processamento de réplica, utilize a seguinte sequência:

  1. Receber alterações
  2. Confirmar todas as transações recebidas na lista de transações confirmadas
  3. Efetuar rollback de todas as transações recebidas na lista de transações que receberam rollback
  4. Iniciar uma transação ou sessão
  5. Aplicar alterações à transação ou sessão
  6. Salvar a transação ou sessão na lista pendente
  7. Retornar resposta

Observe que, na réplica, não existem interações do Utilitário de Carga enquanto ele está no modo de réplica. O primário deve enviar todas as alterações por meio do Utilitário de Carga. A réplica não altera os dados. Um efeito secundário deste algoritmo é que a réplica sempre tem as transações, mas elas não são confirmadas, até que a próxima transação primária envie o status de confirmação destas transações. Elas são então confirmadas ou recebem rollback na réplica. Mas, até então, as transações não são confirmadas. É possível incluir um cronômetro no primário que envia o resultado da transação após um pequeno período (alguns segundos). Esse cronômetro limita, mas não elimina, nenhuma deterioração desse espaço de tempo Este staleness é um problemas apenas ao utilizar o modo de leitura de réplica. Do contrário, a deterioração não tem impacto sobre o aplicativo.

Quando o primário falha, é provável que poucos commits ou rollback tenham ocorrido nas transações no primário, mas a mensagem nunca fez isto para a réplica com estas saídas. Quando uma réplica for promovida para o novo primário, uma de suas primeiras ações será manipular esta condição. Cada transação pendente é processada novamente junto ao novo conjunto de mapas do primário. Se houver um Utilitário de Carga, então, cada transação é fornecida para o Utilitário de Carga. Estas transações são aplicadas na ordem FIFO (primeiro a entrar, primeiro a sair) estrita. Se uma transação falhar, ela será ignorada. Se três transações estiverem pendentes, A, B e C, A poderá ser confirmada, B poderá ser retrocedida e C também poderá ser confirmada. Nenhuma transação tem impacto sobre as outras. Suponha que elas sejam independentes.

Um utilitário de carga talvez queira utilizar uma lógica um pouco diferente quando no modo recuperação de failover versus modo normal. O carregador pode saber facilmente quando está em modo de recuperação de failover, implementando a interface ReplicaPreloadController. O método checkPreloadStatus só é chamado quando a recuperação de failover é concluída. Portanto, se o método de aplicação da interface do Carregador for chamado antes do método checkPreloadStatus, ele será uma transação de recuperação. Após o método checkPreloadStatus ser chamado, a recuperação de failover será concluída.