DuckDB – 笔记本的数据处理工具,现在支持Clojure (2023)
2023-09-02 确立需求 我们的内存列主数据处理平台 tech.ml.dataset (TMD) 驱动着功能性数据科学的未来。当数据变得足够大而无法放入内存时,可以通过对数据的样本进行操作,或通过过滤相关子集来继续使用 TMD,以适应工作环境施加的限制。此外,可以使用 nippy、arrow 或 parquet 实现数据的小和大持久性。当数据的规模足够大时,例如大约 100GB 的 .csv 文件集,且具有关系特征时,当前状态下的工具可能会变得笨重。一些人可能会试图卷入不必要的非功能性集群问题。当然,维持某种程度的事务交互和简单的磁盘 IO 模型仍然是非常可取的。地方磁盘足够大,地方芯片足够快,没有必要采取冒险的措施。关系数据库非常适合于内存外存储和快速关系查询,但如何在不放弃功能编程的优势以及 TMD 的列主处理模型的情况下利用这一点呢?JDBC 与 Postgres 一起为这个问题提供了一个良好的初步答案,但通过一个低效的、非批处理的 API 执行完整的行到列转换,以便通过 JDBC 将数据传输到 TMD 是令人恼火的。 新的挑战者出现了 DuckDB 通过 2021 年 5 月的 GitHub 问题出现,tmducken 在该年的 12 月与他们的 C 绑定进行了最小整合。在那个版本中,所有查询结果一次性返回,因此需要适合内存。此外,在 DuckDB 的早期,没有专门的高性能附加或插入系统,因此 IO 限制了潜在性能,而 Postgres 仍然作为 TMD 的辅助手段。自那时起,发生了很多变化。在过去两年中,DuckDB 有了很大改善。重要的是,C 接口现在为插入和查询提供了一个批处理系统,这使得处理非常大的连接成为可能 - 进一步的内容稍后会讲。这些改进的功能现在可以通过 TMD 在 Clojure 中利用,以访问 DuckDB 的高端向量化 SQL 执行引擎,这非常出色。 实际使用 基于我们以前的帖子,有一个 50GB 的 .csv 文件,包含 3 年的交易数据,总计 4 亿行: $ ll -h data.csv -rw-rw-r-- 1 harold harold 50G Aug 8 09:49 data.csv 将其加载到 DuckDB 中出乎意料地简单 - 不过,您确实需要等 2分钟: $ time duckdb data.ddb 'CREATE TABLE data AS FROM "data.csv";' 100% ▕████████████████████████████████████████████████████████████▏ real 1m50.091s user 21m42.693s sys 0m57.887s $ ll -h data.ddb -rw-rw-r-- 1 harold harold 18G Sep 6 10:57 data.ddb 因此,文件减少到 18GB,其中包含由 DuckDB 自动创建的所有索引(!)数据在此: $ duckdb data.ddb v0.8.1 6536a77232 输入 ".help" 获取使用提示。 D SELECT COUNT(*) AS n FROM data; ┌───────────┐ │ n │ │ int64 │ ├───────────┤ │ 400000000 │ └───────────┘ D DESCRIBE TABLE data; ┌────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐ │ column_name │ column_type │ null │ key │ default │ extra │ │ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │ ├────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤ │ customer-id │ VARCHAR │ YES │ │ │ │ │ day │ BIGINT │ YES │ │ │ │ │ inst │ TIMESTAMP │ YES │ │ │ │ │ month │ BIGINT │ YES │ │ │ │ │ brand │ VARCHAR │ YES │ │ │ │ │ style │ VARCHAR │ YES │ │ │ │ │ sku │ VARCHAR │ YES │ │ │ │ │ year │ BIGINT │ YES │ │ │ │ │ transaction-id │ VARCHAR │ YES │ │ │ │ │ quantity │ BIGINT │ YES │ │ │ │ │ price │ DOUBLE │ YES │ │ │ │ ├────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┤ │ 11 行 6 列 │ └──────────────────────────────────────────────────────────────────────┘ 通过 TMD 从 Clojure 访问这一点也很简单: user> (require '[tmducken.duckdb :as duckdb]) nil user> (require '[tech.v3.dataset :as ds]) nil user> (duckdb/initialize!) Sep 06, 2023 11:00:12 AM clojure.tools.logging$eval7454$fn__7457 invoke INFO: 正在尝试从 "./binaries/libduckdb.so" 加载 duckdb true user> (def db (duckdb/open-db "data.ddb")) #'user/db user> (def conn (duckdb/connect db)) #'user/conn user> (time (duckdb/sql->dataset conn "SELECT COUNT(*) AS n FROM data")) "经过时间: 10.305756 毫秒" :_unnamed [1 1]: | n | |----------:| | 400000000 | 现在,想象一下管理层通知我们还有另一组数据,以捕捉每个 SKU 关于产品颜色的信息。这也需要在数据库中: user> (-> (let [colors ["red" "green" "blue" "yellow" "purple" "black" "white"]] (->> (for [brand (range 100) style (range 10) item (range 10)] (let [sku (format "sku-%s-%s-%s" brand style item) n (rand-int 8)] (for [color (take n (shuffle colors))] {"sku" sku "color" color}))) (apply concat))) (ds/->dataset {:dataset-name "colors"})) colors [35179 2]: | sku | color | |-
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡