從 OLTP 到 OLAP#
到此為止本章節談的都是 OLTP(線上交易):訂單、商品、購物車、賬戶。但電商還有大量分析需求:
- 用戶行為日誌(每個點擊、每個瀏覽)
- 推薦系統訓練資料
- BI 報表(銷量、轉化、留存)
- 風控規則計算
- 個性化定價
這些是 OLAP(線上分析),特性:
| 維度 | OLTP | OLAP |
|---|---|---|
| 寫入 | 單筆即時 | 批次或流 |
| 讀取 | 單筆 / 小範圍 | 跨百萬行聚合 |
| 並發 | 高 | 低 |
| 延遲 | < 100ms | 秒到分鐘可接受 |
| 資料模型 | 行式 | 列式 |
| 規模 | TB | PB |
| 一致性 | 強 | 弱(分析可容忍) |
兩個世界用不同儲存、不同思考。
前端埋點之類的「海量原始資料」#
埋點:用戶在 app 上做的每個動作都記錄一條。
每用戶每天 ~100 條
1000 萬日活 → 10 億條/天
平均 1 KB/條 → 1 TB/天這量根本不該存 MySQL ── 用對的工具:
client → API → Kafka → ?
├─ 即時:Flink 流式處理
├─ 短期:ClickHouse(分鐘級可查)
└─ 長期:HDFS / S3(PB 級存檔)Kafka:管道的脊椎#
Kafka 在資料架構中扮演緩衝 + 解耦 + 重播的角色:
+-----------+ +-------+ +-----------+
| Producers |───→ | Kafka |───→ | Consumers |
+-----------+ +-------+ +-----------+
topics +
partitions
(分散在多 broker)特性:
- 寫入吞吐極高(100 萬 QPS / 叢集)
- 訊息保留可設定(小時、天、TB)
- 多 consumer group 各自獨立 offset
- 順序:partition 內保證順序
Topic、Partition、Consumer Group#
topic: user_events
├─ partition 0 ──→ broker 1
├─ partition 1 ──→ broker 2
├─ partition 2 ──→ broker 3
└─ partition 3 ──→ broker 4partition 是並行單位 ── 一個 consumer group 最多能有 partition 數的 consumer 並行。
producer 寫入 partition 策略:
- 沒指定 key ➡️ round-robin
- 指定 key ➡️
hash(key) % num_partitions,同 key 同 partition(保順序)
Kafka 在電商的典型用法#
| 場景 | topic | producer | consumer |
|---|---|---|---|
| 用戶行為 | user_events | API gateway | 流處理、ML 訓練、ClickHouse |
| 訂單事件 | order_lifecycle | order service | 庫存、支付、配送、分析 |
| 商品變更 | product_updates | product CDC | search index、cache 失效 |
| 計費 / 報表 | billing_events | various | 結算系統 |
| 系統日誌 | app_logs | log shipper | ELK、長期歸檔 |
HDFS:更大的儲存#
Kafka 適合「短期持有 + 流式消費」,但保留 PB 級 / 多年 ➡️ 太貴。轉到 HDFS 或物件儲存(S3):
Kafka ──Flink/Spark──→ Parquet files on HDFS / S3HDFS 特性:
- 為大檔(128 MB block)批次處理優化
- 高吞吐順序讀
- 與 MapReduce / Spark 緊密整合
近年趨勢:HDFS 被物件儲存(S3) 取代。原因:
- S3 也能跑 Spark
- 不用自建 HDFS 叢集
- Decoupling 計算與儲存
「Data Lake on S3」(Apache Hudi、Iceberg、Delta Lake)是現在的主流。
Parquet:列式檔案格式#
OLAP 用列式儲存的根本原因:
需求:SELECT SUM(amount) FROM orders WHERE date BETWEEN ... AND ...
行式(傳統 MySQL):
讀整行(含所有欄位)─ amount 只占 1/30,瀏覽 30x 多資料
列式(Parquet):
只讀 amount 欄位 ─ I/O 直接是需要量
+ 同欄位資料類型一致 → 壓縮比極高(10x 常見)Parquet 還有:
- columnar(按欄存)
- 內建壓縮(Snappy / Gzip / Zstd)
- 內建統計(每個 row group 的 min/max ➡️ 跳過不相關 group)
- schema evolution
列式儲存與 OLAP 壓縮#
前面提到列式(columnar)「只讀需要的欄位、壓縮比高」,這裡把引擎內部為什麼能做到講清楚 ── 列式不只是換個存法,它讓壓縮、向量化、排序互相加成,這正是 OLAP 引擎快的根本。
行式 vs 列式的 I/O 差異#
同一張表,物理擺法完全不同:
行式(OLTP,按列 row 連續存) 列式(OLAP,按欄 column 連續存)
row1: [id, name, city, age, amount] id: [1, 2, 3, 4, ...]
row2: [id, name, city, age, amount] name: [Tom, Amy, ...]
row3: [id, name, city, age, amount] amount: [100, 50, 80, ...]對「SELECT SUM(amount)」這種只碰一欄的分析查詢,差距是數量級的:
| 維度 | 行式 | 列式 |
|---|---|---|
| 讀取 I/O | 讀整行,amount 只占 1/N | 只讀 amount 欄,I/O = 需要量 |
| 壓縮比 | 一行內型別混雜,壓縮差 | 同欄同型別,壓縮比常 10× 以上 |
| CPU 處理 | 逐行解析 | 整欄連續,可向量化 / SIMD |
| 適合操作 | 點查、單行讀寫(OLTP) | 大範圍聚合、掃描(OLAP) |
「只讀需要的欄位」是列式最直觀的勝點:一張 30 欄的寬表,聚合一欄時行式要瀏覽 30× 多的資料,列式直接命中。
壓縮編碼:bitmap + 遊程#
列式真正的威力在同欄資料型別一致、值域集中,於是能上專用編碼:
- 點陣圖編碼(bitmap encoding):低基數欄位(如
city、status只有幾十種值)每個 distinct value 配一個 bitmap,第 i 位為 1 表示第 i 列是該值。WHERE city='TPE' AND status='paid'直接做 bitmap 的 AND,硬體位元運算極快。
city 欄(值:TPE, KHH, TPE, TPE, KHH)
bitmap[TPE] = 1 0 1 1 0
bitmap[KHH] = 0 1 0 0 1
查 city='TPE' → 直接回傳 bitmap[TPE],免逐列比較- 遊程編碼(run-length encoding, RLE):欄位排序後大量連續重複,把「值 × 重複次數」存一筆即可。
TPE,TPE,TPE,KHH,KHH➡️(TPE,3)(KHH,2)。bitmap 本身也常再套 RLE 壓縮稀疏位元。
排序與壓縮的耦合#
關鍵洞察:排序鍵的選擇直接決定壓縮比。資料按某欄排序後,該欄(及相關欄)出現長連續段,RLE/bitmap 壓得極好。所以 OLAP 引擎建表時要慎選排序鍵 ── 這跟 OLTP 的主鍵思路完全不同,OLTP 選唯一性、OLAP 選「能讓資料聚集、壓縮、跳過的維度」(如 (date, brand))。配合每個 row group 的 min/max 統計,查詢能直接跳過整段不相關資料。
向量化 / SIMD#
列式讓同一欄的值在記憶體中連續排列,CPU 可以用 SIMD(單指令多資料) 一次處理一批(如一條指令加 8 個整數),而非逐行函式呼叫。ClickHouse 的「極致 CPU 效率」就來自向量化執行 + JIT,對連續欄做批次運算。
列式寫入借用 LSM#
列式檔案不可變、壓縮後不能原地改,那怎麼支援持續寫入?答案是借 LSM 的 append + 後台 merge(見 15 RocksDB 與 LSM-Tree):
flowchart LR
W[批次寫入] --> P[寫成小的列式 part<br/>各自排序、壓縮]
P --> M[後台 merge<br/>合併小 part 成大 part]
M --> L[更大、更壓縮、<br/>排序段更長的 part]
L --> Q[查詢:跨 part 合併 +<br/>min/max 跳過]ClickHouse 的 MergeTree 就是這個模式:寫入落成小 part(所以單行寫慘、要批次寫數萬行),後台不斷 merge 成更大、排序段更長、壓縮更好的 part。這也解釋了 上文 說的「append-only + 後台 merge(LSM 風格)」── 列式 OLAP 與 LSM 在寫入路徑上同源,只是 LSM 存 KV、MergeTree 存列式 block。
流處理:Flink / Spark Streaming#
從 Kafka 拉資料、即時處理、輸出到下游:
Kafka ─→ Flink ─→ ClickHouse / Kafka / DB
─→ 計算窗口(5 分鐘 PV、UV)
─→ 異常偵測
─→ 即時 joinFlink 是業界標準(事件時間、watermark、exactly-once、state management)。Spark Streaming 微批處理較簡單但延遲較高。
# Flink 概念
stream = env.from_source(kafka_source)
stream.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.aggregate(PV_counter)
.sink_to(clickhouse_sink)OLAP 引擎選擇#
當資料已在 lake / DW 裡,怎麼快速查?
| 引擎 | 特性 |
|---|---|
| ClickHouse | 列式、極快聚合、寫入也快、分散式 SQL |
| Druid | 即時 OLAP、優秀 time-series、聚合預計算 |
| Presto / Trino | 跨多 source 查詢(S3、Hive、MySQL) |
| Apache Doris | 國內常用、MPP 架構 |
| Snowflake | 商業 SaaS,極簡運維 |
| BigQuery | Google Cloud,serverless |
| Redshift | AWS,商業 |
| StarRocks | Doris 衍生,性能更激進 |
電商選型:
- 即時報表 / 明細查詢:ClickHouse
- 即時聚合(時序):Druid
- ad-hoc / 跨源:Trino
- 雲端託管簡化:BigQuery / Snowflake
ClickHouse 是過去 5 年的明星 ── 對「結構化大規模 OLAP」幾乎是預設選項。
ClickHouse 的核心特色#
- 列式 + 高壓縮:1 億行 100 GB ➡️ 壓到 5 GB 常見
- MergeTree 引擎家族:append-only 寫入 + 後台 merge(LSM 風格)
- 分散式表:透明分片 + 副本
- 物化視圖(MV):寫入時自動聚合,查詢直接讀預聚合結果
- SIMD + JIT:極致 CPU 效率
寫入特性:
- 寫入要批次(一次數萬行),單行寫慘
- 不支援高併發點查(每查一次掃 column)
- 適合 append + 偶爾重寫,不適合頻繁 UPDATE/DELETE
對應的查詢場景#
-- ClickHouse 強項:聚合
SELECT date, brand, SUM(amount), COUNT(DISTINCT user_id)
FROM orders
WHERE date BETWEEN '2025-01-01' AND '2025-04-30'
GROUP BY date, brand
ORDER BY date
-- 1 億行掃描在幾秒內完成-- ClickHouse 弱項:點查
SELECT * FROM orders WHERE order_id = 'abc123'
-- 慢,因為要掃 column 找該行
-- 解:加 PRIMARY KEY (date, order_id) + 用 date 篩選寬表 vs 星型 schema#
OLTP 走第三範式(多表 + 外鍵)。OLAP 反範式:
事實表 + 維度表(star schema)
或
寬表(denormalized)寬表把 join 預先做掉、犧牲空間換查詢速度:
CREATE TABLE order_wide (
order_id, user_id, user_name, user_city, user_age, user_level,
sku_id, sku_name, brand_name, category_name,
price, amount, ...
)ClickHouse 偏好寬表:列式壓縮讓重複資料代價小,查詢時不需要 join。
資料管道全景#
把這章串起來:
[WEB / APP]
│ 用戶行為
▼
┌──────────┐
│ Kafka │ ← 短期緩衝(小時級保留)
└─┬──────┬─┘
│ │
┌─────────┘ └─────────┐
▼ ▼
[流處理 Flink] [批寫 Parquet]
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ClickHouse│ │ S3 │
│(即時OLAP) │ (長期lake)│
└──────────┘ └─┬────────┘
│
▼
┌──────────┐
│ Trino │
│ Spark │
│ (offline)│
└──────────┘OLTP 系統(MySQL)也透過 binlog ➡️ Kafka ➡️ ClickHouse 同步明細到 OLAP,達成「線上業務 + 即時分析」雙系統解耦。
小結#
- OLTP 與 OLAP 是兩個獨立世界,要用對的工具
- Kafka 是現代資料管道的脊椎:緩衝、解耦、重播
- HDFS 逐漸被物件儲存取代,PB 級資料 lake 在 S3
- Parquet 是行業 OLAP 檔案格式
- 流處理:Flink;批處理:Spark
- OLAP 引擎:ClickHouse(近年首選)、Druid、Trino
- 寬表 + 反範式是 OLAP schema 設計的常規
下章看「總結性的選型決策」:給定一個業務問題,怎麼挑儲存。