如何根据事件日志 table 计算 SQL 服务器中的平均队列长度

How to calculate average queue length in SQL Server from an event log table

我正在监视一个系统,该系统将事件接收到队列中并一次在 order/sequentially/one 中处理它们。这些事件由 运行 连续(一年 365 天,24x7)的物理设备生成。个别设备可能会因维护而下线,但过程不会停止。

系统提供商正在更新它,我正在尝试使用 2 个指标来分析随时间变化的性能:

  1. 事件在队列中停留的时间长度的每日平均值
  2. 每日平均队列长度(数据点是添加新事件时队列的长度)

事件以“活动”状态到达队列,如果已成功处理,处理器将事件设置为“完成”,如果在重试 5 次后失败,则处理器将事件设置为“无效”。只有在一个事件被标记为“完成”或“无效”之后,处理器才会继续处理下一个事件。该过程的结果是更新 table 其他系统使用的事实。

我们在 SQL 服务器中的 table 中获得了以下日志:

  1. 事件的 ID,生成为连续整数。逻辑上(但实际上不是)这就像 IDENTITY(1,1)
  2. 事件的当前状态,外键,只能是:活动、完成或无效
  3. 事件生成的日期和时间
  4. 状态设置为“已完成”或“无效”的日期和时间
  5. 重试次数(详细信息或失败原因存储在别处)

日志 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 行。有什么推荐的方法吗?


执行计划显示查询的昂贵部分是:

  1. 索引假脱机 (51%)
    • 输出列表:EventLog.StateIdEventLog.Modified
    • 求谓词:Seek Keys[1]: End: EventLog.Generated < Scalar Operator(EventLog.Generated)
  2. 过滤 (40%)
    • 谓词:EventLog.Modified as sub.Modified>EventLog.Generated AND EventLog.StateId as sub.StateId<>(0)


其他context/notes:

  1. 事件数据可能非常突发,大量事件快速到达(队列建立起来)然后是安静的时期(处理器可以赶上(希望如此))
  2. 处理器确实会不时赶上。
  3. 处理中存在性能问题,如果队列变得太大,一个单独的进程会清除队列并生成事件以重建事实 tables。
  4. 我无法控制日志的结构 table,或者处理系统的架构。对此的任何更改都超出了范围。
  5. 事件的处理不是外部系统的主要目的。它是向业务的其他部分提供数据的附加组件。

注意: 这不是最终答案,因为我得到了一些细微的值差异,但速度快了几个数量级。我会尝试进一步完善它,但我想把它作为一个起点。

我以不同的方式处理它并使用了通用 Table 表达式 (CTE)

  1. 添加(按 Generated 分组)或删除(按 Modified 分组)的事件计数。
  2. 将它们合并在一起(FULL OUTER JOIN 在日期上)给了我队列长度变化的时间列表,以及变化了多少。
  3. 我发现 this explanation on how to calculate a rolling sum 可以给出队列在发生变化的时间点的长度。 (在阅读 link 之前我不知道这是可能的)
  4. 然后我根据日期汇总了它。
  5. 我根据日期计算了原始 EventLog 事件持续时间的总和。
  6. 最后我在日期
  7. 加入了两个集合

最终查询为:

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 行只花了很长时间。