子图 > 最佳实践
7 分钟
子图最佳实践5-使用时间序列和聚合进行简化和优化
TLDR
利用子图中新的时间序列和聚合功能可以显著提高索引速度和查询性能。
概述
时间序列和聚合通过将聚合计算卸载到数据库和简化映射代码来减少数据处理开销并加速查询。这种方法在处理大量基于时间的数据时特别有效。
时间序列和聚合的好处
- 缩短了索引时间
- 要加载的数据更少:映射处理的数据更少,因为原始数据点存储为不可变的时间序列实体。
- 数据库管理聚合:聚合由数据库自动计算,减少了映射的工作量。
- 简化映射代码
- 无需手动计算:开发人员不再需要在映射中编写复杂的聚合逻辑。
- 降低复杂性:简化代码维护,最大限度地减少错误的可能性。
- 查询速度快得多
- 不可变数据:所有时间序列数据都是不可变的,可以实现高效的存储和检索。
- 高效的数据分离:聚合与原始时间序列数据分开存储,使查询处理的数据大大减少,通常减少几个数量级。
重要注意事项
- 不可变数据:时间序列数据一旦写入就不能更改,从而确保数据完整性并简化索引。
- 自动ID和时间戳管理:ID和时间戳字段由图节点自动管理,减少了潜在的错误。
- 高效的数据存储:通过将原始数据与聚合分离,存储得到优化,查询运行速度更快。
如何实现时间序列和聚合
先决条件
此功能需要规范版本1.1.0
。
定义时间序列实体
时间序列实体表示随时间收集的原始数据点。它是用@entity(timeseries:true)
注释定义的。关键要求:
- 不可变:时间序列实体始终是不可变的。
- 必填字段:
id
:必须是Int8!
类型并且自动递增。timestamp
:必须是Timestamp!
类型并自动设置为块时间戳。
例子:
1type Data @entity(timeseries: true) {2 id: Int8!3 timestamp: Timestamp!4 amount: BigDecimal!5}
定义聚合实体
聚合实体从时间序列源计算聚合值。它是用@aggregation
注释定义的。关键部件:
- 注释参数:
interval
:指定时间间隔(例如,["hour", "day"]
)。
例子:
1type Stats @aggregation(intervals: ["hour", "day"], source: "Data") {2 id: Int8!3 timestamp: Timestamp!4 sum: BigDecimal! @aggregate(fn: "sum", arg: "amount")5}
在这个例子中,Stats在每小时和每天的时间间隔内聚合Data中的金额字段,计算总和。
查询聚合数据
聚合通过查询字段公开,这些字段允许基于维度和时间间隔进行过滤和检索。
例子:
1{2 tokenStats(3 interval: "hour"4 where: { token: "0x1234567890abcdef", timestamp_gte: "1704164640000000", timestamp_lt: "1704251040000000" }5 ) {6 id7 timestamp8 token {9 id10 }11 totalVolume12 priceUSD13 count14 }15}
在聚合中使用维度
维度是用于对数据点进行分组的非聚合字段。它们支持基于特定标准的聚合,例如金融应用程序中的代币。
例子:
时间序列实体
1type TokenData @entity(timeseries: true) {2 id: Int8!3 timestamp: Timestamp!4 token: Token!5 amount: BigDecimal!6 priceUSD: BigDecimal!7}
带维度的聚合实体
1type TokenStats @aggregation(intervals: ["hour", "day"], source: "TokenData") {2 id: Int8!3 timestamp: Timestamp!4 token: Token!5 totalVolume: BigDecimal! @aggregate(fn: "sum", arg: "amount")6 priceUSD: BigDecimal! @aggregate(fn: "last", arg: "priceUSD")7 count: Int8! @aggregate(fn: "count", cumulative: true)8}
- 维度字段:代币对数据进行分组,因此每个代币计算聚合。
- 聚合:
- totalVolume:总金额。
- priceUSD:最后记录的价格USD。
- count:记录的累计计数。
聚合函数和表达式
支持的聚合功能:
- 求和
- 计数
- 最小值
- 最大值
- 第一
- 最后
@aggregate中的参数可以是
- 时间序列实体中的字段名。
- 使用字段和常量的表达式。
聚合表达式示例
- 代币总值: @aggregate(fn: “sum”, arg: “priceUSD _ amount”)
- 最大正金额: @aggregate(fn: “max”, arg: “greatest(amount0, amount1, 0)”)
- 条件总和: @aggregate(fn: “sum”, arg: “case when amount0 > amount1 then amount0 else 0 end”)
支持的运算符和函数包括基本算术(+、-、_、/)、比较运算符、逻辑运算符(和、或、非)以及最大、最小、合并等SQL函数。
查询参数
- interval:指定时间间隔(例如,“hour”)。
- where: 基于维度和时间戳范围的筛选器。
- timestamp_gte/timestamp_lt:开始和结束时间的筛选器(自时期以来的微秒)。
注意:
- 排序:结果按时间戳和id降序自动排序。
- 当前数据:可选的当前参数可以包括当前部分填充的间隔。
结论
在子图中实现时间序列和聚合是处理基于时间的数据的项目的最佳实践。这种方法:
- 提高性能:通过减少数据处理开销来加快索引和查询。
- 简化开发:消除了在映射中手动聚合逻辑的需要。
- 高效扩展:在不影响速度或响应性的情况下处理大量数据。
通过采用这种模式,开发人员可以构建更高效、更可扩展的子图,为最终用户提供更快、更可靠的数据访问。要了解有关实现时间序列和聚合的更多信息,请参阅时间序列和聚集自述文件,并考虑在子图中尝试此功能。