这可能是最轻量级的列存技术了
列式存储是提高数据分析计算性能的重要手段。如果数据表的总列数很多而计算涉及的列很少,采用列存就只读取需要的列即可,能够减少硬盘访问量,提高性能。而且,同一列数据往往是同一类型的,甚至有些情况取值都很接近,这样的一批数据连续存储,通常可以实施更高效的数据压缩。
但是,在实际应用中搭建一个列式数据仓库太复杂了。常用的轻量级数据库,比如 Mysql 都不支持列存。列式存储大都存在于大型 MPP 数据库中。Hadoop 也提供了列存文件格式,比如 parquet、orc 等,但仍要借助复杂的环境(Hadoop 或 spark)才能工作,架构过于沉重,虽然软件本身是开源免费的,但整体应用成本还是很高。
那么,有没有不需要繁复架构的轻量级列存技术?
开源计算引擎 esProc 的 ctx 文件就是一种很轻的列存技术。esProc 提供了简捷的编程语言 SPL 用于操作 ctx 文件。
用 SPL 可以把来自各种数据源的数据转成 ctx 实现列存,也可以把 ctx 转成其他数据源。以常见的 csv 和数据库为例:
A |
B |
|
1 |
/csv to ctx |
|
2 |
=T@c("orders.csv").sortx(O_ORDERKEY) |
|
3 |
=file("orders.ctx").create@y(#O_ORDERKEY,O_CUSTKEY,O_ORDERSTATUS,O_TOTALPRICE,O_ORDERDATE,O_ORDERPRIORITY,O_CLERK,O_SHIPPRIORITY,O_COMMENT) |
|
4 |
=A3.append(A2) |
=A3.close() |
5 |
/ctx to csv |
|
6 |
=file("orders_new.csv").export@tc(T@c("orders.ctx")) |
|
7 |
/DB to ctx |
|
8 |
=connect("demo") |
|
9 |
=A8.cursor("select ORDERID,CLIENT,SELLERID,AMOUNT,ORDERDATE from sales order by ORDERID") |
|
10 |
=file("sales.ctx").create@y(#ORDERID,CLIENT,SELLERID,AMOUNT,ORDERDATE) |
|
11 |
=A10.append(A9) |
=A10.close() |
12 |
/ctx to DB |
|
13 |
=A8.execute(T@c("sales.ctx"),"insert into sales (ORDERID,CLIENT,SELLERID,AMOUNT,ORDERDATE) values(?,?,?,?,?)",#1,#2,#3,#4,#5) |
|
14 |
>A8.close() |
列存文件的使用相对行存文件要稍复杂些,需要事先确定数据结构并创建相应的索引区才能写入数据,但在 SPL 支持下,这些代码仍然非常简单。
列存通常用于存储较大数据量,所以这些示例代码都使用了游标,ctx 也可以很好地支持以游标方式流式读写数据。
SPL 还为 ctx 提供了强大的计算能力,支持分段并行:
A |
B |
|
1 |
=file("orders.ctx").open().cursor@m(O_ORDERKEY,O_CUSTKEY;between(O_ORDERDATE,date(1996,1,1):date(1996,1,31))) |
/过滤 |
2 |
=file("orders.ctx").open().cursor@m(O_ORDERDATE,O_TOTALPRICE).groups(O_ORDERDATE;sum(O_TOTALPRICE):all,max(sum(O_TOTALPRICE)):max) |
/分组汇总 |
3 |
=file("orders.ctx").open().cursor@m(O_CUSTKEY;[2,3,5,8].contain(O_CUSTKEY)).total(icount(O_CUSTKEY)) |
/去重计数 |
4 |
=file("orders.ctx").open().cursor@m(O_ORDERKEY,O_TOTALPRICE;![20,31,55,86].contain(O_CUSTKEY)).total(top(-10;O_TOTALPRICE)) |
/TOP N |
5 |
= T("customer.btx").keys(C_CUSTKEY) |
/外键关联 |
6 |
=file("orders.ctx").open().cursor@m(O_ORDERKEY,O_CUSTKEY,O_TOTALPRICE).switch(O_CUSTKEY,A5) |
|
7 |
=file("customer.ctx").open().cursor@m(C_CUSTKEY,C_ACCTBAL) |
/主键关联 |
8 |
=file("customer_info.ctx").open().cursor(CI_CUSTKEY,FUND;;A7) |
|
9 |
=joinx(A7:c, C_CUSTKEY;A8:ci,CI_CUSTKEY) |
|
10 |
=A9.new(c.C_ACCTBAL+ci.FUND:newValue) |
cursor 函数加上 @m 选项,就表示对 ctx 分段进行多线程并行计算,非常简单易用。
代码中文件对象 file("orders.ctx") 可以定义一次反复使用。不过游标只能计算一次,每次计算都要定义新的游标。
分段是并行计算的前提。业界普遍采用的分块列存方案,只有在总数据量很大时才有性能上的意义,一般要达到单表十亿记录、空间约在百 G 左右。规模较小的数据量就不容易获得并行计算的性能提升。ctx 采用了自创的倍增分段方案,很小的数据量且在不断追加的过程中,都可以获得良好的分段效果,保证并行计算的性能提升。
除了倍增分段,ctx 还内置了很多高性能存储方案。比如列存很难实现索引,而 ctx 使用独有的序号机制克服了这个困难。再如 ctx 利用有序存储机制,让同一列的相同值连续存放,进一步提高了列存压缩效率。
经过对比测试,ctx 读取性能几乎比 ORC 快了一倍,更是远远超过了 Parquet:
详细的测试过程和结论参见乾学院:esProc 组表,ORC,Parquet 的对比。
esProc SPL 非常轻,集成开发环境 IDE 即装即用,无需像 Hadoop 那样配置各种环境,更不需要集群:
esProc 提供了标准 JDBC 驱动,使得 ctx 很容易嵌入应用,只要将 esProc 核心 jar 包和配置文件放到 Java 应用的类路径中,ctx 文件和编写好的 SPL 脚本(比如 compute.splx)放到配置好的目录就可以调用了:
…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st =con.prepareCall("call computeCtx()");
st.execute();
ResultSet set = st.getResultSet();
…
对于相同的计算逻辑,SPL 代码量会比 Java 少很多。可以用 SPL 脚本实现 ctx 相关的各种复杂计算,前端应用只要接收计算结果然后展现出来就可以了。
esProc 核心 jar 包非常小,只有不到 100MB。
在报表类应用中,非常适合用 ctx 来缓存报表数据,能够获得专业列存数仓的计算性能,也不必安装配置 MPP 或 Hadoop/spark 这种重量级的产品。
英文版