我每天都有数百万个时间序列点,需要搜索两个重要轴。我的数据如下所示:
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的任意组合。
答案 0 :(得分:1)
基于对查询使用物化视图的答案,如果满足以下条件,则可以进行改进:
将时间序列数据“实时”写入数据库
表示您不要写入过去通过“窗口”的数据,例如。让我们假设昨天。
在这种情况下,您可以合并来自实例化视图的数据,一个保存过去每天的汇总数据的表。
这个想法是,当在特定日期时间之间进行查询时,例如startTime = 2019-03-03 12:00:00-> endTime = 2019-04-02 12:00:00:
假设表data
和AggData
:
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
,这对于这样的条件将是一种改进,例如:
请注意,当获取接近当前时间 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数据库(提供的索引)来说,这都是解决此问题的好方法。
如果到达轴X
和Y
的插入物,则仅获取具有X
和Y
轴的记录,然后在它们上重新运行计算。在我的情况下,这非常有效,因为每个轴对的每日插入频率非常低(尽管所有轴对的插入频率很高)。
何时:
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> SET X#Y#2020-01-01 1
"OK"
redis> INCR X#Y#2020-01-01
(integer) 2
这对于多轴数据来说比较困难。
AWS DynamoDB具有“流”,这些流提供了一种在更改时通知AWS Lambda函数的方法。
MongoDB具有可用于响应数据库更新的变更日志。
在两种情况下,您都可以对数据运行背景图/缩小并根据扫描的数据更新计算。
与使用内存中较小的数据集(Redis)或RDMBS(上方)进行操作相比,这通常要昂贵得多。
注意:我仍在为NoSQL平台上的多轴时序数据寻找更好的解决方案,因为我目前的建议说起来容易做起来难。