Database design 在多个轴上聚合timeseries数据?

Database design 在多个轴上聚合timeseries数据?,database-design,time-series,mariadb,rdbms,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,

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

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

我正在寻找有关存储此数据的方法(或新数据库)的建议,以便在过滤时间戳或值的同时快速查找X和Y的任何组合。

Raymond Nijland发布了使用物化视图(从其他表上的查询构建的表)的建议。起初,我拒绝了它,因为我当前用于构建物化视图的查询需要(几乎)一个完整的表扫描来运行计算,这是我试图避免的问题

然而,一次也可以构建一个物化视图,这对于NoSQL和SQL数据库(提供索引)来说都是一个很好的解决方案

关系数据库 如果轴
X
Y
的插入到达,则仅获取轴
X
Y
的记录,并对其重新运行计算。在我的例子中,这非常有效,因为每个轴对每天插入的频率非常低(尽管所有轴对插入的频率都很高)

当:

然后运行:

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

雷迪斯 原子计数器是一种非常有用的方法,可以为传入的度量保留聚合分数。每次插入后,只需为您关心的轴更新一个单独的复合关键点计数器:

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平台上多轴时间序列数据的更好解决方案,因为我目前的建议说起来容易做起来难。

基于您对使用物化视图进行查询的回答,如果:

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

这意味着你不会写过去通过“窗口”的数据,例如,让我们假设昨天

在这种情况下,您可以组合物化视图中的数据,一个包含过去每天聚合数据的表

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

  • 从时间序列表中获取聚合数据,其中时间戳介于开始时间和结束时间之间(2019-03-03 12:00:002019-03-04 00:00:00)
  • 从物化视图获取(2019-03-042019-04-01)之间天数的聚合数据
  • 从时间序列表中获取聚合数据,其中时间戳介于开始时间-到开始时间(2019-04-02 00:00:002019-04-02 12:00:00)的当天结束之间
  • 最后,使用union all组合上述值

假设表
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
CREATE DEFINER=`root`@`localhost`过程`getDataForPeriods`(在`startTime`INT(32)中,在`endTime`INT(32)中,在`AggSum1`FLOAT中)
无SQL
开始
选择SUM(allData.summed1)进入AggSum1
FROM(从'data`d1中选择SUM(d1.value)作为summed1,d1.X作为X,d1.Y作为Y
其中UNIX_时间戳(d1.`TIMESTAMP`)>startTime
和UNIX_时间戳(d1.`TIMESTAMP`)startTime
和UNIX_时间戳(s1.DAY)+24*60*60UNIX_时间戳(日期(FROM_UNIXTIME(endTime)))
和UNIX_时间戳(d2.`TIMESTAMP`)
查看条件
WHERE TIMESTAMP>NOW()-间隔30天
,这将是对此类条件的改进,如下所示:

  • 物化表不需要频繁更新
  • 瓶颈似乎是查询返回一个30天的大型结果集,然后对其进行聚合,这样您可以从物化表返回大部分数据,并聚合少得多的行

请注意,当获取接近当前时间的数据时,您可以将第三个查询更改为包含更多的日期,而不仅仅是今天,以防您仍然收到时间序列数据,例如昨天的数据。

MySQL和MariaDB没有所需的详细信息,但汇总表是可行的。但是首先

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 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;
SELECT
    ( SELECT COUNT(*) FROM RawData WHERE ... (the partial day 30 days ago) ) +
    ( SELECT SUM(ct) FROM SummaryXY WHERE ... (the 30 full days) );
SUM(value_sum) / SUM(ct)