Melhor maneira de implementar fila simultânea baseada em tabela

10

Eu tenho uma tabela no MySQL que representa uma fila de links a serem processados. Os links são processados ​​por um aplicativo externo, um por um, e excluídos no final. Essa é uma fila de alto volume e tenho várias instâncias do aplicativo de processamento, espalhadas por vários servidores.

Como posso garantir que cada registro seja escolhido por apenas um aplicativo? Existe uma maneira de sinalizar / bloquear o registro?

No momento, para evitar dois ou mais capturar o mesmo link, estou permitindo que cada instância pegue apenas um determinado conjunto de registros (com base no MOD de seu ID), mas essa não é uma maneira transparente de aumentar o processamento da fila velocidade apenas adicionando novas instâncias.

Miguel E
fonte
Meu mantra: "Não faça fila, apenas faça". Ou seja, em vez de lançar uma tarefa em uma fila, inicie um processo para executar a tarefa.
Rick James

Respostas:

7

Primeiro: o MySQL é um dos piores softwares possíveis para implementar isso, especialmente se for muito dinâmico. O motivo é que mecanismos como MEMORY e MyISAM têm apenas bloqueios de tabela completa, enquanto mecanismos mais adequados, como o InnoDB, têm uma penalidade de gravação mais alta (para fornecer propriedades ACID) e são otimizados para acessar registros que estão próximos espacial e temporalmente (aqueles configurados na memória ) Também não existe um bom sistema de notificação de alterações para o MySQL - ele deve ser implementado como uma pesquisa. Existem dezenas de softwares mais otimizados para essa tarefa .

Dito isto, vi implementar com êxito esse tipo de acesso se os requisitos de desempenho / eficiência não forem muito altos. Muitas pessoas não podem se dar ao luxo de introduzir e manter uma peça completa de tecnologia separada apenas por uma pequena parte da lógica de negócios.

SELECT FOR UPDATEé o que você está procurando - ler serialização. Enquanto um UPDATE / DELETE sempre bloqueia a linha durante uma transação MYSQL em execução, convém evitar uma transação grande enquanto o processo está em andamento, portanto:

START TRANSACTION;
SELECT * FROM your_table WHERE state != 'PROCESSING' 
  ORDER BY date_added ASC LIMIT 1 FOR UPDATE;
if (rows_selected = 0) { //finished processing the queue, abort}
else {
UPDATE your_table WHERE id = $row.id SET state = 'PROCESSING'
COMMIT;

// row is processed here, outside of the transaction, and it can take as much time as we want

// once we finish:
DELETE FROM your_table WHERE id = $row.id and state = 'PROCESSING' LIMIT 1;
}

O MySQL cuidará do bloqueio de todas as seleções simultâneas, exceto uma ao selecionar linhas. Como isso pode levar a muitas conexões bloqueadas ao mesmo tempo, mantenha a transação inicial o menor possível e tente processar mais de uma linha por vez.

jynus
fonte
Obrigado. Você acha que o desempenho pode se beneficiar de um bloqueio maior (alterando o LIMIT para dizer 10)?
Miguel E
@MiguelE Em geral, sim, quanto mais tempo você gasta processando e menor a probabilidade de colidir com outras transações, melhor. Mas isso pode depender em alguns casos - também pode causar o efeito oposto (mais transações sendo bloqueadas). Sempre teste primeiro. Também é importante indexar adequadamente a tabela, ou você pode acabar com um bloqueio de tabela completo em alguns modos de isolamento.
jynus
11
E provavelmente seria uma boa ideia acompanhar a data em que você começou a processar a linha, para o caso de o processo travar e você desejar implementar um mecanismo de tempo limite.
Julian
3

Como expliquei neste artigo , o MySQL 8 introduziu suporte para SKIP LOCKED e NO WAIT.

SKIP LOCKED é útil para implementar filas de trabalhos (também conhecidas como filas de lotes), para que você possa pular os bloqueios que já estão bloqueados por outras transações simultâneas.

NO WAIT é útil para evitar a espera até que uma transação simultânea libere os bloqueios nos quais também estamos interessados ​​em bloquear. Sem NO WAIT, precisamos aguardar até que os bloqueios sejam liberados (no momento da confirmação ou liberação pela transação que atualmente mantém os bloqueios) ou o tempo limite de aquisição do bloqueio. Portanto, NO WAIT atua como um tempo limite de bloqueio com um valor de 0.

Para mais detalhes sobre SKIP LOCK e NO WAIT, consulte este artigo .

Vlad Mihalcea
fonte
0

Fiz algo semelhante com as verificações DBCC offline (dois servidores fazendo restaurações de backup e, em seguida, um DBCC checkdb). Um servidor reúne todos os 31 backups do servidor ontem e os coloca em uma fila e, em seguida, esse servidor e outro pull dessa fila. Embora não haja muitos servidores, o método deve permanecer o mesmo: faça com que o servidor de aplicativos execute uma consulta de atualização na fila, atualizando um campo de data / hora e um campo "servidor de aplicativos" com o nome desse servidor de aplicativos ou o ID numérico ainda melhor. Isso causará um bloqueio ou, se já houver um bloqueio de outro servidor que esteja obtendo a próxima linha, ele será bloqueado e aguardará o outro aplicativo concluir a obtenção da próxima linha. Você desejará que o aplicativo retire o registro mais recente da fila para o campo do aplicativo e obtenha as informações desejadas. Usando o MySQL '

Chris Woods
fonte