如何从事件日志表计算 SQL Server 中的平均队列长度
Posted
技术标签:
【中文标题】如何从事件日志表计算 SQL Server 中的平均队列长度【英文标题】:How to calculate average queue length in SQL Server from an event log table 【发布时间】:2021-09-22 02:59:29 【问题描述】:我正在监视一个系统,该系统将事件接收到队列中并按顺序/顺序/一次处理它们。这些事件由连续运行(一年 365 天、每天 24 天、每天 24 天)的物理设备生成。个别设备可能会因维护而下线,但该过程不会停止。
系统提供商正在对其进行更新,我正在尝试使用 2 个指标来分析一段时间内的性能:
-
事件在队列中停留的时间平均长度
每日平均队列长度(数据点是添加新事件时队列的长度)
一个事件以“活动”状态到达队列,如果成功处理,处理器将事件设置为“完成”,如果在 5 次重试尝试后失败,则将事件设置为“无效”。只有在一个事件被标记为“完成”或“无效”之后,处理器才会移动到下一个事件。该过程的结果是更新其他系统使用的事实表。
我们在 SQL Server 的表中记录了以下内容:
-
事件的 ID,生成为连续整数。逻辑上(但实际上不是)这就像
IDENTITY(1,1)
事件的当前状态,外键,只能是:Active、Completed 或 Invalid
事件生成的日期和时间
状态设置为“已完成”或“无效”的日期和时间
重试尝试次数(详细信息或失败原因存储在其他位置)
日志表如下所示:
CREATE TABLE EventLog (
Id int NOT NULL,
StateId int NOT NULL,
Generated datetime NULL,
Modified datetime NOT NULL,
Retries int NULL,
PRIMARY KEY CLUSTERED ( Id ASC ))
我还有两个日期和 stateId 的索引
CREATE NONCLUSTERED INDEX EventLog_DatesState ON EventLog
(
Generated ASC,
Modified ASC,
StateId ASC
)
我可以使用DATEDIFF(SECOND, Generated, Modified )/60.0
来计算事件在队列中花费的时间(以分钟为单位)。然后我可以平均每天的数据,看看它是如何随时间变化的。
计算一天的平均排队长度可能很困难。要在入队时获取我尝试过的项目的队列长度:
WITH EventDuration (SELECT
CAST(Generated AS Date) GeneratedDate
, DATEDIFF(SECOND, Generated, Modified)/60.0 TimeMinutes
, (
SELECT COUNT(*)
FROM EventLog sub
WHERE EventLog.Generated between sub.Generated and sub.Modified
AND StateId != @ActiveState
) queueLength
FROM EventLog
WHERE StateId != @ActiveState)
SELECT
GeneratedDate
, AVG(TimeMinutes) AvgTime
, AVG(QueueLength) AvgLength
, COUNT(*) Count
FROM EventDuration
GROUP BY GeneratedDate
ORDER BY GeneratedDate DESC
但是自联接/子查询需要从亚秒查询到我在 >10 分钟后放弃。
我的表中有大约 175000 行。有什么推荐的方法吗?
执行计划显示查询的昂贵部分是:
-
索引线轴 (51%)
输出列表:
EventLog.StateId
,EventLog.Modified
搜索谓词:Seek Keys[1]: End: EventLog.Generated < Scalar Operator(EventLog.Generated)
过滤器 (40%)
谓词:EventLog.Modified as sub.Modified>EventLog.Generated AND EventLog.StateId as sub.StateId<>(0)
其他上下文/注释:
-
事件数据可能非常突发,大量事件快速到达(队列不断增加),然后是安静的时期(处理器可以赶上(希望如此))
处理器确实会不时赶上。
处理过程中存在性能问题,如果队列太大,一个单独的进程会清除队列并生成事件以重建事实表。
我无法控制日志表的结构或处理系统的架构。对此的任何更改都超出了范围。
事件的处理不是外部系统的主要目的。它是为业务的其他部分提供数据的附加组件。
【问题讨论】:
您需要发布您的索引定义。单个索引不一定对此有用。但是索引应该是正确的方法,140,000 行不足以引起任何问题。 您还可以从检查执行计划中受益,因为它通常会告诉您问题所在。 你能提供一些样本数据和想要的结果吗?我很难理解您是否想要均匀分布的行和间隔的平均值,或者只是想将您获得的结果除以它返回的行数。这将根据活动的偏差对平均值产生很大的影响(例如,如果一切都在一小时内发生,那么前一种情况下一天的平均值会小得多,而后者会高得多)。 为什么 Generated 可以为空?即使根据您的使用情况不可能有空值,这似乎也是一个缺陷。 设置状态的日期和时间 你的意思是StateId,它是一个int? 一天的平均排队长度不,你不是。您的查询会根据 StateId 选择所有内容。 @SMor 我已经更新了问题以澄清一些事情。表结构就是这样,我无法控制它,不幸的是。是的,我的意思是StateId。我扩展了查询以显示我是如何进行聚合的。 CTE 获取事件在队列中的时间以及此时队列的大小。聚合是微不足道的,但我应该将它包含在上下文中。 CTE 是性能瓶颈。 【参考方案1】:注意:这不是最终答案,因为我得到了一些微小的价值差异,但它的速度要快几个数量级。我会尝试进一步完善它,但我想把它作为一个起点。
我以不同的方式处理它并使用了通用表表达式 (CTE)
-
添加(按
Generated
分组)或删除(按Modified
分组)的事件计数。
将这些加入在一起(日期为FULL OUTER JOIN
)给了我一份队列长度变化的时间列表,以及变化了多少。
我发现this explanation on how to calculate a rolling sum 给出了队列在更改时间点的长度。 (在阅读链接之前我不知道这是可能的)
然后我根据日期汇总。
我根据日期计算了原始EventLog
的事件持续时间聚合。
最后我在日期加入了两个聚合
最后的查询是:
DECLARE @ActiveState INT = 0;
WITH
Added AS (
SELECT Id, Generated AS Stamp, 1 AS Delta
FROM EventLog
WHERE StateId != @ActiveState
),
Removed AS (
SELECT Id, Modified AS Stamp, -1 AS Delta
FROM EventLog
WHERE StateId != @ActiveState),
LengthChange AS (
SELECT * FROM Added
UNION ALL
SELECT * FROM Removed),
RollingQueueLength AS (
SELECT
Id,
Delta,
SUM(Delta) OVER(ORDER BY Stamp, Id) - Delta AS QueueLength -- don't include this row in total
FROM LengthChange
),
EventDuration AS (
SELECT
Id,
Generated,
DATEDIFF(SECOND, Generated, Modified)/60.0 TimeMinutes
FROM EventLog
WHERE StateId != @ActiveState
),
EventPerformance AS (
SELECT
EventDuration.Id,
CAST(Generated AS DATE) GeneratedDate,
TimeMinutes,
QueueLength
FROM EventDuration
INNER JOIN RollingQueueLength
ON EventDuration.Id = RollingQueueLength.Id AND Delta = 1
)
SELECT
GeneratedDate
, AVG(TimeMinutes) AvgTime
, AVG(QueueLength) AvgLength
, COUNT(*) Count
FROM EventPerformance
GROUP BY GeneratedDate
ORDER BY GeneratedDate DESC
原始查询的问题在于它是 O(N2) 并且超过 175k 行只需要很长时间。
【讨论】:
以上是关于如何从事件日志表计算 SQL Server 中的平均队列长度的主要内容,如果未能解决你的问题,请参考以下文章
SQL Server 从当前日期计算天数,不包括另一个表中的天数