Airflow:排程器、worker 與那個藏起來的狀態
· tech
📑 目錄
上一篇埋了個伏筆:每個系統都有一塊逃不掉的狀態,認出它就掌握了命門。 Airflow 是這句話最好的示範。它表面上全是看似無狀態、可重啟的組件——scheduler、webserver、worker,你 kill 掉哪個再拉起來都沒事。但整個系統的記憶(哪些 DAG 跑過、哪個 task 卡在哪、上次成功是什麼時候)其實藏在一個你可能沒特別注意的地方:metadata DB。這一篇,就從這顆藏起來的 DB 講起。
狀態(樞紐):真狀態全在 metadata DB
這顆 DB 的地位怎麼強調都不為過:它是整個 Airflow 的 single source of truth。而且有個很漂亮(也很危險)的設計——連 scheduler 的 HA 都建在它上面。Airflow 2.0 之後可以同時跑多個 active scheduler,它們怎麼不搶同一個 task?靠對 metadata DB 的資料列上鎖(row-level lock)。也就是說,Airflow 把「狀態」和「協調」兩件事都壓在同一顆 DB上——這讓所有組件都能無狀態化、隨便擴,代價是那顆 DB 成了更集中的瓶頸與命門。所以維運 Airflow 的頭號功課,就是把這顆 DB 當心臟養:用 managed 的 Postgres(RDS / Cloud SQL)、做好備份與 HA,而不是隨手在角落塞一個。
worker 怎麼長,取決於 executor
Airflow 的 worker 到底是「固定一群」還是「用完即拋」,由你選的 executor 決定。這是 Airflow on infra 最該先想清楚的一題:
HA、容量、監控、在 k8s 上
- HA:組件都好做,DB 才是關鍵。scheduler(2.0 後可多 active)、webserver、worker 全能多開——因為它們無狀態。真正要花心思做 HA 的是那顆 metadata DB:它掛了,整個 Airflow 停擺。所以 DB 一律用 managed、多可用區、有備份。這整套「組件無狀態、狀態全外部化」的架構,好處就是 HA 幾乎只剩一件事:顧好 DB。
- 容量:瓶頸常在 DB。scheduler 會頻繁查詢 DB 來排程,DAG 一多、parallelism 一高,DB 連線與查詢往往是第一個撞牆的地方——調
parallelism、max_active_runs、DB 連線池,常比加 worker 更關鍵。worker 端的容量就是 slot / pod 數量,那個好水平擴。 - 監控:盯排程健康與 backlog。scheduler 的 heartbeat(有沒有在跑)、卡在
queued的 task backlog、DAG run 成功率與 task 執行時間、DB 連線數、Celery 的 queue depth。task 一直卡在 queued,通常不是 worker 不夠就是 DB/broker 出事。 - 在 k8s 上:scheduler / webserver 用 Deployment,executor 多半選 KubernetesExecutor(task = pod),metadata DB 外接 managed 或用 StatefulSet + PV,官方 Helm chart 幫你把這些兜起來。真正跑重活時,Airflow 常只是去觸發一個 Spark 作業,自己不搬資料。
反思
最危險的狀態,是你以為沒有的那個
Airflow 給人的第一印象是「一堆可重啟的組件」,這印象會讓人鬆懈——直到某天那顆被塞在角落、沒人好好照顧的 metadata DB 出事,你才發現整個系統的記憶全在它身上,而你從沒把它當一回事。這件事給我的教訓超越 Airflow:一個系統最脆弱的地方,往往是它「看起來沒有狀態」而讓你忽略的那塊狀態。 無狀態的組件會誠實地告訴你「我可拋」,於是你認真做了冗餘;有狀態的核心卻常常藏得很好,騙過你的注意力。所以我看任何系統,第一個動作永遠是 把狀態核心揪出來——不是問「它有沒有狀態」,而是問「它的狀態藏在哪」。找到那顆藏起來的 DB,你才知道該把備份與 HA 的力氣花在哪。
把狀態和協調都收斂到一個地方,其餘就能無狀態化
Airflow 用 metadata DB 同時扛「狀態」和「scheduler 的協調」,這個設計我越看越覺得有代表性。它其實是一個很通用的模式:把難的東西(持久狀態 + 分散式協調)全部收斂到一個中心,系統其餘部分就能全部無狀態化、隨便擴。 K8s 把這個中心叫 etcd、Kafka 早期叫 ZooKeeper、Airflow 就叫 metadata DB。這是一種聰明的偷懶——與其讓每個組件都自己處理狀態與共識,不如指定一顆「心臟」扛下全部,其餘器官都做成可替換的。代價很明確也很公平:那顆心臟就是你必須用盡全力保護的單點,它的可用性直接封頂整個系統的可用性。認得出這個模式,你看任何分散式系統都會先去找它的「那顆心臟」在哪。
好的編排器應該很「瘦」
最後一個體會,是關於 Airflow 的分寸。它是編排器——負責「什麼時候、用什麼順序」跑,而不該親自扛運算。理想的 Airflow task,常常只是去觸發一個 Spark 作業、呼叫一個 API、送出一個查詢,真正的重活外包給專門的運算層。這讓 Airflow 自己保持輕盈:它不搬大資料,所以它的 worker 可以很小、它的瓶頸集中在排程與 DB 而非算力。我看過反例——把沉重的 pandas 運算硬塞進 Airflow task,結果 worker 記憶體爆掉、排程也被拖垮。編排的歸編排、運算的歸運算:一個好的編排器該像交通指揮,只管誰先走誰後走,絕不自己下場搬貨。這條界線劃清楚,整個資料平台的每一層才各自輕盈、各自好擴。