比 csv 快 N 倍的通用文件格式
用 csv 文件存储数据很普遍。类似的还有纯文本文件、tsv 文件等等,这些文件都是平面结构,没有层次关系,数据都存成文本字符。有些 xls 文件也是类似的平面结构。
csv 文件具有简单、通用性强等优点,但也存在一些明显的缺点。文本字符不能直接参与计算,把这些字符解析成日期、实数等内存数据类型是非常耗时的过程,特别是要判断非法情况时会更复杂,所以 csv 文件的性能一向不好。数据无压缩会使 csv 文件体积膨胀,占用更大存储空间,进一步拖累性能,也对文件的操作和管理造成困难。高精度数值数据存成文本还可能丢失信息,其他如日期型数据、包含逗号的字符串等也可能在解析的时候出现错误,都要人为加上引号并在解析时专门处理才能消除。
csv 文件的这些问题都可以用二进制格式的文件来解决。但是,业界缺少一种通用、简单的二进制存储格式。很多软件都有高效的二进制文件格式,但绝大多数是私有格式,不对外提供 API,也就无法供第三方使用。而 Parquet 这些开源的二进制格式文件又过于沉重了,虽然有公开的 API,但需要借助复杂的环境(比如 Hadoop)才能工作,应用成本过高。
开源计算引擎 esProc 的 btx 是一种非常简单、开放的二进制文件格式,性能比 csv 文件快 4-5 倍!在 btx 文件中,可以直接写入各种数据类型对应的内存字节,读取时也只要直接取出重新装载成内存数据,没有复杂的解析过程,也不需要判断和识别非法情况。btx 还提供了简单压缩方式,采用适当的压缩比,既能一定程度上减少硬盘空间占用,又不会在读取时带来过多的 CPU 消耗。
btx 覆盖了 csv 文件的所有功能,除了存储日期、数值、字符串等,还允许更复杂的集合和记录数据类型,支持数据的嵌套存储。
使用 esProc 提供的脚本语言 SPL 很容易实现 btx 与 csv/DB 的相互转换,可以保证 btx 的通用性:
A |
|
1 |
/btx to csv |
2 |
=file("nation.csv").export@ct(T("nation.btx")) |
3 |
=file("orders.csv").export@ct(T@c("orders.btx")) |
4 |
/csv to btx |
5 |
=file("nation.btx").export@b(T("nation_new.csv")) |
6 |
=file("orders.btx").export@b(T@c("orders_new.csv")) |
7 |
/DB to btx |
8 |
=connect("demo") |
9 |
=file("cities.btx").export@b(A8.query("select * from cities")) |
10 |
=file("sales.btx").export@b(A8.cursor("select * from sales")) |
11 |
/btx to DB |
12 |
=A8.execute(T("cities_new.btx"),"insert into cities (CID,NAME,POPULATION,STATEID) values(?,?,?,?)",#1,#2,#3,#4) |
13 |
=A8.execute(T@c("sales_new.btx"),"insert into sales(ORDERID,CLIENT,SELLERID,AMOUNT,ORDERDATE) values(?,?,?,?,?)",#1,#2,#3,#4,#5) |
14 |
>A8.close() |
除了 csv/DB 外,其他数据源都可以和 btx 相互转换,详细介绍参见 SPL 的说明文档。各种数据源转 btx 时都不需要事先建数据结构,直接写入文件就可以了,非常方便。数据量较大时,可以采用游标方式转换。
SPL 还为 btx 提供了强大的计算能力:
A |
B |
|
1 |
=T("nation.btx").select(N_NAME=="CHINA" && like(N_COMMENT,"*express*")) |
/过滤 |
2 |
=T("nation.btx").groups(N_REGIONKEY;count(1):nationCount) |
/分组汇总 |
3 |
=T("nation.btx").select(N_NATIONKEY>10).icount(N_REGIONKEY) |
/去重计数 |
4 |
=T("customer.ctx").select([2,3,5,8].contain(C_NATIONKEY)).top(3;C_ACCTBAL) |
/TOP N |
5 |
=T("nation.btx").keys(N_NATIONKEY) |
|
6 |
=T("customer.btx").switch(C_NATIONKEY,A5:N_NATIONKEY) |
/外键关联 |
7 |
=T("nation.btx").keys(N_NATIONKEY) |
|
8 |
=T("nation_info.btx").keys(NI_NATIONKEY) |
|
9 |
=join(A7:n,N_NATIONKEY;A8:ni,POPULATION) |
|
10 |
=A9.new(n.N_NAME/"-"/ni.POPULATION) |
/主键关联 |
这里每次都使用了 T() 函数读取 btx,适合独立进行多种计算。如果是连续的多步骤计算,只要读一次文件,后续计算都基于内存表就可以了。
btx 较大时,SPL 还支持大文件游标外存计算和分段并行计算。
A |
B |
|
1 |
=file("orders.btx").cursor@bm(O_ORDERKEY,O_CUSTKEY).select(between(O_ORDERDATE,date(1996,1,1):date(1996,1,31)).fetch(100) |
/大文件游标,外存过滤 |
2 |
=file("orders.btx").cursor@bm(O_ORDERDATE,O_TOTALPRICE).groups(O_ORDERDATE;sum(O_TOTALPRICE):all,max(sum(O_TOTALPRICE)):max) |
/分组汇总 |
3 |
=file("orders.btx").cursor@bm(O_CUSTKEY).total(icount(O_CUSTKEY)) |
/去重计数 |
4 |
=file("orders.btx").cursor@bm(O_ORDERKEY,O_TOTALPRICE).total(top(-10;O_TOTALPRICE)) |
/TOP N |
5 |
= T("customer.btx").keys(C_CUSTKEY) |
|
6 |
=file("orders.btx").cursor@bm(O_ORDERKEY,O_CUSTKEY,O_TOTALPRICE).switch(C_NATIONKEY,A5:N_NATIONKEY) |
/外键关联 |
7 |
=file("customer.btx").cursor@bm(C_CUSTKEY,C_ACCTBAL) |
|
8 |
=file("customer_info.btx").cursor@bm(CI_CUSTKEY,FUND) |
|
9 |
=joinx(A7:c, C_CUSTKEY;A8:ci,CI_CUSTKEY) |
|
10 |
=A9.new(c.C_ACCTBAL+ci.FUND:newValue) |
/主键关联,要求游标对主键有序 |
cursor@b 函数可以定义 btx 文件的游标进行外存计算,@m 选项表示对 btx 分段进行多线程并行计算,可以显著提高外存计算性能。
代码中文件对象 file("orders.btx") 可以定义一次反复使用。不过游标只能计算一次,每次计算都要定义新的游标。多步骤计算则可以使用延迟游标。
esProc SPL 很轻,集成开发环境 IDE 即装即用,无需像 Hadoop 那样配置各种环境,更不需要集群:
esProc 提供了标准 JDBC 驱动,使得 btx 很容易嵌入应用,只要将 esProc 核心 jar 包和配置文件放到 Java 应用的类路径中,btx 文件放到配置好的目录就可以调用了:
…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st =con.prepareCall("=T(\"nation.btx\")");
st.execute();
ResultSet set = st.getResultSet();
…
这个例子是用 Java 读取 btx 文件后进行处理,更推荐的做法是编写 SPL 脚本处理 btx 相关的各种复杂计算,代码量比 Java 简单很多。在应用中调用 SPL 脚本(比如 csv2btx.splx)也很简单,这个 Java 代码稍作修改就可以了:
…
st =con.prepareCall("call csv2btx()");
st.execute();
…
esProc 核心 jar 包非常小,只有不到 100MB。
在报表类应用中,非常适合用 btx 来存储报表的缓存数据,能够实现应用本地的可控缓存,包括部分缓存和缓存复用,效果远好于 csv 文件或数据库临时表。
在数据分析场景中,把数据库中的部分数据存入 btx 随身携带,可以随时随地使用 SPL 的 IDE 进行分析。
英文版