如何根据事件日志 table 计算 SQL 服务器中的平均队列长度
How to calculate average queue length in SQL Server from an event log table
我正在监视一个系统,该系统将事件接收到队列中并一次在 order/sequentially/one 中处理它们。这些事件由 运行 连续(一年 365 天,24x7)的物理设备生成。个别设备可能会因维护而下线,但过程不会停止。
系统提供商正在更新它,我正在尝试使用 2 个指标来分析随时间变化的性能:
- 事件在队列中停留的时间长度的每日平均值
- 每日平均队列长度(数据点是添加新事件时队列的长度)
事件以“活动”状态到达队列,如果已成功处理,处理器将事件设置为“完成”,如果在重试 5 次后失败,则处理器将事件设置为“无效”。只有在一个事件被标记为“完成”或“无效”之后,处理器才会继续处理下一个事件。该过程的结果是更新 table 其他系统使用的事实。
我们在 SQL 服务器中的 table 中获得了以下日志:
- 事件的 ID,生成为连续整数。逻辑上(但实际上不是)这就像
IDENTITY(1,1)
- 事件的当前状态,外键,只能是:活动、完成或无效
- 事件生成的日期和时间
- 状态设置为“已完成”或“无效”的日期和时间
- 重试次数(详细信息或失败原因存储在别处)
日志 table 如下所示:
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 ))
我还有一个关于 2 个日期和 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
但是那个自我 join/subquery 需要从亚秒级到我在超过 10 分钟后放弃查询。
我在 table 中有大约 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)
其他context/notes:
- 事件数据可能非常突发,大量事件快速到达(队列建立起来)然后是安静的时期(处理器可以赶上(希望如此))
- 处理器确实会不时赶上。
- 处理中存在性能问题,如果队列变得太大,一个单独的进程会清除队列并生成事件以重建事实 tables。
- 我无法控制日志的结构 table,或者处理系统的架构。对此的任何更改都超出了范围。
- 事件的处理不是外部系统的主要目的。它是向业务的其他部分提供数据的附加组件。
注意: 这不是最终答案,因为我得到了一些细微的值差异,但速度快了几个数量级。我会尝试进一步完善它,但我想把它作为一个起点。
我以不同的方式处理它并使用了通用 Table 表达式 (CTE)
- 添加(按
Generated
分组)或删除(按 Modified
分组)的事件计数。
- 将它们合并在一起(
FULL OUTER JOIN
在日期上)给了我队列长度变化的时间列表,以及变化了多少。
- 我发现 this explanation on how to calculate a rolling sum 可以给出队列在发生变化的时间点的长度。 (在阅读 link 之前我不知道这是可能的)
- 然后我根据日期汇总了它。
- 我根据日期计算了原始
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 行只花了很长时间。
我正在监视一个系统,该系统将事件接收到队列中并一次在 order/sequentially/one 中处理它们。这些事件由 运行 连续(一年 365 天,24x7)的物理设备生成。个别设备可能会因维护而下线,但过程不会停止。
系统提供商正在更新它,我正在尝试使用 2 个指标来分析随时间变化的性能:
- 事件在队列中停留的时间长度的每日平均值
- 每日平均队列长度(数据点是添加新事件时队列的长度)
事件以“活动”状态到达队列,如果已成功处理,处理器将事件设置为“完成”,如果在重试 5 次后失败,则处理器将事件设置为“无效”。只有在一个事件被标记为“完成”或“无效”之后,处理器才会继续处理下一个事件。该过程的结果是更新 table 其他系统使用的事实。
我们在 SQL 服务器中的 table 中获得了以下日志:
- 事件的 ID,生成为连续整数。逻辑上(但实际上不是)这就像
IDENTITY(1,1)
- 事件的当前状态,外键,只能是:活动、完成或无效
- 事件生成的日期和时间
- 状态设置为“已完成”或“无效”的日期和时间
- 重试次数(详细信息或失败原因存储在别处)
日志 table 如下所示:
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 ))
我还有一个关于 2 个日期和 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
但是那个自我 join/subquery 需要从亚秒级到我在超过 10 分钟后放弃查询。
我在 table 中有大约 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)
- 谓词:
其他context/notes:
- 事件数据可能非常突发,大量事件快速到达(队列建立起来)然后是安静的时期(处理器可以赶上(希望如此))
- 处理器确实会不时赶上。
- 处理中存在性能问题,如果队列变得太大,一个单独的进程会清除队列并生成事件以重建事实 tables。
- 我无法控制日志的结构 table,或者处理系统的架构。对此的任何更改都超出了范围。
- 事件的处理不是外部系统的主要目的。它是向业务的其他部分提供数据的附加组件。
注意: 这不是最终答案,因为我得到了一些细微的值差异,但速度快了几个数量级。我会尝试进一步完善它,但我想把它作为一个起点。
我以不同的方式处理它并使用了通用 Table 表达式 (CTE)
- 添加(按
Generated
分组)或删除(按Modified
分组)的事件计数。 - 将它们合并在一起(
FULL OUTER JOIN
在日期上)给了我队列长度变化的时间列表,以及变化了多少。 - 我发现 this explanation on how to calculate a rolling sum 可以给出队列在发生变化的时间点的长度。 (在阅读 link 之前我不知道这是可能的)
- 然后我根据日期汇总了它。
- 我根据日期计算了原始
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 行只花了很长时间。