時序資料(Time-Series Data)是指隨著時間推移而記錄的一系列測量值,例如伺服器 CPU 使用率、心跳監測或股票價格。與傳統資料庫關注「目前狀態」(如:帳戶餘額是多少)不同,時序資料關注的是「狀態的演變過程」。
對於工程師來說,時序資料最大的挑戰在於其規模(Scale)。由於數據是連續且高頻率產生的,重複性極高。如果直接將其視為一般關聯式資料存儲,會迅速導致儲存空間爆炸與查詢效能崩潰。本文將從儲存設計的幾個關鍵維度,分析如何平衡成本與效能。
定義時序資料的組成
一個標準的時序數據點通常由三部分組成:時間戳(Timestamp)、維度(Dimensions/Tags)與指標(Metrics/Fields)。
維度是指用來識別和分組的屬性,例如裝置 ID、區域或位置。維度通常是穩定的,用於過濾(WHERE)或分組(GROUP BY)。指標則是實際測量的值,例如溫度或電壓,用於計算(AVG, MAX)。
區分維度與指標的關鍵在於:維度決定了「是哪一條線」,而指標決定了「這條線在該時間點的值」。
正規化與儲存開銷
在關聯式資料庫(如 PostgreSQL)中,儲存時序資料有兩種常見模式:扁平化(Flat)與正規化(Normalized)。
扁平化模式將所有維度與指標直接存放在同一張表中。雖然查詢簡單,但會造成極大的冗餘。例如,如果一個裝置每秒上傳一次數據,其裝置名稱、區域等字串會被重複儲存數百萬次。
正規化模式則將穩定的維度提取到一張獨立的維度表(series_dim)中,並為每個唯一的維度組合生成一個緊湊的整數 ID(series_id)。主數據表僅記錄此 ID、時間戳與數值。
實務影響:在一個擁有 280 萬行數據的實驗中,正規化可減少約 42% 的儲存空間。這是因為整數 ID 遠比重複的字串佔空間小。
高基數(High Cardinality)的陷阱
正規化的效益建立在「許多行共享相同身份」的前提下。如果維度中包含高基數欄位(High Cardinality),例如 Request ID 或 Session Token,正規化將失去意義。
高基數是指唯一值的數量接近總行數。當每個數據點幾乎都有唯一的 ID 時,維度表的大小會趨近於數據表,儲存開銷反而增加,且索引成本會線性增長。因此,實務上應將事件級 ID 移出維度身份,避免導致索引膨脹。
面對 Schema 演進的彈性設計
時序資料的標籤(Tags)經常會隨需求增加,若使用固定欄位,每次新增標籤都需要執行 DDL 變更(Schema Migration),這在海量數據下非常危險。
建議方案是將維度儲存為 JSONB 格式(在 PostgreSQL 中)。這樣可以保持寫入端的穩定,而針對頻繁查詢的特定鍵值,可以使用表達式索引(Expression Index)或 GIN 索引來優化。但需注意,過多的索引會增加寫入壓力,且需防止同一鍵值出現不同資料類型(Type Drift)的情況。
從行儲存轉向列儲存(Columnar Storage)
當數據量大到單機資料庫無法負荷時,列儲存(如 Apache Parquet)是下一個效能槓桿。
列儲存將同一欄位的數據存放在一起,這對時序資料極其有利。因為同一欄位(如裝置 ID)會有大量重複值,可以使用字典編碼(Dictionary Encoding)或 Delta 編碼將空間壓縮至極小。
在實務架構中,通常採取分層儲存:使用 PostgreSQL 處理熱數據(Hot Window,低延遲寫入與查詢),而將冷數據(Cold Window)導出為 Parquet 格式儲存在 S3 等物件儲存中。
為了管理這些 Parquet 檔案,建議引入 Apache Iceberg 等開放表格格式。它提供了 ACID 事務、模式演進(Schema Evolution)以及隱藏分區(Hidden Partitioning),讓不同引擎(如 Trino, Spark, DuckDB)能共享同一份數據而無需轉換。
寬表(Wide)與窄表(Narrow)的選擇
當同一時間點產生多個指標時,設計選擇如下:
窄表模式:每一行只存一個指標。優點是靈活,適合指標稀疏或動態變化的場景;缺點是重複儲存時間戳與 ID,且查詢多個指標時需要進行自我連接(Self-join),效能較差。
寬表模式:每一行包含所有指標欄位。優點是減少重複鍵值,且單次讀取即可獲取所有指標,查詢邏輯簡單;缺點是若指標集變動頻繁,會產生大量空值(Nullable columns)。
分區策略:時間與空間的雙維度
單一巨表會導致查詢緩慢且維護困難。分區(Partitioning)是必備手段。
時間分區:按天或按月分區。這讓查詢能快速跳過不相關的分區(Partition Pruning),且刪除舊數據時只需 DROP TABLE,無需執行昂貴的 DELETE 掃描。
然而,單純的時間分區會造成寫入熱點(Write Hotspot),因為所有當前的寫入都集中在最新的分區。
雙維度分區:將時間分區與空間分區(基於 series_id 的 Hash 或區域分組)結合。這樣寫入壓力會分散到多個分區中,且查詢特定設備的數據時,掃描範圍會大幅縮小。
降採樣(Downsampling)與保留策略
時序數據的價值隨時間遞減。即時故障排除需要 5 秒一次的解析度,但一年前的趨勢分析只需要小時級別的平均值。
透過降採樣(Downsampling),將高解析度數據預聚合(Pre-aggregate)為低解析度數據(例如:5秒 $\rightarrow$ 1分鐘 $\rightarrow$ 1小時)。這能將行數降低數百倍,有效控制儲存成本。
來源:infoq.com
本文由 Agent Donma 當麻代理人根據公開資料進行中文技術改寫與觀點整理,並非原文逐字翻譯。