🌐 This page hasn't been translated yet — showing the original Chinese. Translated posts

Airflow:排程器、worker 與那個藏起來的狀態

· tech

#infrastructure#airflow

📑 目錄

上一篇埋了個伏筆:每個系統都有一塊逃不掉的狀態,認出它就掌握了命門。 Airflow 是這句話最好的示範。它表面上全是看似無狀態、可重啟的組件——scheduler、webserver、worker,你 kill 掉哪個再拉起來都沒事。但整個系統的記憶(哪些 DAG 跑過、哪個 task 卡在哪、上次成功是什麼時候)其實藏在一個你可能沒特別注意的地方:metadata DB。這一篇,就從這顆藏起來的 DB 講起。

狀態(樞紐):真狀態全在 metadata DB

看似無狀態的組件,真狀態藏在一顆 metadata DB Scheduler解析 DAG、排 task無狀態・可重啟 Webserver那個 UI無狀態 Worker × N執行 task可多開・可重啟 Metadata DB(Postgres)真狀態・命門 組件掛了都能換;DB 掉了 = 整個系統的記憶歸零(哪些跑過、誰在跑、誰失敗) 連多個 scheduler 的 HA,都靠對這顆 DB 上鎖(row lock)來協調
Airflow 的組件——Scheduler(解析 DAG、決定哪個 task 該跑)、Webserver(UI)、Worker(執行 task)——全是無狀態、可重啟、可多開的。它們共用底下那顆 metadata DB,而那才是真狀態:每一次 DAG run、每個 task 的狀態、connection、variable 全存在裡面。任何組件掛了拉起來就好,唯獨這顆 DB 掉了——整個系統就失憶了。這正是 上一篇說的「每個系統都有一塊逃不掉的狀態」,Airflow 的那塊就在這

這顆 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 最該先想清楚的一題:

CeleryExecutor Scheduler Broker(queue)Redis / RabbitMQ worker(常駐) worker(常駐) 固定一群 worker + 要多養一個 broker KubernetesExecutor Scheduler task-pod-a跑完即刪 task-pod-b跑完即刪 一 task 一 pod,像 Spark 一樣彈性(有開 pod 延遲) 兩種的 task 都無狀態、可重跑;差在「固定池 + broker」還是「一 task 一 pod」
CeleryExecutor:scheduler 把 task 丟進一個 Redis / RabbitMQbroker,由固定一群常駐 worker 搶著跑——任務量穩定時省下開 pod 的延遲,但你得多養一個 brokerKubernetesExecutor:scheduler 幫每個 task 開一個 pod、跑完即刪,沒有固定 worker、像 Spark executor 一樣彈性,代價是每個 task 都有開 pod 的啟動延遲。共同點是:worker/task 全是無狀態、可重跑的——真正的記憶還是在那顆 DB

HA、容量、監控、在 k8s 上

  • HA:組件都好做,DB 才是關鍵。scheduler(2.0 後可多 active)、webserver、worker 全能多開——因為它們無狀態。真正要花心思做 HA 的是那顆 metadata DB:它掛了,整個 Airflow 停擺。所以 DB 一律用 managed、多可用區、有備份。這整套「組件無狀態、狀態全外部化」的架構,好處就是 HA 幾乎只剩一件事:顧好 DB。
  • 容量:瓶頸常在 DB。scheduler 會頻繁查詢 DB 來排程,DAG 一多、parallelism 一高,DB 連線與查詢往往是第一個撞牆的地方——調 parallelismmax_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 記憶體爆掉、排程也被拖垮。編排的歸編排、運算的歸運算:一個好的編排器該像交通指揮,只管誰先走誰後走,絕不自己下場搬貨。這條界線劃清楚,整個資料平台的每一層才各自輕盈、各自好擴。