数据库中自动数据聚合的最佳方法
Best approach to automatic data aggregation in DB
我们目前正在开发一个 Web 应用程序来处理位于数据库 table 中的大量存档数据。 table 中的数据行由一个唯一的行 ID、两个标识一台机器和一个数据点的 ID、一个值和一个时间戳组成。每当值更改超过给定阈值时,每台机器都会将其数据发送到此 table。 table 通常包含数百万到数亿个条目。
出于可视化目的,我创建了一个存储过程,它采用识别机器和数据点所需的两个 ID,以及开始和结束日期时间。然后它将开始和结束之间的值聚合成可变长度的块(通常为 15 分钟、1 小时、7 天等)和 returns 给定时间间隔内每个块的平均值、最小值和最大值。
该方法有效,但需要花费大量时间,即使有大量的数据库优化和索引。所以在前端图表页面上显示所选范围和机器的数据大约需要 10 到 60 秒,我认为这太多了。
所以我开始考虑创建一个新的 table,其中包含每个 "chunk" 的每台机器的预聚合数据。为了实现这一点,必须为每台机器每隔 [chunksize]
minutes/hours/days 自动调用聚合过程。然后可以很容易地从更精细的块创建更粗糙的块,等等。据我所知,这将大大加快整个过程。
问题是:实现周期聚合的最佳方式是什么?有没有办法让数据库自己完成这项工作?或者我是否必须在 ASP.NET MVC Web 应用程序中实施基于计时器的解决方案?后者将要求网络应用程序始终 运行,这可能不是最佳方式,因为它可能会因各种原因而关闭。另一种选择是负责此任务的独立应用程序或服务。还有其他我没有想到的方法吗?你会如何处理这个问题?
在我们的系统中,我们有一个 table 具有原始原始数据。此原始数据汇总为每小时、每天和每周的时间间隔(每个时间间隔的原始值的总和、最小值、最大值)。
我们将原始数据保存 30 天(4 周),每小时保存 43 天(6 周),每天保存 560 天(18 个月),每周保存 10 年。每天晚上这四个 table 都是 "cleaned" 并且删除超过阈值的数据。每小时 table 大约有 3000 万行,每天 1800 万行。一些 reports/charts 使用每小时数据,大多数使用每日数据。有时我们需要查看原始数据以详细调查问题。
我有一个用 C++ 编写的专用应用程序,它 运行 在服务器上 24/7 全天候运行,并从大约 200 台其他服务器收集原始数据并将其插入中央数据库。在应用程序内部,我定期(每 10 分钟)调用一个重新计算摘要的存储过程。如果用户想查看最新的数据,最终用户也可以随时 运行 此存储过程。通常 运行 大约需要 10 秒,因此最终用户通常会看到延迟的摘要。因此,从技术上讲,服务器上可能有一个计划作业,运行 每 10 分钟执行一次该过程。当我通过应用程序执行此操作时,我可以更好地控制收集数据的其他线程。本质上,我会在汇总数据时暂停尝试插入新数据。但是,仅使用独立的存储过程也可以达到相同的效果。
就我而言,我可以使摘要的重新计算变得相当高效。
随着新数据在这 10 分钟内流入数据库 window,我将原始数据直接插入主数据库 table。原始数据点永远不会更新,它们只会被添加(插入)。所以,这一步简单高效。我使用带有 table 值参数的存储过程,并在一次调用中传递一大块新数据。因此,在一个 INSERT
语句中插入了很多行,这很好。
摘要 table 使用第二个存储过程每 10 分钟用新数据更新一次。必须更新一些现有行,添加一些行。为了有效地做到这一点,我有一个单独的 "staging" table,其中包含机器 ID、每小时日期时间、每天日期时间、每周日期时间列。当我将原始数据插入主 table 时,我还将受影响的机器 ID 和受影响的时间间隔插入到此暂存 table.
所以,有两个主要的存储过程。应用程序使用多个线程循环遍历 200 个远程服务器,并在无限循环中从每个服务器下载新数据。一旦从某个远程服务器下载了一批新数据,就会调用第一个存储过程。这种情况经常发生。此过程按原样将一批原始数据插入原始 table 并将受影响的时间间隔列表插入 "staging" table.
说,原始数据的传入批次如下所示:
ID timestamp raw_value
1 2015-01-01 23:54:45 123
1 2015-01-01 23:57:12 456
1 2015-01-02 00:03:23 789
2 2015-01-02 02:05:21 909
4 行按原样插入主 table(ID、时间戳、值)。
3行被插入到staging中table(通常有很多具有同一小时时间戳的值,所以有很多原始行,但staging中很少table):
ID hourlytimestamp dailytimestamp weeklytimestamp
1 2015-01-01 23:00:00 2015-01-01 00:00:00 2014-12-29 00:00:00
1 2015-01-02 00:00:00 2015-01-02 00:00:00 2014-12-29 00:00:00
2 2015-01-02 00:00:00 2015-01-02 00:00:00 2014-12-29 00:00:00
请注意,我在这里 collate/condense/merge 将所有 ID 和时间戳放入唯一集合中,而此阶段 table 根本没有值,它仅包含受影响的 ID 和时间间隔(StatsToRecalc
是这个staging table,@ParamRows
是有一批带有新数据的行的存储过程的参数):
DECLARE @VarStart datetime = '20000103'; -- it is Monday
INSERT INTO dbo.StatsToRecalc
(ID
,PeriodBeginLocalDateTimeHour
,PeriodBeginLocalDateTimeDay
,PeriodBeginLocalDateTimeWeek)
SELECT DISTINCT
TT.[ID],
-- Truncate time to 1 hour.
DATEADD(hour, DATEDIFF(hour, @VarStart, TT.PlaybackStartedLocalDateTime), @VarStart),
-- Truncate time to 1 day.
DATEADD(day, DATEDIFF(day, @VarStart, TT.PlaybackStartedLocalDateTime), @VarStart),
-- Truncate time to 1 week.
DATEADD(day, ROUND(DATEDIFF(day, @VarStart, TT.PlaybackStartedLocalDateTime) / 7, 0, 1) * 7, @VarStart)
FROM @ParamRows AS TT;
然后从 @ParamRows
.
简单 INSERT
到原始 table
因此,有许多 INSERTS
进入原始和暂存 tables 从许多线程使用此过程 10 分钟。
每 10 分钟调用一个重新计算摘要的第二个过程。
它做的第一件事是启动事务并锁定暂存 table 直到事务结束:
SELECT @VarCount = COUNT(*)
FROM dbo.StatsToRecalc
WITH (HOLDLOCK)
如果暂存 table StatsToRecalc
不为空,我们需要做一些事情。由于这个 table 被锁定,所有工作线程都不会干扰,并且会等到重新计算完成后再添加更多数据。
通过使用此暂存 table 我可以快速确定我需要重新计算哪些 ID 的小时、天和周。实际的汇总计算是在 MERGE
语句中完成的,它一次性处理所有受影响的 ID 和间隔。我运行三个MERGEs
将原始数据汇总成每小时汇总,然后每小时汇总成每日,然后每天汇总成每周。然后 staging table 被清空(每 10 分钟一次),因此它永远不会变得太大。
每个 MERGE
首先列出自上次重新计算以来受到影响的 ID 和时间戳(例如,从每小时更新 table):
WITH
CTE_Changed (ID, PeriodBeginLocalDateTimeDay)
AS
(
SELECT
dbo.StatsToRecalc.ID
, dbo.StatsToRecalc.PeriodBeginLocalDateTimeDay
FROM
dbo.StatsToRecalc
GROUP BY
dbo.StatsToRecalc.ID
,dbo.StatsToRecalc.PeriodBeginLocalDateTimeDay
)
然后在 MERGE
中每小时 table 加入此 CTE:
MERGE INTO dbo.StatsDay AS Dest
USING
(
SELECT
...
FROM
dbo.StatsHour
INNER JOIN CTE_Changed ON
CTE_Changed.ID = dbo.StatsHour.ID AND
CTE_Changed.PeriodBeginLocalDateTimeDay = dbo.StatsHour.PeriodBeginLocalDateTimeDay
)
...
为了帮助进行这种多阶段汇总,我在原始、每小时和每天 table 中提供了辅助列。例如,每小时 table 有一列 PeriodBeginLocalDateTimeHour
,其中包含如下值:
2015-01-01 22:00:00
2015-01-01 23:00:00
2015-01-02 00:00:00
2015-01-02 01:00:00
...
,即一个小时的界限。同时还有第二列包含这些时间戳 "truncated" 到日期边界:PeriodBeginLocalDateTimeDay
,其中包含如下值:
2015-01-01 00:00:00
2015-01-02 00:00:00
...
,即一天的界限。第二列仅在我将小时数汇总为天数时使用 - 我不必即时计算日期时间戳,而是使用持久的索引值。
我应该补充一点,在我的情况下,如果那个专用的 C++ 应用程序关闭一段时间是可以的。就是说数据会延迟10分钟以上,但不会丢失任何东西。
我们目前正在开发一个 Web 应用程序来处理位于数据库 table 中的大量存档数据。 table 中的数据行由一个唯一的行 ID、两个标识一台机器和一个数据点的 ID、一个值和一个时间戳组成。每当值更改超过给定阈值时,每台机器都会将其数据发送到此 table。 table 通常包含数百万到数亿个条目。
出于可视化目的,我创建了一个存储过程,它采用识别机器和数据点所需的两个 ID,以及开始和结束日期时间。然后它将开始和结束之间的值聚合成可变长度的块(通常为 15 分钟、1 小时、7 天等)和 returns 给定时间间隔内每个块的平均值、最小值和最大值。
该方法有效,但需要花费大量时间,即使有大量的数据库优化和索引。所以在前端图表页面上显示所选范围和机器的数据大约需要 10 到 60 秒,我认为这太多了。
所以我开始考虑创建一个新的 table,其中包含每个 "chunk" 的每台机器的预聚合数据。为了实现这一点,必须为每台机器每隔 [chunksize]
minutes/hours/days 自动调用聚合过程。然后可以很容易地从更精细的块创建更粗糙的块,等等。据我所知,这将大大加快整个过程。
问题是:实现周期聚合的最佳方式是什么?有没有办法让数据库自己完成这项工作?或者我是否必须在 ASP.NET MVC Web 应用程序中实施基于计时器的解决方案?后者将要求网络应用程序始终 运行,这可能不是最佳方式,因为它可能会因各种原因而关闭。另一种选择是负责此任务的独立应用程序或服务。还有其他我没有想到的方法吗?你会如何处理这个问题?
在我们的系统中,我们有一个 table 具有原始原始数据。此原始数据汇总为每小时、每天和每周的时间间隔(每个时间间隔的原始值的总和、最小值、最大值)。
我们将原始数据保存 30 天(4 周),每小时保存 43 天(6 周),每天保存 560 天(18 个月),每周保存 10 年。每天晚上这四个 table 都是 "cleaned" 并且删除超过阈值的数据。每小时 table 大约有 3000 万行,每天 1800 万行。一些 reports/charts 使用每小时数据,大多数使用每日数据。有时我们需要查看原始数据以详细调查问题。
我有一个用 C++ 编写的专用应用程序,它 运行 在服务器上 24/7 全天候运行,并从大约 200 台其他服务器收集原始数据并将其插入中央数据库。在应用程序内部,我定期(每 10 分钟)调用一个重新计算摘要的存储过程。如果用户想查看最新的数据,最终用户也可以随时 运行 此存储过程。通常 运行 大约需要 10 秒,因此最终用户通常会看到延迟的摘要。因此,从技术上讲,服务器上可能有一个计划作业,运行 每 10 分钟执行一次该过程。当我通过应用程序执行此操作时,我可以更好地控制收集数据的其他线程。本质上,我会在汇总数据时暂停尝试插入新数据。但是,仅使用独立的存储过程也可以达到相同的效果。
就我而言,我可以使摘要的重新计算变得相当高效。
随着新数据在这 10 分钟内流入数据库 window,我将原始数据直接插入主数据库 table。原始数据点永远不会更新,它们只会被添加(插入)。所以,这一步简单高效。我使用带有 table 值参数的存储过程,并在一次调用中传递一大块新数据。因此,在一个
INSERT
语句中插入了很多行,这很好。摘要 table 使用第二个存储过程每 10 分钟用新数据更新一次。必须更新一些现有行,添加一些行。为了有效地做到这一点,我有一个单独的 "staging" table,其中包含机器 ID、每小时日期时间、每天日期时间、每周日期时间列。当我将原始数据插入主 table 时,我还将受影响的机器 ID 和受影响的时间间隔插入到此暂存 table.
所以,有两个主要的存储过程。应用程序使用多个线程循环遍历 200 个远程服务器,并在无限循环中从每个服务器下载新数据。一旦从某个远程服务器下载了一批新数据,就会调用第一个存储过程。这种情况经常发生。此过程按原样将一批原始数据插入原始 table 并将受影响的时间间隔列表插入 "staging" table.
说,原始数据的传入批次如下所示:
ID timestamp raw_value
1 2015-01-01 23:54:45 123
1 2015-01-01 23:57:12 456
1 2015-01-02 00:03:23 789
2 2015-01-02 02:05:21 909
4 行按原样插入主 table(ID、时间戳、值)。
3行被插入到staging中table(通常有很多具有同一小时时间戳的值,所以有很多原始行,但staging中很少table):
ID hourlytimestamp dailytimestamp weeklytimestamp
1 2015-01-01 23:00:00 2015-01-01 00:00:00 2014-12-29 00:00:00
1 2015-01-02 00:00:00 2015-01-02 00:00:00 2014-12-29 00:00:00
2 2015-01-02 00:00:00 2015-01-02 00:00:00 2014-12-29 00:00:00
请注意,我在这里 collate/condense/merge 将所有 ID 和时间戳放入唯一集合中,而此阶段 table 根本没有值,它仅包含受影响的 ID 和时间间隔(StatsToRecalc
是这个staging table,@ParamRows
是有一批带有新数据的行的存储过程的参数):
DECLARE @VarStart datetime = '20000103'; -- it is Monday
INSERT INTO dbo.StatsToRecalc
(ID
,PeriodBeginLocalDateTimeHour
,PeriodBeginLocalDateTimeDay
,PeriodBeginLocalDateTimeWeek)
SELECT DISTINCT
TT.[ID],
-- Truncate time to 1 hour.
DATEADD(hour, DATEDIFF(hour, @VarStart, TT.PlaybackStartedLocalDateTime), @VarStart),
-- Truncate time to 1 day.
DATEADD(day, DATEDIFF(day, @VarStart, TT.PlaybackStartedLocalDateTime), @VarStart),
-- Truncate time to 1 week.
DATEADD(day, ROUND(DATEDIFF(day, @VarStart, TT.PlaybackStartedLocalDateTime) / 7, 0, 1) * 7, @VarStart)
FROM @ParamRows AS TT;
然后从 @ParamRows
.
INSERT
到原始 table
因此,有许多 INSERTS
进入原始和暂存 tables 从许多线程使用此过程 10 分钟。
每 10 分钟调用一个重新计算摘要的第二个过程。
它做的第一件事是启动事务并锁定暂存 table 直到事务结束:
SELECT @VarCount = COUNT(*)
FROM dbo.StatsToRecalc
WITH (HOLDLOCK)
如果暂存 table StatsToRecalc
不为空,我们需要做一些事情。由于这个 table 被锁定,所有工作线程都不会干扰,并且会等到重新计算完成后再添加更多数据。
通过使用此暂存 table 我可以快速确定我需要重新计算哪些 ID 的小时、天和周。实际的汇总计算是在 MERGE
语句中完成的,它一次性处理所有受影响的 ID 和间隔。我运行三个MERGEs
将原始数据汇总成每小时汇总,然后每小时汇总成每日,然后每天汇总成每周。然后 staging table 被清空(每 10 分钟一次),因此它永远不会变得太大。
每个 MERGE
首先列出自上次重新计算以来受到影响的 ID 和时间戳(例如,从每小时更新 table):
WITH
CTE_Changed (ID, PeriodBeginLocalDateTimeDay)
AS
(
SELECT
dbo.StatsToRecalc.ID
, dbo.StatsToRecalc.PeriodBeginLocalDateTimeDay
FROM
dbo.StatsToRecalc
GROUP BY
dbo.StatsToRecalc.ID
,dbo.StatsToRecalc.PeriodBeginLocalDateTimeDay
)
然后在 MERGE
中每小时 table 加入此 CTE:
MERGE INTO dbo.StatsDay AS Dest
USING
(
SELECT
...
FROM
dbo.StatsHour
INNER JOIN CTE_Changed ON
CTE_Changed.ID = dbo.StatsHour.ID AND
CTE_Changed.PeriodBeginLocalDateTimeDay = dbo.StatsHour.PeriodBeginLocalDateTimeDay
)
...
为了帮助进行这种多阶段汇总,我在原始、每小时和每天 table 中提供了辅助列。例如,每小时 table 有一列 PeriodBeginLocalDateTimeHour
,其中包含如下值:
2015-01-01 22:00:00
2015-01-01 23:00:00
2015-01-02 00:00:00
2015-01-02 01:00:00
...
,即一个小时的界限。同时还有第二列包含这些时间戳 "truncated" 到日期边界:PeriodBeginLocalDateTimeDay
,其中包含如下值:
2015-01-01 00:00:00
2015-01-02 00:00:00
...
,即一天的界限。第二列仅在我将小时数汇总为天数时使用 - 我不必即时计算日期时间戳,而是使用持久的索引值。
我应该补充一点,在我的情况下,如果那个专用的 C++ 应用程序关闭一段时间是可以的。就是说数据会延迟10分钟以上,但不会丢失任何东西。