web-dev-qa-db-fra.com

Hangfire provoquant des verrous dans SQL Server

Nous utilisons Hangfire 1.7.2 dans notre projet Web ASP.NET avec SQL Server 2016. Nous avons environ 150 sites sur notre serveur, chaque site utilisant Hangfire 1.7.2. Nous avons remarqué que lorsque nous avons mis à niveau ces sites pour utiliser Hangfire, le serveur DB s'est effondré. En vérifiant les journaux DB, nous avons découvert qu'il y avait plusieurs requêtes de verrouillage. Nous avons identifié un événement RPC "sys.sp_getapplock; 1" dans toutes les sessions de blocage. Il semble que Hangfire verrouille notre base de données, rendant toute la base de données inutilisable. Nous avons remarqué près de 670+ requêtes de verrouillage à cause de Hangfire.

Cela pourrait être dû à ces propriétés que nous avons configurées:

   SlidingInvisibilityTimeout = TimeSpan.FromMinutes(30),
   QueuePollInterval = TimeSpan.FromHours(5)

Chaque site compte environ 20 tâches d'arrière-plan, certaines s'exécutent toutes les minutes, d'autres toutes les heures, toutes les 6 heures et certaines une fois par jour.

J'ai cherché dans la documentation mais je n'ai rien trouvé qui pourrait expliquer ces deux propriétés ou comment les définir pour éviter les verrous de base de données.

Vous cherchez de l'aide à ce sujet.

EDIT: les requêtes suivantes sont exécutées à chaque seconde:

exec sp_executesql N'select count(*) from [HangFire].[Set] with (readcommittedlock, forceseek) where [Key] = @key',N'@key nvarchar(4000)',@key=N'retries'

select distinct(Queue) from [HangFire].JobQueue with (nolock)

exec sp_executesql N'select count(*) from [HangFire].[Set] with (readcommittedlock, forceseek) where [Key] = @key',N'@key nvarchar(4000)',@key=N'retries'

indépendamment des diverses combinaisons de valeurs de durée que nous avons définies. Voici le code des GetHangfirServers que nous utilisons:

  public static IEnumerable<IDisposable> GetHangfireServers()
    {
        // Reference for GlobalConfiguration.Configuration: http://docs.hangfire.io/en/latest/getting-started/index.html
        // Reference for UseSqlServerStorage: http://docs.hangfire.io/en/latest/configuration/using-sql-server.html#configuring-the-polling-interval
        GlobalConfiguration.Configuration
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)

            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings()
            .UseSqlServerStorage(ConfigurationManager.ConnectionStrings["abc"]
                .ConnectionString, new SqlServerStorageOptions
            {
                CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
                SlidingInvisibilityTimeout = TimeSpan.FromMinutes(30),
                QueuePollInterval = TimeSpan.FromHours(5), // Hangfire will poll after 5 hrs to check failed jobs.
                UseRecommendedIsolationLevel = true,
                UsePageLocksOnDequeue = true,
                DisableGlobalLocks = true
            });

        // Reference: https://docs.hangfire.io/en/latest/background-processing/configuring-degree-of-parallelism.html
        var options = new BackgroundJobServerOptions
        {
            WorkerCount = 5
        };

        var server = new BackgroundJobServer(options);

        yield return server;
    }

Le nombre de travailleurs est fixé à 5.

Il n'y a que 4 tâches et même celles-ci sont terminées (SELECT * FROM [HangFire]. [State]): enter image description here

Avez-vous une idée pourquoi le Hangfire envoie autant de requêtes à chaque seconde?

9
Raghav

Nous avons rencontré ce problème dans l'un de nos projets. Le tableau de bord Hangfire est assez lu et interroge la base de données Hangfire très fréquemment pour actualiser l'état du travail.

La meilleure solution qui a fonctionné pour nous était d'avoir une base de données Hangfire dédiée. De cette façon, vous isolerez les requêtes d'application des requêtes hangfire et vos requêtes d'application ne seront pas affectées par le serveur hangfire et les requêtes du tableau de bord.

7
shbht_twr

Il existe une option de configuration plus récente appelée SlidingInvisibilityTimeout lors de la configuration de SqlServerStorage qui provoque ces verrous de base de données dans le cadre de la récupération plus récente de l'algorithme de récupération de message non transactionnel. Il est destiné aux travaux de longue durée qui peuvent entraîner une erreur de sauvegarde des journaux des transactions (car une transaction de base de données est toujours active dans le cadre du travail de longue durée).

.UseSqlServerStorage(
    "connection_string", 
    new SqlServerStorageOptions { SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5) });

Notre DBA n'aimait pas les verrous de base de données, j'ai donc juste supprimé cette option SlidingInvisibilityTimeout pour utiliser l'ancien algorithme de récupération de messages basé sur les transactions, car je n'avais pas de longs travaux en cours dans ma file d'attente.

Que vous activiez ou non cette option dépend de votre situation. Vous pouvez envisager de déplacer votre base de données de files d'attente en dehors de votre base de données d'application si ce n'est pas déjà fait et activer l'option SlidingInvisibilityTimeout. Si votre DBA ne peut pas vivre avec les verrous même si la file d'attente est une base de données distincte, vous pouvez peut-être refactoriser vos tâches en de nombreuses tâches plus petites et de plus courte durée. Juste quelques idées.

https://www.hangfire.io/blog/2017/06/16/hangfire-1.6.14.html

1
bfenske

SqlServerStorage exécute Install.sql qui prend un verrou de schéma exclusif sur le schéma Hangfire.

DECLARE @SchemaLockResult INT;
EXEC @SchemaLockResult = sp_getapplock @Resource = '$(HangFireSchema):SchemaLock', 
@LockMode = 'Exclusive'

Dans la documentation Hangfire:

"Les objets SQL Server sont installés automatiquement à partir du constructeur SqlServerStorage en exécutant les instructions décrites dans le fichier Install.sql (qui se trouve dans le dossier tools du package NuGet). Celui-ci contient le script de migration, afin que les nouvelles versions de Hangfire avec des modifications de schéma puissent être installé de façon transparente, sans votre intervention. "

Si vous ne souhaitez pas exécuter ce script à chaque fois, vous pouvez définir SqlServerStorageOptions.PrepareSchemaIfNecessary sur false.

var options = new SqlServerStorageOptions
{
    PrepareSchemaIfNecessary = false
};

var sqlServerStorage = new SqlServerStorage(connectionstring, options);

Exécutez plutôt Install.sql manuellement à l'aide de cette ligne:

SqlServerObjectsInstaller.Install(connection);
0
hightech