AWS Redshift 設計架構筆記

fcamel
fcamel的程式開發心得
9 min readJul 1, 2023

這篇是 “AWS re:Invent 2019: Deep dive and best practices for Amazon Redshift (ANT418)” 前半的讀後心得。

Redshift 的用途是 Data Warehouse,主要用作資料分析,強調平行計算大量資料 (PB 等級),語法相容 PostgreSQL。

Architecture: Computation and Storage

Client 連到 Leader node 由 Leader node 發包請求,算是滿直覺的架構。只要資料之間沒有關聯性,就能讓 Compute nodes 同時處理。

Redshift 後來提供 Redshift Managed Storage (RMS),分離 Compute node 和資料,透過這層 indirection,Client 可以享受動態增長資料上限的功能。以往則是得選擇使用特化計算或儲存空間的機器類型。就我的理解,這樣 Client 比較不會因為空間不足要加機器,而是因算力不足才要加機器。

後來 2021 出現的 Redshift Serverless,更進一步提升加機器的彈性,變成設定好一組機器對應固定的單位時間計算量,下 SQL 後才開起來使用,最低以一分鐘為單位收費。或許 2019 RMS 有在為 Redshift Serverless 佈局吧?

Columnar Architecture

以「欄的方式儲存資料」可說是 data warehouse 的基本作法吧,多數 SQL 只需查詢部份欄位,在這樣的儲存方式下,大幅減少需要從硬碟讀取的資料量。

更進一步,同一欄的資料格式一致,可以用更特化的壓縮方法。比方說上圖中的 loc (location) 的類型不多,可以用 BYTEDICT 壓縮,這個作法會另建一個 index 將數字對到名稱,在 256 個類型以內的情況下,只需 1 byte 即可表示。

注意 columnar architecture 無法建立傳達 RMDBS 的 index,只能設 distribution key 和 sort key。

Block, Zone maps, Sort key

為了減少從硬碟取資料,Redshift 將資料以 Block 為單位儲存在硬碟裡。

Block:

  • Column data is persisted to 1 MB immutable blocks
  • Blocks are individually encoded with 1 of 13 encodings
  • A full block can contain millions of values

並且在 memory 裡存著每個 block 的 min/max 值,稱為 zone maps。於是可以透過 zone maps 濾出必須讀取的 blocks。

最後則是將 blocks 依 sort key 排序,如下圖所示:

於是在過濾條件有用到 sort key 時,可以直接跳過不相干的 blocks,如下圖所示:

通常會依時間分析的資料,滿適合設定時間欄位為 sort key。這樣在計算週平均、月平均、年平均等數據時,只會讀到該時間範圍內的 blocks。

Data Distribution

前面提到如何最小化要讀取的資料,再來要考慮如何最大化同時讀取必須讀取的資料。

共有四種分配資料到 Node 的方法。在討論這些方法前,先談一下 Slice。

Redshift 在每個 Node 多加了一層 indirection: Slice,每筆資料分配到 Slice 而非 Node。依先前讀 DynamoDBRedis Cluster 的心得,推測多了 Slice 會比較好搬資料。當某個 Node 硬碟快滿了,Redshift Managed Storage (RMS) 應該會自動補新的 Storage Node,然後將部份 Slice 搬到新的 Node。

我猜 Slice 會有固定上限大小,這樣才能確保搬移一個 Slice 花費時間的上限。印象中 DynamoDB 是限制 10GB,在 10Gbps 的的網路下,理論上搬動一個單位資料容器只需 10 秒上下。

接著回來談分配資料的方法,預設是 EVEN,採用 round robin 方式填資料,是最平均的作法。若想最大化平行取資料的速度,理論上都用 EVEN 就好了。那為什麼還要有其它方式?

Collocated Join

為了節省儲存空間,通常會有 Fact Table 和圍繞在 Fact Table 旁的 Dimension Tables (統稱為 Star Schema )。透過 join 兩者,取得額外的資料。

https://en.wikipedia.org/wiki/Star_schema#/media/File:Star-schema.png

PB 級的資料自然無法全部存在一個 Node 裡,此時如何將有效率地取得 join 所需的資料,可以大幅降低要讀取的資料。

若 Fact Table 和 Dimension Table 採用同一個欄位作為 distribution key,可以保證兩個 table 要合併的 data row 位在同一個 Slice。因此,只要 distribution key 不會造成嚴重的 data skew ( 過多資料集中在同個 Slice),採用 KEY 的分配方式比 EVEN 綜合來說更有效率。

除 KEY、EVEN 外,ALL 表示全部資料放到全部 Node 的第一個 Slice。AUTO 表示先用 ALL,等資料量變大後再改用 EVEN。

中途小結

這頁總結架構的優點和基於此架構的正確使用方式。比較有趣的一點是 “Avoid distribution keys on temporal columns”。前面提過 columnar architecture 只能設 distribution key 和 sort key,其中 distribution key 用作 collocated join,sort key 用來只取必要的 blocks。因此,若將時間欄位設成 distribution key,結果變成 data skew 很嚴重,比方說只取特定一天的資料,變成全部計算都在單一 Node 上,無法享受分散式系統的優勢。

想像一個例子是記錄使用者購買記錄,用帳號當 distribution key;日期當作 sort key。若要取最近 30 天前 100 名購買總金額最多的人,Redshift 會讓全部 Node 同時在各自 Node 用日期選出範圍內的 blocks,然後找出同一 Node 的金額欄位,加總算出再匯集排序得到結果。

DynamoDB 一樣有 distribution (partition) key 和 sort key,不過使用情境和用法就完全不同。DynamoDB 重視單筆讀寫立即返回結果,Redshift 則善用平行處理。

Data storage, Ingestion, 和 ELT

ELT 和 ETL 的差別是資料先進 Data Warehouse 再處理 (ELT) 或是先處理完再進 Data Warehouse 或是 Database (ETL)。關鍵在於 Dare Warehouse 具備強大的平行處理能力,所以資料量很大的時候,用 ELT 會比較有效率。

這部份講的概念滿直覺的,這裡摘要一下:

  • 每更動 5G 資料或 8h 後,會建一份新的 snapshot 到 S3
  • Temporary table 沒有備份,所以適合當作 staging area 操作
  • 預設每個 SQL 是一個 transaction,如果要作一連串的修改,明確地用 BEGIN / COMMIT 包起來會比較有效率
  • 支援直接從 S3 匯入資料到 Redshift。可以用 S3 當作一連串 ELT 的中繼點
  • 採用 immutable block 的設計,因此 UPDATE = DELETE + INSERT。在高效率的系統,很常見這種 immutable 的設計。例如 log structured file systems
  • Redshift 會自己找適合的時機作 vacuum 釋放已刪除的 blocks,通常沒必要自己手動觸發 vacuum

後面就不描述細節,大致上投影片寫的滿清楚的。

特別提一下上面這頁投影片,這裡提供完整的 UPSERT 例子,綜合使用了前述的概念:

  • 用 TEMP table 作為 staging table 避免不必要的背景操作
  • 從 S3 載入更新
  • 用刪掉舊資料再寫入新資料的作法達到 UPSERT

結語

這樣反覆消化 Redshift 的設計後,有比較清楚 Data Warehouse 和 Database 的區別。

在高效能的需求下,會看到重覆的設計模式不斷的出現,例如:

  • (Redshift, Redis Cluster) 使用 immutable block 作為最小單位,方便搬資料
  • (Redshift, DynamoDB) 使用 indirection 方便動態擴充計算和儲存的節點
  • (Redshift, DynamoDB, Redis) 使用 distribution key 降低相依性,從而達到完全平行處理
  • (Redshift, DynamoDB) 使用 sort key 濾出必要的資料

--

--