数据源的schema老变,如何存储——(Clickhouse篇)?

来源:安瑞哥是码农


这个问题是之前球友群里的几个小伙伴提出来的,说自己当前工作中遇到了这样的问题:


某个业务上游数据源为 json ,但是 json 里面的 key 是变动的,有时候多,有时候少、且 key 名也不全一样。


但是,需求又想把这个数据存起来后,可以根据 json 里面的 key 进行查询。


问这种情况,该怎么对数据进行存储?


巧了,最近我在工作中,也遇到了跟这一毛一样的场景。


这种数据源schema-free情况,对于当下的很多数据库来说,是提供了现成解决方案的,比如我最近测试用得最多的数据库,Clickhouse(下称CK)、Doris、Elasticsearch,都支持对 json 数据的存储和查询。


但是我们要知道,这种官方宣称的功能,支持归支持,具体它到底好不用,咱得把它「跑起来」才能知道。


由于以上3种数据库,在理论上都能满足当前的这个需求,那我就准备做一个系列性的测评,在摸清楚它们3,各自如何搞定这个问题的同时,也能找出最符合你胃口的方案。


废话不多说,咱先从 CK 开始。



0. 理论准备


想要对一个 schema-free 的 json 数据进行有效存储,大致方案一般有两个:


第1个:把里面所有的 key-value 都展开,分别作为表的字段跟值存储;


第2个:直接把整个 json 作为一个 json 类型进行存储,然后根据数据库提供的,对 json 类型数据的用法,对目标 key 进行查询。


显然,对于 CK 来说,很难满足第1个条件,因为 CK 是一个 「schema-full」 的数据库,数据在写入前,必须严格按照建表时,预定义的字段来,否则,数据就写不进去,也查询不出来(类比 ES 就可以)。


于是,我们只能采用第2个方案。


从 CK 官网中可以看到,它对 json 的支持,体现在两个方面:


一个是支持将 json 格式的字符串,以 json 类型进行存储,只不过呢,当前这个功能还只是处于「实验」阶段,而从后面的操作来看,确实有很多明显的 bug。


CK官网支持的 json 类型


另一个,则是支持对 json 格式的字符串(string 类型),利用对应的 json 函数,对其中的数据进行处理


CK官网支持的 json 函数


从我对这两部分官网的描述和理解来看,如果要对 json 数据,达到文章开头提到的需求的话,就必须要把 json (String)存成 json 类型。



1. 开始操作


我这里当前是通过域名,获取它的 whois 信息,然后它的结果返回就是一个json (String),只不过,对于不同的域名来说,它返回的 json (String)信息不全一样,也就是说,它里面的 key 是变化的。


原始的 json 数据(字符类型)

可以看到,这里截图出来的3条数据中,data 这个 key 里面的数据内容(也是个 json string),都不一样,而我现在的需求就是,要把这个 data 里面的内容(json string),用一种合理的方式给存储起来,方便对里面的 key 展开查询。


1.1 抽取 json 字符串中的 data 数据


对于存储这个原始 json 数据(detail 字段)的表来说,最开始为了方便入库,所以它存储的格式为 String,而现在,我们需要将这个 json string 中 data 的 value 部分提取出来(也是 json string),把它存储为一个 json 类型的字段(detail_json 字段)。


其中,原始表的表结构为:



目标表结构为:



按理说这个操作应该挺简单才对,对于CK,它提供了至少两种提取 json 中 key 的函数。


一个叫simpleJSONExtractRaw」,另一个叫simpleJSONExtractString」,虽然这两个函数,从我的原始表测试查询返回的结果来看,都是一样的。


但是,在把数据源表中的数据,给提取到目标表的时候,情况就变得不一样了。


先用JSONExtractRaw」函数提取 data key 的数据:


直接就报错了。


再用simpleJSONExtractString」试试:



虽然是不报错了,但是,咱来看一眼写进去的 json 数据(data 这个 key 对应的 value):


这都他娘的什么鬼?


而且,说出来你可能不信,我把官网提供的,所有能够提取 data 这个 key 对应 value 的函数都试了(可把我累够呛),要么是写不进去,要么就是上面你看到的这个熊样。


要知道,我这写进去的数据,可都是经过了 json 类型判断的呀,居然都不行,就离谱。


既然这种方法行不通,那就只能换另一条出路。


1.2  直接存储原始 json 字符串为 json 类型


把上的写入 SQL 语气,改为下面这种:



结果,居然非常顺利的写成功了。


看一眼写进去的数据:


从这个写进去的结构来看,CK 自动统一了 json 里面的 key,让不同的 json 之间保持 key 一致,这个是不是有点像我们上学那会数学学里面的「最大公倍数」


也就是说,存储进来的任何一个 json,里面包含的 key,是所有原始 json key 的总和,如果原始 json 没有某个 key,那么统一之后,新 json 对应这个 key 的值就为空


而且,为了方便后续的查询,新的 json,会将原本有嵌套 json 的内容,全部拉平


比如,我要查询注册在冰岛这个国家的域名有多少个,SQL 语句就可以这么来写:


想想,如果咱是对存储为 String 类型的 json,实现同样的功能,是不是得费老鼻子劲了,而且关键,查询的效率一定会更低。


另外,还可以用 json 里面的 key 进行聚合查询,比如:



所以这个时候,你就完全可以把 json 里面的 key,当普通的表字段来使用。



2. 还有其他哪些坑


第一个坑:


这个严格来说,算是个注意事项,对于 CK 来说,这个 json 类型,因为目前还只是「实验」阶段,所以如果你要想使用它,就必须得打开这个允许实验的设置。


否则,就会抛出下面的异常



第二个坑:


比如,就刚才那个 insert 语句,一毛一样的 SQL,在执行的时候,时而能运行,时而又不行。


不行时抛出的异常

行的时候又可以

就问你懵不懵逼吧?耐心不够的人,遇到这种,估计早就骂娘了有没有。


最后

对于一开始文中提出的问题,CK 确实有对应的解决方案,那就是把这个 schema-free 的数据,也就是 json String,为一个字段,给存储到 CK 的 json 类型中,然后用我文中提到的方式进行查询,使用方法跟普通字段一样自如。

只不过呢,可能是由于当前的这个 json 数据类型,尚处于临床实验阶段的原因,我在用的时候,确实有一些莫名其妙的 bug,中间好几次都想砸键盘来着,但好在忍住了。

虽然算是解决了问题,但是体验过程确实比较一般,关于这部分还有很多地方需要完善和优化。

那么对于 CK 的 json 类型,如果是你,你会用吗?

(PS:下期对比测试 Doris 的 json 类型,希望你不要嫌弃)

请使用浏览器的分享功能分享到微信等