Hologres技术揭秘: JSON半结构化数据的极致分析性能

作者:王华峰(花名继儒),Hologres研发

近年来,随着移动端应用的普及,应用埋点、用户标签计算等场景开始诞生,为了更好的支撑这类场景,越来越多的大数据系统开始使用半结构化JSON格式来存储此类数据,以获得更加灵活的开发和处理。Hologres是阿里云自研的云原生一站式实时数仓,支持PB级数据多维分析(OLAP)以及高并发低延迟的在线数据服务(Serving),在对半结构化数据分析场景,Hologres持续优化技术能力,从最开始支持JSONB类型,到支持JSONB GIN索引,再到1.3版本支持JSONB列存,在不牺牲使用灵活性的前提下,提升JSONB数据的写入和查询性能,同时也降低存储成本。JSONB列存也在阿里集团内部多个核心业务使用,其中稳定支撑搜索事业部2022年双11大促,历经生产考验,查询性能提升400%,存储下降50%!

点击查看阿里巴巴搜索事业部双11JSONB实践>>升级JSONB列式存储,Hologres助力淘宝搜索2022双11降本增效!

通过本文,我们将会揭秘Hologres JSONB半结构化数据的技术原理,实现JSON半结构数据的极致分析性能。

什么是半结构化数据

介绍什么是半结构数据之前,我们首先明确下什么是结构化数据。结构化数据可以理解成在关系型数据库(RDBMS)中的一张表,每张表都有明确严格的结构定义,比如包含哪些列,每列的数据类型是怎样的,存储的数据必须严格遵循表结构的定义。

相对应的,半结构化数据就是非固定结构的、经常变化的,且一般是自描述的,数据的结构和内容混杂在一起,最典型的例子就是JSON格式数据。JSON有标准的格式定义,其主要由对象(Object)和数组构成(Array),对象中存储的是键值对,其中键只能是字符串,值可以是字符串、数组、布尔值、Null值、对象或者数组,数组中可以存放任意多个值。

以下就是一个简单的JSON实例,相信大家都很熟悉:

{"user_name": "Adam", "age": 18, "phone_number": [123456, 567890]}

Hologres当前正是通过支持JSON数据类型来提供半结构化数据的能力,为了兼容Postgres生态,我们支持Postgres的JSON/JSONB这两种原生类型,其中JSON类型实际以TEXT格式进行存储,而JSONB类型存储的是解析过后的二进制,因为查询时不需要再解析,所以JSONB在处理时会快很多,下文提到的Hologres半结构化数据方案的很多内部优化都是依托JSONB类型完成的。

我们为什么需要半结构化数据?

半结构化数据得益于其本身的易用性以及强大的表达能力,使得半结构化数据的使用场景非常广泛。

对于数仓来说,每当上游的数据格式有变更时,比如变更数据类型、增删字段,数仓中的强Schema格式的表,必须进行相应的表结构演进(Schema Evoluation)来适配上游的数据,比如需要执行DDL进行加列或者删列,甚至中间的实时数据ETL作业也需要进行适配改动并重新上线。

在有频繁Schema Evoluation的场景的时候,如何保证数据的质量是个很大的挑战,同时维护和管理表结构,对于数据开发人员来说也是一项琐碎且麻烦的工作。

而半结构化数据则天然支持Schema Evoluation,上游业务的变更,只需要在JSON列数据中进行增删相应的字段,无需对数仓中的表做任何DDL就能完成,也能对中间的ETL作业做到透明,这样就能大大降低维护和管理表结构的成本。

传统数仓的半结构化数据解决方案

数仓在处理半结构化数据的时候,衡量一个解决方案好坏的核心考量主要有两点:

  1. 能否保持半结构化数据的易用性和灵活性
  2. 能否实现高效的查询性能

而传统的解决方案常常是顾此失彼,没法做到“熊掌”与“鱼”的兼得。常见的JSON数据处理方式有2种:

以下方案都以JSON数据为例,假设我们有如下JSON数据:

{“user_id”:1001, “user_name”: “Adam”, “gender”: “Male”, “age”: 16}
{“user_id”:1002, “user_name”: “Bob”, “gender”: “Male”, “age”: 41}
{“user_id”:1003, “user_name”: “Clair”, “gender”: “Female”, “age”: 21}

方案1: 数仓直接存储原始JSON数据

一种最直观的方案就是将原始JSON数据存成单独的一列,以Hive为例:

在这里插入图片描述

在存储层,这张Hive表的数据也是以一个完整的JSON值作为最小的存储粒度在磁盘上连续存储:

在这里插入图片描述

之后使用相关的JSON函数进行查询,比如查询所有年龄大于20的用户数:

SELECT COUNT(1) FROM tbl WHERE cast(get_json_object(json_data, '$.age') as int) > 20;

抽象成下面的流程:

上游直接写入JSON类型到Hologres,中间不经过处理,应用层查询时,再去解析需要的数据。
在这里插入图片描述

这种处理方式:

  • 优点是:JSON则天然支持Schema Evoluation,上游业务的变更,只需要在JSON列数据中进行增删相应的字段,无需对数仓中的表做任何DDL就能完成,也能对中间的ETL作业做到透明,最大程度地保留了半结构化数据的易用性和灵活性,能大大降低维护和管理表结构的成本。
  • 缺点是:应用端查询时需要选择合适的处理函数和方法,才能解析到需要的数据,开发较为复杂,如果JSON较复杂,同时查询性能会有退化,因为每次JSON列的数据参与计算的时候,都需要对JSON数据完整的解析一遍,比如需要抽取出整个JSON中某个字段,那么查询引擎执行的时候就要读出每一行JSON,解析一遍,取出需要的字段再返回。这中间会涉及大量的IO和计算,而需要的可能只是JSON数据成百上千字段当中的一个字段,这中间的大量IO和计算都是浪费的。

方案2: 加工成宽表

既然JSON查询时的解析开销很大,那就把解析前置在数据加工链路中,于是另外一种做法就是把JSON拍平成了一张宽表:

在这里插入图片描述

相应的抽象出来的流程如下:

上游是JSON格式,在导入时,将JSON进行解析,比如常见的通过Flink的JSON_VALUE函数解析,然后打宽成一张大宽表,再写入至Hologres,对于上层应用,直接查询Hologres中已经解析好的列。

在这里插入图片描述

对于这种处理方法:

  • 优点是:写入Hologres时,因为是普通列写入,所以写入性能会更好,同时在查询侧,不需要对JSON数据进行解析,查询性能也会更好。
  • 缺点是:每当上游的数据格式有变更时,比如变更数据类型、增删字段、执行DDL进行加列或者删列,中间的实时数据ETL作业也需要进行适配改动并重新上线,使用非常不灵活,也会额外增加运维和开发负担。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值