SQL Server 2016のASP.NET Webプロジェクト内でHangfire 1.7.2を使用しています。サーバーには約150のサイトがあり、各サイトでHangfire 1.7.2を使用しています。これらのサイトをHangfireを使用するようにアップグレードすると、DBサーバーが崩壊することに気づきました。 DBログを確認したところ、複数のロッククエリがあることがわかりました。すべてのブロッキングセッションで1つのRPCイベント「sys.sp_getapplock; 1」を識別しました。 HangfireがDBをロックしているため、DB全体が使用できなくなります。 Hangfireのため、670以上のロッククエリに気づきました。
これは、設定した次のプロパティが原因である可能性があります。
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(30),
QueuePollInterval = TimeSpan.FromHours(5)
各サイトには約20のバックグラウンドジョブがあり、その一部は毎分実行されますが、他のサイトは1時間ごと、6時間ごと、および1日1回実行されます。
私はドキュメントを検索しましたが、これら2つのプロパティを説明するものや、DBロックを回避するためにそれらを設定する方法を見つけることができませんでした。
これについていくつかの助けを探しています。
編集:次のクエリは毎秒実行されます:
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'
設定したタイムスパン値のさまざまな組み合わせに関係なく。以下は、使用しているGetHangfirServersのコードです。
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;
}
ワーカー数は5に設定されています。
ジョブは4つしかなく、それらも完了しています(SELECT * FROM [HangFire]。[State]):
Hangfireが毎秒非常に多くのクエリをヒットしている理由が何かわかりますか?
私たちはプロジェクトの1つでこの問題に直面しました。 hangfireダッシュボードはかなり読まれており、hangfire dbを非常に頻繁にポーリングしてジョブステータスを更新します。
私たちにとって最も効果的な解決策は、専用のハングファイアデータベースを用意することでした。このようにして、アプリケーションクエリをhangfireクエリから分離します。アプリケーションクエリは、hangfireサーバーとダッシュボードクエリの影響を受けません。
SqlServerStorageを構成するときにSlidingInvisibilityTimeoutと呼ばれる新しい構成オプションがあり、新しいトランザクション非メッセージフェッチアルゴリズムの一部としてこれらのデータベースロックが発生します。これは、トランザクションログのバックアップでエラーが発生する可能性がある長時間実行ジョブを意味します(長時間実行ジョブの一部としてまだアクティブなデータベーストランザクションがあるため)。
.UseSqlServerStorage(
"connection_string",
new SqlServerStorageOptions { SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5) });
私たちのDBAはデータベースロックを好まなかったため、キューに長時間実行されているジョブがないため、このSlidingInvisibilityTimeoutオプションを削除して、古いトランザクションベースのメッセージフェッチアルゴリズムを使用しました。
このオプションを有効にするかどうかは、状況によって異なります。キューデータベースをアプリケーションデータベースの外に移動していない場合は移動することを検討し、SlidingInvisibilityTimeoutオプションを有効にすることをお勧めします。キューが個別のデータベースであっても、DBAがロックを維持できない場合は、タスクをリファクタリングして、存続期間の短い、より多くの小さなタスクにすることができます。いくつかのアイデア。
https://www.hangfire.io/blog/2017/06/16/hangfire-1.6.14.html
SqlServerStorageは、Hangfireスキーマで排他的なスキーマロックを取得するInstall.sqlを実行します。
DECLARE @SchemaLockResult INT;
EXEC @SchemaLockResult = sp_getapplock @Resource = '$(HangFireSchema):SchemaLock',
@LockMode = 'Exclusive'
Hangfireのドキュメントから:
"SQL Serverオブジェクトは、Install.sqlファイル(NuGetパッケージのtoolsフォルダーにある)に記述されているステートメントを実行することにより、SqlServerStorageコンストラクターから自動的にインストールされます。これには移行スクリプトが含まれているため、スキーマを変更したHangfireの新しいバージョンでユーザーの介入なしにシームレスにインストールできます。」
このスクリプトを毎回実行したくない場合は、SqlServerStorageOptions.PrepareSchemaIfNecessaryをfalseに設定できます。
var options = new SqlServerStorageOptions
{
PrepareSchemaIfNecessary = false
};
var sqlServerStorage = new SqlServerStorage(connectionstring, options);
代わりに、次の行を使用してInstall.sqlを手動で実行します。
SqlServerObjectsInstaller.Install(connection);