汇总多个轴上的时间序列数据?

时间:2019-04-01 17:16:35

标签: database-design time-series mariadb rdbms

我每天都有数百万个时间序列点,需要搜索两个重要轴。我的数据如下所示:

X, Y, value, TIMESTAMP

这些最初存储在MariaDB中,但是表大小增长得太快。即使在具有索引的中型服务器上,执行简单的聚合查询(例如SUM())也要花费很长时间。

以下是一些示例查询:

SELECT COUNT(*) FROM tbl 
WHERE X = 23 AND Y = 46 AND TIMESTAMP > NOW() - INTERVAL 30 DAY

SELECT X, Y, COUNT(*) FROM tbl
WHERE TIMESTAMP > NOW() - INTERVAL 30 DAY
GROUP BY X, Y
ORDER BY COUNT(*) DESC

我有两个索引:

X, Y, value
X, Y, TIMESTAMP

我正在寻找有关存储此数据的方式(或新数据库)的建议,以便在对TIMESTAMP或值进行过滤时快速查找X和Y的任意组合。

3 个答案:

答案 0 :(得分:1)

基于对查询使用物化视图的答案,如果满足以下条件,则可以进行改进:

  

将时间序列数据“实时”写入数据库

表示您不要写入过去通过“窗口”的数据,例如。让我们假设昨天。

在这种情况下,您可以合并来自实例化视图的数据,一个保存过去每天的汇总数据的表

这个想法是,当在特定日期时间之间进行查询时,例如startTime = 2019-03-03 12:00:00-> endTime = 2019-04-02 12:00:00:

  • 从TIMESTAMP在startTime之间-直到startTime的一天结束之间的时间序列表中获取汇总数据(2019-03-03 12:00:00,2019-03-04 00:00:00)< / li>
  • 从实例化视图中获取(2019-03-04,2019-04-01)之间几天的汇总数据
  • 从时间序列表中获取聚合数据,其中TIMESTAMP在startTime之间-直到startTime的一天结束为止(2019-04-02 00:00:00,2019-04-02 12:00:00)< / li>
  • 最后使用全部联盟组合上述值。

enter image description here

假设表dataAggData

CREATE TABLE `data` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `X` varchar(32) NOT NULL,
 `Y` varchar(32) NOT NULL,
 `value` float(10,2) NOT NULL,
 `TIMESTAMP` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`)
);

CREATE TABLE `AggData` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `X` varchar(32) NOT NULL,
 `Y` varchar(32) NOT NULL,
 `DAY` date NOT NULL,
 `sum1` float NOT NULL,
 PRIMARY KEY (`id`)
)

您可以使用以下过程合并数据:

CREATE DEFINER=`root`@`localhost` PROCEDURE `getDataForPeriods`(IN `startTime` INT(32), IN `endTime` INT(32), OUT `AggSum1` FLOAT)
    NO SQL
BEGIN
SELECT SUM(allData.summed1) INTO AggSum1
FROM (SELECT SUM(d1.value) AS summed1,d1.X AS X,d1.Y AS Y FROM `data` d1
WHERE UNIX_TIMESTAMP(d1.`TIMESTAMP`) > startTime
AND UNIX_TIMESTAMP(d1.`TIMESTAMP`) <  UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(startTime + 24*60*60)))
GROUP BY d1.X,d1.Y
      UNION ALL
SELECT SUM(s1.`sum1`) AS summed1,s1.X AS X,s1.Y AS Y FROM AggData s1
WHERE UNIX_TIMESTAMP(s1.DAY) > startTime 
AND UNIX_TIMESTAMP(s1.DAY) + 24*60*60 < endTime
GROUP BY s1.X,s1.Y
     UNION ALL
     SELECT SUM(d2.value) AS summed1,d2.X AS X,d2.Y AS Y FROM `data` d2
WHERE UNIX_TIMESTAMP(d2.`TIMESTAMP`) > UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(endTime)))
AND UNIX_TIMESTAMP(d2.`TIMESTAMP`) < endTime
GROUP BY d2.X,d2.Y) allData
GROUP BY allData.X,allData.Y;
END

考虑条件WHERE TIMESTAMP > NOW() - INTERVAL 30 DAY,这对于这样的条件将是一种改进,例如:

  • 物化表无需频繁更新
  • 瓶颈似乎是查询返回了30天的大型结果集,然后对其进行汇总,这样您可以从物化表中返回大部分数据并汇总更少的行

请注意,当获取接近当前时间 NOW()的数据时,您可以更改第三次查询以包括更多天,以防万一您仍然会收到时间序列数据,例如昨天。

答案 1 :(得分:1)

MySQL和MariaDB没有所需的细节,但是使用Summary表是可行的。但是首先...

mysql> SELECT NOW() - INTERVAL 30 DAY;
+-------------------------+
| NOW() - INTERVAL 30 DAY |
+-------------------------+
| 2019-03-10 11:48:24     |
+-------------------------+

您真的要跨越一段开始的30天吗?人们通常只需要30天的时间:

WHERE ts >= CURDATE() - INTERVAL 30 DAY
  AND ts  < CURDATE();

mysql> SELECT CURDATE() - INTERVAL 30 DAY, CURDATE();
+-----------------------------+------------+
| CURDATE() - INTERVAL 30 DAY | CURDATE()  |
+-----------------------------+------------+
| 2019-03-10                  | 2019-04-09 |
+-----------------------------+------------+
1 row in set (0.00 sec)

甚至纪念变长月份:

WHERE ts >= CURDATE() - INTERVAL 1 MONTH
  AND ts  < CURDATE();

mysql> SELECT CURDATE() - INTERVAL 1 MONTH, CURDATE();
+------------------------------+------------+
| CURDATE() - INTERVAL 1 MONTH | CURDATE()  |
+------------------------------+------------+
| 2019-03-09                   | 2019-04-09 |
+------------------------------+------------+

如果您只希望整天查看,则构建和维护摘要表(具体化视图)既简单又高效:

CREATE TABLE SummaryXY (
    x ...,
    y ...,
    dy DATE,
    ct INT UNSIGNED,
    PRIMARY KEY(x,y,dy)
) ENGINE=InnoDB;

您将有一项工作要在每高一个午夜之后添加新行。

另一方面,如果您需要更新到当前时间,则可以通过IODKU(INSERT ... ON DUPLICATE KEY UPDATE...)进行更新,该更新将根据需要进行更新或插入。

如果您需要回到几小时而不是几天,请更改dy。但是,如果您确实需要返回任意一秒,则分两步执行任务:

SELECT
    ( SELECT COUNT(*) FROM RawData WHERE ... (the partial day 30 days ago) ) +
    ( SELECT SUM(ct) FROM SummaryXY WHERE ... (the 30 full days) );

(并由IODKU或类似的SELECT COUNT(*) FROM RawDATA处理部分当天的时间。)

您的简单示例是否很复杂?我所描述的内容对X=constant AND y=constant AND ts...来说效果很好,但对X>constant等效果不好。

如果您需要AVG(value),则存储COUNT(*)(如上所述)和SUM(VALUE)。然后,得出平均值:

SUM(value_sum) / SUM(ct)

如果您还需要WHERE x=1 AND w=2 AND ts...,则根据x,w,ts构建第二个摘要表。

如果您还需要WHERE x=1 AND y=1 AND z=3 AND ts...,则根据x,y,z,ts构建一个摘要表,但将其用于x,y,ts。可能典型的是5个汇总表处理40个案例。

有关汇总表的更多讨论:http://mysql.rjweb.org/doc.php/summarytables

您的第二个查询(GROUP BY X, Y ORDER BY COUNT(*) DESC)当前会对大型Raw表进行表扫描,即使您索引ts。使用我建议的摘要表,查询将是摘要表的表罐。由于它可能小10倍,因此对其进行表扫描将明显更快。

COUNT(*)上的额外排序是一个很小的负担;这取决于结果集中的行数。

答案 2 :(得分:0)

Raymond Nijland发布了一个建议,以使用实例化视图(根据其他表的查询构建的表)。最初,我将其消除了,因为我当前用于构建实例化视图的查询需要(几乎)全表扫描来运行计算,而这正是我要避免的问题。

但是,也可以一次一次构建一个物化视图,对于NoSQL和SQL数据库(提供的索引)来说,这都是解决此问题的好方法。

RDBMS

如果到达轴XY的插入物,则仅获取具有XY轴的记录,然后在它们上重新运行计算。在我的情况下,这非常有效,因为每个轴对的每日插入频率非常低(尽管所有轴对的插入频率很高)。

何时:

INSERT X, Y, value, TIMESTAMP

然后运行:

INSERT INTO reports (X, Y, cnt, updated_at, ...) 
SELECT X, Y, COUNT(*), NOW(), ...(other columns)... FROM tbl 
WHERE X = ? AND Y = ? AND TIMESTAMP BETWEEEN ? AND ?)

这是一个模糊的示例,但是假设结构正确的索引和分区/主键,您可以维护一个经常更新的物化报告表。

如果某些轴的更新不是很频繁,则可以运行第二个后台任务来识别和删除/更新行WHERE updated_at < NOW() - INTERVAL 1 DAY

Redis

原子计数器是一种非常有用的方法,可以保持传入指标的总得分。每次插入后,只需为您关心的轴更新一个单独的复合键计数器:

redis> SET X#Y#2020-01-01 1
"OK"
redis> INCR X#Y#2020-01-01
(integer) 2

这对于多轴数据来说比较困难。

DynamoDB,MongoDB等...

  • AWS DynamoDB具有“流”,这些流提供了一种在更改时通知AWS Lambda函数的方法。

  • MongoDB具有可用于响应数据库更新的变更日志。

在两种情况下,您都可以对数据运行背景图/缩小并根据扫描的数据更新计算。

与使用内存中较小的数据集(Redis)或RDMBS(上方)进行操作相比,这通常要昂贵得多。

注意:我仍在为NoSQL平台上的多轴时序数据寻找更好的解决方案,因为我目前的建议说起来容易做起来难。