如何从事件日志表计算 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.StateIdEventLog.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 从当前日期计算天数,不包括另一个表中的天数

如何从视图中复制数据并在 sql server 数据库中创建新表? [复制]

SQL SERVER日志限制增长

如何查看SQL server 2008的操作日志

sql server 错误日志errorlog

如何将 XML 文件(在本例中为业务流程事件日志)导入和查询到 SQL Server Express?