Olá pessoas mais espertas que eu! Eu criei uma espécie de sistema de tabela de fila, mas parece simples demais para estar protegido contra condições de corrida. Estou faltando alguma coisa ou a seguinte condição de corrida é segura?
O Esquema
Eu tenho uma mesa, vamos chamá-la ProductQueue
:
CREATE TABLE dbo.ProductQueue
(
SerialId BIGINT PRIMARY KEY,
QueuedDateTime DATETIME NOT NULL -- Only using this for reference, no functionality is tied to it
);
Eu tenho um procedimento para adicionar à fila chamado AddToProductQueue
:
CREATE PROCEDURE dbo.AddToProductQueue (@SerialId BIGINT)
AS
BEGIN
INSERT INTO dbo.ProductQueue (SerialId, QueuedDateTime)
OUTPUT Inserted.SerialId
SELECT @SerialId, GETDATE();
END
Também tenho um procedimento para remover da fila chamado RemoveFromProductQueue
:
CREATE PROCEDURE dbo.RemoveFromProductQueue (@SerialId BIGINT)
AS
BEGIN
DELETE FROM dbo.ProductQueue
OUTPUT Deleted.SerialId
WHERE SerialId = @SerialId;
END
Observe SerialId
que é globalmente exclusivo para um Product
banco de dados/sistema de origem. Ou seja, duas instâncias de a Product
nunca podem ter o mesmo SerialId
. Essa é a extensão do lado do banco de dados.
O fluxo de trabalho
- Eu tenho um processo de inscrição que é executado de hora em hora.
- Esse processo obtém uma lista de variáveis
SerialIds
do sistema de origem. - Ele chama iterativamente o
AddToProductQueue
procedimento em cada umSerialId
de sua lista. - Se o procedimento tentar inserir um
SerialId
que já existe naProductQueue
tabela, ele gerará um erro de violação de chave primária e o processo do aplicativo capturará esse erro e o ignoraráSerialId
. - Caso contrário, o procedimento adiciona isso
SerialId
àProductQueue
tabela com êxito e o retorna ao processo de aplicativo. - O processo de inscrição então adiciona o que foi enfileirado com sucesso
SerialId
em uma lista separada. - Depois que o processo de aplicativo termina de iterar sua lista de todos os candidatos
SerialIds
a serem enfileirados, ele itera sua nova lista de enfileirados com sucessoSerialIds
e realiza trabalho externo neles, em um thread separado porSerialId
. (Este trabalho não está relacionado ao banco de dados.) - Finalmente, à medida que cada thread termina seu trabalho externo, a última etapa desse thread assíncrono é removê-lo
SerialId
daProductQueue
tabela chamando oRemoveFromProductQueue
procedimento. (Observe que um novo objeto de contexto de banco de dados é instanciado e uma nova conexão é criada para cada chamada assíncrona para esse procedimento, para que ele seja thread-safe no lado do aplicativo.)
Informações adicionais
- Não há índices na
ProductQueue
tabela e ela nunca terá mais de 1.000 linhas ao mesmo tempo. (Na verdade, na maioria das vezes terá literalmente apenas algumas linhas.) - O mesmo
SerialId
pode se tornar candidato novamente para ser adicionado novamente à tabela de filas em uma futura execução do processo de aplicação. - Não há proteções que impeçam a execução simultânea de uma segunda instância do processo de aplicativo, seja por acidente ou se a primeira instância demorou mais de 1 hora para ser executada, etc. (Esta é a parte simultânea com a qual estou mais preocupado.)
- O nível de isolamento da transação do banco de dados (e da conexão que está sendo feita) onde residem a tabela de filas e os procedimentos é o nível de isolamento padrão de
Read Committed
.
Problemas potenciais
- A instância em execução do processo do aplicativo trava de forma não tratada, deixando-
SerialIds
a presa na tabela de filas. Isso é aceitável para as necessidades do negócio e planejamos ter relatórios de exceção para nos ajudar a remediar manualmente esse caso. - O processo do aplicativo é executado várias vezes simultaneamente e captura algumas das mesmas
SerialIds
entre as instâncias em suas listas de origem iniciais. Ainda não consigo pensar em nenhuma ramificação negativa deste caso, uma vez que o procedimento de enfileiramento é atômico, e a lista real emSerialIds
que o processo de aplicação funcionará deve ser independente devido a esse procedimento de enfileiramento atômico. Não nos importamos qual instância do processo de aplicativo realmente processa cada umaSerialId
, desde que a mesmaSerialId
não seja processada simultaneamente por ambas as instâncias do processo.