ByteScope

Parquet 檔案格式

Apache Parquet,用於巨量資料分析的欄式儲存格式。

MIME 類型
application/vnd.apache.parquet
副檔名
.parquet
分類
資料與資料庫

Magic number(檔案簽章)

0x0000000050415231PAR1'PAR1' header
-0x0000000450415231PAR1'PAR1' footer (last 4 bytes)

關於 Parquet 格式

Parquet 是一欄一欄存資料,不是一列一列存,所以一個只碰到一百欄裡三欄的查詢就只讀那三欄;而且每一欄壓得更好,因為相鄰的值型別相同、範圍通常也很窄。所有描述性的東西——結構描述、row group、每個 column chunk 的位移、編解碼器與最小/最大統計值——都用 Thrift 的 compact protocol 序列化,並且寫在資料後面,所以這個檔案是頭尾都有錨點的:位移 0 的 PAR1 讓掃描工具認出格式,檔尾的 PAR1 則錨定讀取端真正的起點——footer。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
04File magic 'PAR1'4 個位元組 50 41 52 31。它存在的目的是讓掃描檔案開頭的工具能叫出格式名稱;沒有任何讀取端會從這一端解析其他東西。footer 有加密的檔案,這裡和檔尾放的是 PARE,等於告訴讀取端 footer 前面還有一段檔案層級的加密中繼資料。
4variableFirst column chunk資料緊接在 magic 之後開始——一頁接一頁的編碼值,每一頁前面都有一個 Thrift compact 的 PageHeader。檔案前端沒有結構描述、沒有索引、也沒有列數,所以 Parquet 檔案的第一個 MB 幾乎不會告訴你裡面裝了什麼。
variablevariableFileMetaData (Thrift compact)結構描述、row group,以及每個 column chunk 的位移、編解碼器、編碼方式與統計值。它就緊貼在最後 8 個位元組前面,讀取端是用那個位置減掉 footer 長度來找它的起點——絕對不會從位移 0 往後掃。
-84Footer length無號 32 位元小端序,是它前面那段 FileMetaData 的位元組數。讀取端會 seek 到最後 8 個位元組、檢查 magic、讀這個數字,然後往回 seek 那麼多位元組。這裡的值錯了就是致命的,即使每一頁資料都完好無缺。
-44Trailing magic 'PAR1'和位移 0 相同的 4 個位元組——footer 有加密時是 PARE。因為這個格式的錨點在尾巴,這幾個位元組是每個讀取端第一個檢查的東西,所以下載到一半的 Parquet 檔案會被直接拒收,儘管它的標頭好得很。

怎麼跟長得像的格式分開

容易搞混的怎麼分
ORC同一批資料管線裡的另一種欄式格式,footer 放檔尾的設計也一樣。ORC 開頭是 3 個位元組 4F 52 43('ORC'),不是 4 位元組的 magic,而它的最後一個位元組根本不是 magic,而是 postscript 長度,一個小於 256 的單一數值。所以讀最後 4 個位元組:PAR1 是 Parquet,一個隨意的小位元組就是 ORC。
Avro OCFAvro 的物件容器檔案出自同一批寫入工作,但它是列式而且自我描述。標頭是 4 個位元組 4F 62 6A 01('Obj' 後接 1),接著一張放 avro.schema 與 avro.codec 的中繼資料表,然後是 16 位元組的同步標記。它沒有尾端 magic,所以 Avro 檔案可以一直往後追加還照樣讀得動,Parquet 不行。
Encrypted Parquet (PARE)版面配置相同,只是頭尾的 magic 換成 PARE 而不是 PAR1,這是刻意的:讓舊的讀取端卡在 magic,而不是卡在讀不懂的 Thrift。footer 加密時,尾端 magic 前面那 4 個位元組的長度涵蓋了加密中繼資料加上加密後的 footer,所以同一張表的這個數字會比明文的情況大。
SQLite 資料庫兩者都會被當成「那個分析用的檔案」傳來傳去,也都是單一檔案,但 SQLite 是列式儲存、magic 在前 16 個位元組,而且完全沒有尾端標記。如果位移 0 是 PAR1、最後 4 個位元組也是 PAR1,中間那段就不用查了。

常見的壞法

  • 尾巴被截斷。任何讓傳輸提早結束的事情——物件儲存的下載被砍掉、寫入端在 close 之前掛掉——都會留下開頭的 magic 和好幾 GB 完好的頁面,後面卻沒有寫成的 footer。每個引擎都會回報「這不是 Parquet 檔案」,因為它們檢查的是最後 4 個位元組,不是前 4 個。
  • footer 長度對不上了。把兩個 Parquet 檔案串在一起、後面多加一個位元組,或讓編輯器補上一個換行,錨點就位移了:尾端 magic 可能照樣讀到 PAR1,前面那 4 個位元組卻指到某一頁的中間,於是 Thrift 解析器卡在垃圾資料上,而不是告訴你這個檔案被編輯過。
  • 資料頁裡有一個位元組被翻掉。footer 解析得動、結構描述列得出來、那一欄一路讀到損壞的那一頁都正確,然後解壓縮器報錯、或是值解出來全是亂七八糟的東西。Parquet 的每頁 CRC 是選用的,所以很多寫入端根本沒留下任何線索指出是哪一頁出事。
  • 同一個目錄下結構描述悄悄漂移。這不是單一檔案損壞,卻是大家真的會碰到的失敗:每個檔案單獨看都有效,偏偏其中一個把某欄的型別寫成 int32、其他都寫 int64,查詢引擎就把整份資料集拒收。答案在每個檔案的 footer 裡,不在資料裡。

這個格式現在還在用嗎

Parquet 是分析世界的預設儲存格式。Spark、Trino、DuckDB、Polars、pandas、BigQuery 匯出與 Snowflake 的 UNLOAD,全都讀寫它,而建構在它之上的物件儲存表格格式——Iceberg、Delta Lake、Hudi——本質上就是一堆 Parquet 檔案的目錄加一份中繼資料日誌。只要資料量大,它就取代了 CSV,因為 CSV 每次查詢都得從頭到尾重讀重剖,而 Parquet 讀取端是先查 footer、用統計值把不可能命中的 row group 直接丟掉,再只解壓縮被問到的那幾欄。它仍然不適合的場合是小資料和給人看的交換:兩百列的對照表用 CSV 好管理得多;而一筆一筆的串流寫入該交給 Avro,因為 Parquet 檔案要等到 footer 寫下去的那一刻才成為有效檔案。

在這個站上打開 Parquet 檔

這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。

相關格式

容易和 Parquet 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
SQLite 資料庫53 51 4C 69 74 65.sqlite

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 Parquet?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。