6 分钟
子图最佳实践3-通过使用Immutable Entities和Bytes作为ID来提高索引和查询性能
TLDR
在schema.graphql
文件中使用Immutable Entities和Bytes作为ID显著改进索引速度和查询性能。
不可变实体
为了使一个实体不可变,我们只需在实体中添加(immutable:true)
。
1type Transfer @entity(immutable: true) {2 id: Bytes!3 from: Bytes!4 to: Bytes!5 value: BigInt!6}
通过使Transfer
实体不可变,graph-node能够更有效地处理实体,提高索引速度和查询响应能力。
Immutable Entities structures will not change in the future. An ideal entity to become an Immutable Entity would be an entity that is directly logging on-chain event data, such as a Transfer
event being logged as a Transfer
entity.
在后台
可变实体有一个块范围
表示其有效性。更新这些实体需要graph节点调整以前版本的块范围,从而增加数据库工作负载。查询还需要筛选,以便只找到活动实体。不可变实体更快,因为都是活的,而且不会改变,在写入时不需要检查或更新,在查询时也不需要筛选。
何时不使用不可变实体
如果你有一个类似字段的状态
需要随着时间的推移而修改,那么你不应该让实体不可变。否则,您应该尽可能使用不可变实体。
字节作为ID
每个实体都需要一个ID。在前面的示例中,我们可以看到ID已经是Bytes类型。
1type Transfer @entity(immutable: true) {2 id: Bytes!3 from: Bytes!4 to: Bytes!5 value: BigInt!6}
虽然其他类型的ID也是可能的,如String和Int8,但建议对所有ID使用Bytes类型,因为字符串存储二进制数据所需的空间是Byte字符串的两倍,并且UTF-8字符串的比较必须考虑到区域设置,这比用于比较Byte串的逐字节比较要昂贵得多。
不使用字节作为ID的原因
- 如果实体ID必须是人类可读的,例如自动递增的数字ID或可读字符串,则不应使用Bytes 作为 ID。
- 如果将子图的数据与不使用Bytes作为ID的另一个数据模型集成,则不应使用Bytes 作为 ID。
- 索引和查询性能的改进是不可取的。
以Bytes作为ID连接
在许多子图中,使用字符串连接将事件的两个属性组合成一个ID是一种常见的做法,例如使用event.transaction.hash.toHex()+“-”+event.logIndex.toString()
。然而,由于这会返回一个字符串,将严重阻碍子图索引和查询性能。
相反,我们应该使用concatI32()
方法来连接事件属性。此策略产生了性能更高的Bytes
ID。
1export function handleTransfer(event: TransferEvent): void {2 let entity = new Transfer(event.transaction.hash.concatI32(event.logIndex.toI32()))3 entity.from = event.params.from4 entity.to = event.params.to5 entity.value = event.params.value67 entity.blockNumber = event.block.number8 entity.blockTimestamp = event.block.timestamp9 entity.transactionHash = event.transaction.hash1011 entity.save()12}
以Bytes作为ID排序
如本示例查询和响应所示,使用字节作为ID进行排序不是最佳选择。
查询:
1{2 transfers(first: 3, orderBy: id) {3 id4 from5 to6 value7 }8}
查询响应:
1{2 "data": {3 "transfers": [4 {5 "id": "0x00010000",6 "from": "0xabcd...",7 "to": "0x1234...",8 "value": "256"9 },10 {11 "id": "0x00020000",12 "from": "0xefgh...",13 "to": "0x5678...",14 "value": "512"15 },16 {17 "id": "0x01000000",18 "from": "0xijkl...",19 "to": "0x9abc...",20 "value": "1"21 }22 ]23 }24}
ID以十六进制返回。
为了改进排序,我们应该在BigInt实体上创建另一个字段。
1type Transfer @entity {2 id: Bytes!3 from: Bytes! # address4 to: Bytes! # address5 value: BigInt! # unit2566 tokenId: BigInt! # uint2567}
这将允许按顺序优化排序。
查询:
1{2 transfers(first: 3, orderBy: tokenId) {3 id4 tokenId5 }6}
查询响应:
1{2 "data": {3 "transfers": [4 {5 "id": "0x…",6 "tokenId": "1"7 },8 {9 "id": "0x…",10 "tokenId": "2"11 },12 {13 "id": "0x…",14 "tokenId": "3"15 }16 ]17 }18}
结论
使用Immutable Entities和Bytes作为ID已被证明可以显著提高子图效率。具体来说,测试表明查询性能提高了28%,索引速度提高了48%。
在Edge&Node的软件工程师David Lutterkort的这篇博客文章中,可以阅读到更多关于使用Immutable Entities和字节作为ID的信息:两个简单的子图性能改进。