MOOSSin
選單

全部文章 · 開源工具

DuckDB 與 Parquet:不用裝服務的本機資料倉儲

DuckDB 裝一個 Python 套件就能用,分析型資料庫,讀 Parquet 是原生的。把熱資料留在交易型資料庫、冷資料按月匯成 Parquet、分析全部在 DuckDB 上做,是一套很省事的分工。這篇講做法、幾個小坑、ClickHouse 該什麼時候進場,還有為什麼我覺得它像一台很會算的計算機。

我大學時期最羨慕的一種人,是那種隨手拿一台計算機就能把一整張表的數字算出結論的人。他們不需要 Excel,不需要開電腦,按幾下就說「平均是這個、最高是那個、第三季比第二季多了兩成」。DuckDB 給我的感覺就是那台計算機,只是它算的不是一張表,是幾億列。

它是什麼

DuckDB 常被形容成「分析用的 SQLite」:MIT 授權,跑在你的程序裡,沒有伺服器、沒有埠、沒有帳號,2024 年 6 月出了 1.0。它是欄式儲存、向量化執行,對彙總和掃描很快,對逐筆更新不在行。你不會拿它做網站的資料庫,你會拿它回答「過去三個月每天平均多少」這種問題。

Parquet 則是 Apache 的欄式檔案格式,schema 寫在檔案裡,每一欄各自壓縮。一個 Parquet 檔就是一張表的凍結版,可以丟在硬碟裡、雲端裡、隨身碟裡,任何懂 Parquet 的工具都能直接讀。兩者搭在一起,就是放在資料夾裡的資料倉儲。

為什麼欄式儲存對分析快

想像一本電話簿,一般的資料庫是一頁一個人,名字、電話、地址排在一起。你要算「平均電話號碼長度」(好,這是個很爛的例子,但你懂),得翻每一頁、跳過名字和地址、只看電話。欄式儲存是把所有人的電話抄在同一張紙上,所有名字抄在另一張,所有地址抄在第三張。要算電話的東西就只拿那一張紙,其他兩張碰都不碰。分析查詢通常只看幾欄,所以快很多;逐筆更新要同時改三張紙,所以慢。

分工:熱的留在資料庫,冷的變成檔案

我的做法是寫入層用 PostgreSQL,主表按月分區;每個月把上個月的分區匯出成一個 Parquet 檔,PostgreSQL 只留近十二個月。分析查詢不碰 PostgreSQL,全部在 DuckDB 上對那些 Parquet 檔做。寫入和分析互不干擾,分析工具也不需要資料庫的連線權限,能讀檔就行。

用生活比喻就是:這學期的筆記放在書包裡隨時翻,上學期的整理成一本裝訂好的冊子放書架上。書包不會越來越重,書架上的冊子也不會被你每天翻到爛。

匯出的細節

用 psycopg 對 PostgreSQL 下 COPY,串流寫成暫存 CSV,不把整張表讀進記憶體。時間戳一律輸出成帶 +00:00 的 ISO 文字,DuckDB 讀回來會是有時區的 TIMESTAMPTZ,不會變成看誰的電腦而定的時間。時區這件事我要特別強調:沒有時區的時間戳是資料分析裡最常見的鬼故事,同一筆資料在兩台電腦上差八小時,然後你會花一個下午找一個不存在的 bug。

DuckDB 端 read_csv 時每一欄的型別明確指定,不讓它猜。它猜得很準,但「很準」在幾百萬列裡總會猜錯一欄,例如把某個看起來像數字的字串欄猜成整數,然後在第八十萬列碰到一個字母炸掉。COPY ... TO 'x.parquet' (FORMAT parquet, COMPRESSION zstd) 寫出檔案。先寫到暫存檔名再改名,讀取端永遠不會看到寫到一半的檔案。

查詢的細節

read_parquet(['a.parquet', 'b.parquet'], union_by_name=true) 一次讀一整批月檔,不同月份多一欄少一欄也能合在一起,這在資料來源會改欄位的世界裡超級實用。在它上面建 view,再往上放一個 SQL 檔目錄,每個檔案一個 view,分 staging 和 marts 兩層。這是從 dbt 借來的目錄結構,不用裝 dbt。某張表還沒有任何匯出時,對應的 view 建不起來,程式記下來繼續。

兩個小坑。SET TimeZone 設成 UTC 這件事要在每個連線上明寫,不然時區跟著作業系統走,又是那個鬼故事。Python 上要處理帶時區的時間戳,DuckDB 需要 pytz,文件上有寫,我裝的時候沒看到,然後看著一個很莫名的錯誤訊息想了十分鐘。

小劇場:這跟用 Excel 差在哪

「我這些資料用 Excel 也能算啊,為什麼要學這個?」

「你的資料幾列?」

「大概三萬。」

「那 Excel 可以。等到三百萬列的時候再來找我。」

「三百萬列 Excel 會怎樣?」

「會讓你看著轉圈圈的游標思考人生。DuckDB 對三百萬列的彙總大概一秒。」

「那我現在就學。」

「你現在三萬列,先把 Excel 用好。工具要在你需要它的時候學,不然學了也忘。」

為什麼是 DuckDB

「不用安裝服務」對一個只有一個使用者、只在一台機器上跑的系統,比任何效能數字都重要。它讀 Parquet 不用先匯入,對幾十個月檔做彙總的速度到目前我沒有量到需要擔心的數字。它就是那台計算機,你拿起來就能算,算完放下,不用開機、不用登入、不用管它。

ClickHouse 什麼時候進場

ClickHouse 是伺服器型的分析資料庫,強在真正的大量資料,那種一天幾十億列的等級。它沒有原生的 Windows 版,要跑只能透過 Docker 或 WSL2。我給自己寫了兩個觸發條件:主表累計超過五千萬筆,或常用查詢在 DuckDB 上超過十秒,滿足任一才評估。

把條件寫下來的好處是將來不用再吵一次「該不該換」,看數字就好。現在的資料量離那條線還有好幾個數量級,計算機還很夠用。真的到了需要一台大機器的那天,我會很開心,因為那代表資料真的累積起來了。

那隻鴨子是誰

DuckDB 來自荷蘭的國家數學與資訊研究所 CWI,兩位研究員 Hannes Mühleisen 和 Mark Raasveldt 在 2019 年釋出第一版。名字來自 Hannes 養的一隻鴨子,叫 Wilbur。一個資料庫用寵物鴨命名,跟 PostgreSQL 的大象一樣,這個圈子的人對動物有一種執著。

它的設計哲學很明確:SQLite 讓大家看到「嵌入式資料庫」這個形式可行,但 SQLite 是為交易型工作設計的;分析型工作需要不同的儲存和執行方式,卻一直沒有一個好用的嵌入式選項,大家只能開一台伺服器或者用 pandas 硬撐。DuckDB 補了這個洞。它跟 pandas 和 Arrow 的整合很好,一個 DataFrame 可以直接當表查,這讓很多資料分析的人第一次接觸它就回不去了。

Parquet 則是 2013 年由 Twitter 和 Cloudera 合作推出的,現在是 Apache 專案。它是 Hadoop 時代留下來最有生命力的東西之一,Hadoop 本身沒落了,Parquet 反而成了所有分析工具的共通語言。一個格式能跨越它誕生的那個時代,通常表示它真的解決了一個永恆的問題:怎麼把一張表存成檔案而且讀起來快。

常見的三個誤會

第一個誤會是「DuckDB 可以取代 PostgreSQL」。不行,它們解決不同的問題。DuckDB 的並行寫入支援很有限,它假設你是一個人在分析資料,不是一百個使用者在同時下單。寫入層和分析層分開,正是這篇的重點。

第二個誤會是「Parquet 只是壓縮過的 CSV」。CSV 是文字、逐列、沒有型別;Parquet 是二進位、逐欄、帶 schema、每欄各自壓縮還有統計資訊。查詢時 Parquet 可以跳過整塊不相關的資料,CSV 只能從頭讀到尾。兩者的差別不是大小,是能不能「只讀需要的部分」。

第三個誤會是「嵌入式就是玩具」。DuckDB 處理幾億列是日常,很多公司拿它跑正式的報表管線,只是跑在一個程序裡而不是一個叢集裡。嵌入式描述的是部署形式,不是能力。

一個週末可以試的小練習

pip install duckdb,然後找一個你手上的 CSV,任何一個,幾千列就好。在 Python 裡 duckdb.sql("SELECT * FROM 'data.csv' LIMIT 5"),對,直接對檔名查,不用先匯入。看它猜的欄位型別對不對。

然後 duckdb.sql("COPY (SELECT * FROM 'data.csv') TO 'data.parquet' (FORMAT parquet, COMPRESSION zstd)"),看一下兩個檔案的大小差多少。再對 Parquet 檔做一個 GROUP BY 的彙總,感受一下速度。最後把 CSV 複製三份改成不同月份的名字,用 read_parquet(['*.parquet']) 一次查三個。這一小時會讓你理解為什麼很多人說「我再也不想把 CSV 讀進 pandas 了」。

後記

那台計算機的比喻,我後來覺得還可以再推一步。真正會算的人不是按得快,是知道哪些數字該算、哪些不用。DuckDB 讓「算」這件事變得很便宜,便宜到你可以隨便算、算錯再算,而在便宜之後,剩下的就是你知不知道自己想問什麼。工具解決了一半,另一半永遠是人。

官方網站:duckdb.org、parquet.apache.org

閱讀偏好

返回全部文章作者與內容更正

留言(0)

還沒有留言。讀完有想法,歡迎留下第一則。

留言經站長審核後才會顯示。暱稱和留言會公開,請不要寫下電話、地址等個人資料;資料怎麼處理,請見隱私權政策。