共计 9337 个字符,预计需要花费 24 分钟才能阅读完成。
1. 前言
阿里时序时空数据库 TSDB 最新推出 TSQL,支持标准 SQL 的语法和函数。用户使用熟悉的 SQL,不仅仅查询更简单易用,用户还可以利用 SQL 强大的功能,实现更加复杂的计算分析。
2. 为什么需要用 SQL 做时序查询?
2.1 SQL 拥有广泛用户基础
SQL 作为一个诞生于上世纪 70 年代的编程语言已经存在几十年了。这是一个相对而言较“古老”的编程语言,但又是一个有着广泛用户基础的语言。
在跟踪主要编程语言的流行程度的 TIOBE index[1]中,SQL 在 2019 年 4 月份的排名是第 8。而如果把排名列在 11-20 之间的 SQL 的两个“兄弟”PL/SQL, Transact-SQL 也合并进来的话,SQL 的流行度应该更高。
根据 stackoverflow 网站的调查 [2],SQL 在最流行的编程语言榜上排在第 4 位。
无论 TIOBE index 还是 stackoverflow 的编程语言排行榜,都从一个侧面反映了 SQL 的广泛用户基础。作为一个查询语言,SQL 是用户和数据库系统交互的 (直接或间接) 主要方式。支持一个拥有广泛用户基础的查询语言,对于推广数据库系统来说,是非常重要的。
2.2 用户学习成本
最近几年出现的几个主要面向时序场景的数据库,除了 TimescaleDB 是在 Postgres 基础上所以支持 PG 生态包括 SQL 语言支持,其他几个比如 InfluxDB, OpenTSDB, Prometheus 都有各自不同的查询语言和接口:InfluxDB 有 InfluxQL,OpenTSDB 有自己的 Restful API, 而 Prometheus 有 PromQL。每一个系统都可以声称自己的语言是独一无二的,更适合时序查询这样的场景;但不可否认的事实是用户需要去花时间去学习一种新的语言,并且如果这个语言为了功能完善,还在不断演进中,这样的学习成本对用户来说,尤其显得高了。
举个例子,InfluxDB 的 InfluxQL 并不支持 Join,Subqueries, 以及 SQL 中很常见的 UDF 等功能,这意味着用户并不能在不同数据之间进行关联分析计算,也不能在系统函数基础上进行扩展开发。InfluxDB 设计者在听到社区的意见后,做了一个很有“创意”的事情:在新版本里支持 Join,UDF 等功能,但并不是让 InfluxQL 变得更加接近于 SQL,而是在一个全新的 Flux(一个新的 functional scripting language)里支持 [3]。用户想要做 InfluxQL 不能做的事情,那就再来学习一个新语言吧。
一个很有意思的事情,10 多年前开始出现的 NoSQL 系统,比如 MapReduce/Hadoop, BigTable,Casandra,HBase 等,一开始也是以各自不同的查询语言出现的。在经历了多年用户推广之后,NoSQL 开始拥抱 SQL,变成了 NotOnlySQL 或者 NewSQL。时序数据库这样一个新兴的数据库领域,也有可能重复这样的历史。原因很简单,用户学习一个新语言的成本越高,越会阻碍一个系统被推广到大众接受的程度。
2.3 BI 工具生态支持
时序数据库提供 SQL 的查询支持,一个很重要的原因是将时序数据库的应用场景扩展到商业分析 (BI/Business Analysis),商业决策这样高附加值领域。
当前几个主要的时序数据库,包括 InfluxDB, OpenTSDB 和 Prometheus,主要侧重于基础性能监控这样的场景,利用 Grafana 这样的可视化工具,实现监控报警这一类基本功能。另一方面,监控报警还没有充分利用挖掘时序数据的商业价值。进一步的功能,需要充分利用现有 SQL 生态系统中的商业分析工具,比如 Tableau, Qlik,Oracle BI, IBM Cognos 等。这些 BI 工具,往往是以 SQL 的方式同后端数据库交互。从这个角度来说,时序数据库的 SQL 支持对于对接 BI 生态系统中的各种工具,尤为重要。
2.4 TSQL 面向的用户群
在阿里时序数据库 TSDB 支持的兼容 OpenTSDB 查询协议之上推出的 TSQL 查询引擎,主要是面向以下两类用户:
**- 时序数据库 TSDB 的新应用开发者
**:这类用户往往以前使用关系数据库,因为关系数据库本身处理时序数据的性能和可扩展性的局限,而转而使用 TSDB。这些新应用开发者,希望 TSDB 在提供比关系数据库更好的时序性能和扩展性的同时,能够用他们以前熟悉的查询语言进行应用开发,而不是去学习一个新的查询语言。
**- 数据分析师:
** 这类用户并不开发应用,他们的工作是利用已有的商业分析工具,对时序数据进行进一步的查询分析。他们本身并不直接使用 SQL,但所使用的工具以 SQL 作为和时序数据库 TSDB 交互的查询语言。
3. 现有时序数据库系统 SQL 查询能力比较
这里简单对比时序数据库系统中提供 SQL 查询,或 SQL-like 查询能力的 InfluxDB, TimescaleDB, 阿里云 TSDB。
4. TSQL 系统架构
上图是 TSQL 的总体架构以及和 TSDB 引擎和存储之间的协调工作关系。简单来讲,TSQL 是一个典型的 MPP 的 SQL 分析引擎,通过 Connector 同 TSDB 引擎和存储进行数据交换。Connector 支持 MetaAPI 和 DataAPI。
TSQL 是在两个 Apache 开源项目基础上演进开发的:
- Apache Calcite 作为 SQL 的解析器和计划生成和优化器。
- Apache Drill 提供分布式的 SQL 执行层。
Apache Calcite 作为一个扩展性好,支持标准 SQL 语法和语义的 SQL 计划生成器,已经被很多数据处理相关的开源项目使用 [6],包括大数据 ETL 的 Apache Hive, HBase 上的 SQL 解决方案 Apache Phoenix, 也有流数据处理框架 Apache Fink (阿里的 Blink) 和 Apache Beam 等。TSQL 使用 Calcite 作为 SQL 计划生成器,可以在兼容标准 SQL 方面,充分利用开源社区已有的成果。
4.1 时序数据 Schema 管理
InfluxDB, OpenTSDB 和 Prometheus 都采用的是一种 Schema-on-write 的方式,也就是用户并不需要明确定义 metric 的 schema, 而是将 schema 的信息隐藏在数据中,在数据写入的时候,同时管理着 schema。这样做的好处是更高的灵活性:
- 在写入数据的时候,用户不需要事先必须用 Create Table DDL 来创建 table;
- 在时序数据 tag set 出现变化的时候,用户不需要事先用 Alter Table 来修改 table 的 schema。
TimeScaleDB 从 PG 上扩展而来,所以是采用的是严格的 Schema 的管理方式。在使用灵活性方面,不如上面其他 3 个时序数据库。
Calcite 作为一个 SQL 计划生成器,很适合时序数据库这样的比较松散的 Schema 管理方式。Calcite 的 Schema Adapter,可以支持
- 动态的 Schema 发现,
- 任意一个数据集,只要实现 Schema 管理中的接口 API, 就可以在计划解析生成阶段被当成一个 Table 来处理。
TSQL 在 Calcite 的 Schema Adapter 基础上,利用 TSDB 引擎中新增加的 MetaAPI,来完成 SQL 计划解析和生成。这免去了用户必须事先在一个集中式的 catalog 中预先定义 Table DDL 等繁琐工作,给用户带来了很多的灵活性。
4.2 时序数据查询执行
TSQL 的执行层,利用了 Apache Drill 的 runtime execution。Drill 的 runtime execution,具备以下特点
- 利用 off-heap 内存作为计算内存,减少 Java heap 内存 GC 所带来的延迟问题
- 基于 Columnar 格式的 ValueVector (Apache Arrow 的前身),提升查询执行效率
- 动态代码生成和编译
UDF 支持
5. TSQL 时序查询功能
我们以一个基础性能监控场景来举例说明 TSQL 能完成的时序查询功能。利用一个时序数据库业界公开的时序性能 Benchmark[5] 生成的模拟数据,按照 DevOps 这样的场景,产生了 cpu 相关的 10 不同的 metric。每个 metric 对应了机房(datecenter),主机(hostname),rack 等标签下所采集的服务器 cpu 相关的指标数据。
5.1 元数据查询
可以用下面的方式查询 TSDB 中所有的 metric/table
SHOW TABLES FROM tsdb
如果我们希望列出所有以 cpu 为前缀的 metric/table,可以在上面的查询基础之上添加附带过滤条件.
show TABLES from tsdb where TABLE_NAME like 'cpu%'
下图给出了命令的部分输出:
在获得 metric/table 名字后,我们可以进一步用 SQL 中的 ’DESCRIBE’ 命令来查询这个 metric/table 的 schema 信息
describe tsdb.`cpu.usage_user`
下图显示了上面的 ’describe’ 命令的部分结果:
5.2 时序数据简单查询
用下面的 SQL 查询可以获得指定时间段内的 ’cpu.usage_user’ 的指标值,时间戳,以及对应的标签值。
select *
from tsdb.`cpu.usage_user`
where `timestamp` between '2019-05-01 16:00:00' and '2019-05-01 18:00:00'
这里,将被转换成 metric/table 下所有的列,包括指标值,时间戳,所有的标签列。可以以具体的列名的一个列表来代替。
作为对比,如果把上面的查询转化成 OpenTSDB 协议来查询,相对应的查询如下:
{
"start": "1556726400000",
"end": "1556733600000",
"queries": [
{
"aggregator": "none",
"metric": "cpu.usage_user",
"rate": null,
"downsample": null,
"filters": []}
]
}
可以在时间戳的过滤条件基础上,增加指标列上的条件。下面的查询,列出指定时间段内,3 台主机上的指标值,并且使用 limit, 把查询结果限制在 100 行。
select *
from tsdb.`cpu.usage_user`
where `timestamp` between '2019-05-01 16:00:00' and '2019-05-01 18:00:00' and
hostname in ('host_1', 'host_5', 'host_10')
limit 100
可以在查询中使用标准 SQL 中丰富的数值计算函数,字符串函数或时间戳函数。下面的 SQL,我们分别使用了数值运算函数 sqrt, 时间戳函数 extract 和字符串 lower。
5.3 时序降精度,聚合运算
如果我们要计算两小时之内,每台主机上每 5 分钟的指标 cpu.usage_user 的最大值,最小值,以及数据采样点的个数。这样的查询,代表了在时间维度上的降精度,并且在标签 hostname 上进行的聚合运算。用 TSQL 来表示这样的查询:
select
hostname,
tumble(`timestamp`, interval '5' minute) ts,
max(`value`) maxV,
min(`value`) minV,
count(`value`) cnt
from tsdb.`cpu.usage_user`
where `timestamp` between 1556726400000 and 1556733600000 and
hostname in ('host_8','host_5','host_6')
group by hostname, ts
如果用 OpenTSDB 的协议来查询:
{
"start": "1556726400000",
"end": "1556733600000",
"queries": [
{
"aggregator": "max",
"metric": "cpu.usage_user",
"downsample": "5m-max",
"tags":{"hostname":"host_8|host_5|host_6"}
},
{
"aggregator": "min",
"metric": "cpu.usage_user",
"downsample": "5m-min",
"tags":{"hostname":"host_8|host_5|host_6"}
},
{
"aggregator": "sum",
"metric": "cpu.usage_user",
"rate": null,
"downsample": "5m-count",
"tags":{"hostname":"host_8|host_5|host_6"}
}
]
}
可以看到,相比较原来 Restful API 的查询,TSQL 能够用更简洁的方式来表示相同的查询语义;并且,如果用户本来就熟悉 SQL 的使用方法,节省用户去学习 Restfule API 里 JSON 各个字段的含义。从降低用户学习成本,增加易用性这个角度,TSQL 带来了较明显的价值。
TSQL 不仅仅带来查询简洁,用户易用的优点,并且,更重要的是,用 TSQL 能够表达 Restful API 里不能直接表达的查询语义。在 TSDB 引入 TSQL 之前,如果用户需要进行这样的查询计算,则用户必须通过自己的应用程序,在 Restful API 获得数据后,再进行后计算,来满足业务需要。在自己的应用程序中进行后计算,往往需要付出很大的应用开发代价。
5.4 聚合后计算,过滤,排序
下面的例子,计算 2 个小时内,3 台机器上每 5 分钟内,cpu.usage_user 指标值的最大值和最小值的差异超过 10.0 的时段和 hostname, 并按照差异值从大到小排序:
在上面的例子中个,在获得最大值和最小值后,进一步计算两者的差异值,并根据差异值进行过滤和排序。这样的聚合后计算处理,无法用 OpenTSDB 的查询协议表示;用户如果要表达这样的语义,就必须在应用程序中计算。
select
hostname,
tumble(`timestamp`, interval '5' minute) ts,
max(`value`) - min(`value`) as diffV
from tsdb.`cpu.usage_user`
where `timestamp` between '2019-05-01 16:00:00' and '2019-05-01 18:00:00' and
hostname in ('host_1', 'host_5', 'host_10')
group by hostname, ts
HAVING diffV > 10.0
order by diffV DESC
5.5 任意复杂的条件表达式
TSDB 的 Restful API 对于只提供有限的几种 filter, 而并不支持任意 filter 通过 AND/OR 的组合。比如下面的例子,是一个 TSQL 业务中使用的查询。其中 WHERE 条件部分是并不能用 Restful API 来表示的,因为 Restful 下的 filters 是只有 AND, 而 OR 只有在相同 tag 上通过 ’value1|value2|vale3’ 这样的形式来表达。
where
((obj_id='ems30_NA62_183249003' and obj_type='ems30_NA62_20204' and room='ems30_NA62_C-T01.NA62' and building='ems30_NA62_C') or
(obj_id='ems30_NA62_183249746' and obj_type='ems30_NA62_20204' and room='ems30_NA62_C-T01.NA62' and building='ems30_NA62_C') or
(obj_id='ems30_NA62_183246962' and obj_type='ems30_NA62_20204' and room='ems30_NA62_C-T01.NA62' and building='ems30_NA62_C') or
(obj_id='ems30_NA62_183248143' and obj_type='ems30_NA62_20204' and room='ems30_NA62_C-T01.NA62' and building='ems30_NA62_C') or
(obj_id='ems30_NA62_183249191' and obj_type='ems30_NA62_20204' and room='ems30_NA62_C-T01.NA62' and building='ems30_NA62_C') or
(obj_id='ems30_NA62_183249964' and obj_type='ems30_NA62_20204' and room='ems30_NA62_C-T01.NA62' and building='ems30_NA62_C') or
(obj_id='ems30_NA62_183247148' and obj_type='ems30_NA62_20204' and room='ems30_NA62_C-T01.NA62' and building='ems30_NA62_C')
) and `timestamp` between '2019-04-25 18:20:21' and '2019-04-25 18:20:31'
...
支持任意组合的 AND/OR 的条件表达式,对于应用开发是很有意义的。在集团基础监控业务 (raptor-pro) 中,一个突出的亮点是“定制化监控报警”:允许业务方的用户来定制查询条件,并且查询条件可以是任意的 AND/OR 组合。TSQL 为 ” 定制化监控报警 ” 的功能实现,提供了有力的技术保障。
5.6 多个 metric 之间 join
这个查询,把 cpu.usage_system 和 cpu.usage_idle 在 hostname 和 timestamp 上做等值 join, 然后计算每 5 分钟两个度量值之和的 sum。
select
t1.hostname,
tumble(t1.`timestamp`, interval '5' minute) ts,
sum(t1.`value` + t2.`value`) as sumV
from tsdb.`cpu.usage_system` t1, tsdb.`cpu.usage_idle` t2
where t1.`timestamp` >='2019-05-01' and t1.`timestamp` <= '2019-05-01 01:00:00'
and t1.hostname = t2.hostname
and t1.`timestamp`= t2.`timestamp`
group by t1.hostname, ts
上面的查询,如果我们采用 TSDB 的多值模型,把 cpu.usage_system 和 cpu.usage_idle 处理成一个 metric 的不同的 field, 则不需要 join 就可以完成。但如果我们需要在分组聚合后的结果上再做 join, 多值模型也无法解决问题。
5.7 分组聚合后 join 计算
下面的查询,分别对 cpu.usage_system 和 cpu.usage_idel 按照 5 分钟计算聚合函数 sum(), 再通过 join, 对齐,计算相对应的比例。并且,每个子查询的 Where 条件,除了包括在 tag 上和时间戳上的条件,还包括值上的过滤条件。
类似这样的查询,是无法直接在 TSDB 的 RestAPI 来实现的;用户只能在自己的应用程序中实现,增加了应用开发成本。
select f0.hostname, f0.ts, f0.sumV / f1.sumV as resultValue
from (
select hostname,
tumble(`timestamp`, interval '5' minute) ts,
sum(`value`) as sumV
from tsdb.`cpu.usage_system`
where
hostname in ('host_0', 'host_5', 'host_10') and
`timestamp` between '2019-05-01 00:00:00' and '2019-05-01 01:00:00' and `value`<=50
group by hostname, ts
) as f1
join (
select hostname,
tumble(`timestamp`, interval '5' minute) ts,
sum(`value`) as sumV
from tsdb.`cpu.usage_idle`
where
hostname in ('host_0', 'host_5', 'host_10') and
`timestamp` between '2019-05-01 00:00:00' and '2019-05-01 01:00:00' and `value`<=30
group by hostname, ts
) as f0
on f1.hostname = f0.hostname and f1.ts = f0.ts
5.8 UDF 扩展功能
使用 UDF 来扩展功能,对于时序数据库这样聚焦特定领域的数据库来说,是非常必要的,因为往往 SQL 标准中定义的函数,并不能完全满足需要。TSQL 有一个完善的 UDF 的体系,用户只要按照约定的接口,用 Java 语义就可以实现扩展。比如,我们在 TSQL 中引入的把时间戳分割成不重合的窗口的函数 tumble,其实现就是由下面不到 15 行代码完成。
用户可以用 Java 实现不同的 scalar UDF 或者 aggregate UDF, 并把编译后的 jar 加入到 TSQL 的系统类库目录,就可以自行扩展 TSQL 的查询计算功能了。
@FunctionTemplate(name = "tumble", scope = FunctionTemplate.FunctionScope.SIMPLE, nulls = FunctionTemplate.NullHandling.NULL_IF_NULL)
public static class Tumble implements DrillSimpleFunc {
@Param TimeStampHolder timeStamp;
@Param IntervalDayHolder interval;
@Output TimeStampHolder out;
@Override
public void setup() {}
@Override
public void eval() {
long intervalMs = interval.days * org.apache.drill.exec.vector.DateUtilities.daysToStandardMillis + interval.milliseconds;
out.value = timeStamp.value - timeStamp.value % intervalMs;
}
}
6.TSQL 可视化查询
阿里云 TSDB 已经提供了 TSQL 可视化交互式开发功能,通过 web 页面可以方便的进行 TSQL 的测试和开发,如下图 Demo 所示。
点击了解阿里云时序数据库
本文作者:Roin
阅读原文
本文为云栖社区原创内容,未经允许不得转载。