DbContextPool é um novo recurso introduzido em ASP.NET Core 2.1 que economiza a sobrecarga de criar uma instância de DbContext, mas há um pequeno buraco escondido nele. Recentemente, um projeto ASP.NET Core rodou continuamente por um período de tempo e então apareceu um erro nos logs de que o pool de conexões de banco de dados atingiu o número máximo de conexões:
System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached. em System.Data.Common.ADP.ExceptionWithStackTrace(Exception e) No começo, achei que fosse algum código que fazia o DbContext descartar corretamente, mas não encontrei nenhuma pista no código. Depois, realmente não havia mais nada a duvidar, só o DbContextPool, então tentei remover o DbContextPool, mas o erro desapareceu. De fato, foi causado pelo DbContextPool, mas o que faz as pessoas se perguntarem é que o DbContextPool foi originalmente criado para economizar a sobrecarga de criar instâncias do DbContext, então como ele pode consumir mais conexões de banco de dados, e a carga desse projeto é muito baixa, como pode consumir todo o pool de conexões? Falei sobre esse problema estranho na reunião semanal hoje, e então de repente pensei que cada instância do DbContext ocupará uma conexão de banco de dados (SqlConnection), e quando o DbContextPool não está ativado, assim que a solicitação terminar, a instância correspondente do DbContext será descartada e a conexão do banco de dados será colocada de volta no pool de conexões. Ao usar o DbContextPool, o DbContext não será descartado, mas será colocado de volta no DbContextPool após o término da solicitação, e o DbContext será colocado de volta em seu próprio pool, o que significa que sua conexão correspondente com o banco de dados não será retornada ao pool de conexão ao qual pertence. Cada DbContext no DbContextPool corresponde a uma conexão com banco de dados, e para cada DbContext adicional no DbContextPool, haverá uma conexão a menos no banco de dados. Quando os dois pools têm tamanhos diferentes e o DbContextPool é maior que o pool de conexão de banco de dados, surge o problema: o DbContextPool preenche livremente o DbContext no pool de acordo com o tamanho do seu próprio pool (digamos que seja 128), ignorando o tamanho do pool de conexão do banco de dados (assumindo que seja 100), e o erro acima ocorrerá quando o 101º DbContext for preenchido. Este projeto usa configurações padrão, a configuração padrão causa esse problema?
Olhando o código-fonte da implementação do DbContextPool, o limite padrão de tamanho para o pool de descobertas é 128
Olhando o código-fonte de implementação do SqlConnention, você verá que o limite padrão de tamanho para pools de conexão é 100
A configuração padrão vai desencadear o problema, o que é realmente uma pequena armadilha.
Sabendo o motivo, a solução é simples: definir o poolSize do DbContextPool para menor que o Max_Pool_Size do pool de conexão do banco de dados
|