“日誌=資料庫”?聊一聊日誌背後的抽象

阿里妹導讀
本文從思考日誌的本質開始,一覽業界對日誌使用的最佳實踐,然後嘗試給出分散式儲存場景下對日誌模組的需求抽象,最後是技術探索路上個人的一點點感悟。
一、前言
這年頭,但凡是大佬總是要有一言兩語的金句,否則就稱不上大佬,技術圈亦如是。遙想 re:Invent 2023,來自 AWS Utility Computing 的 Peter DeSantis 的一句 "The log is the database" 吸足了眼球。時至今日,re:Invent 2024 這一年度盛會上,Aurora DSQL,DynamoDB 等產品的釋出會上反覆引用這句話,無疑繼續展示了雲計算領跑者對日誌之於資料庫重要性的高度認可。可是,“日誌=資料庫”?!小夥伴們乍一看心裡不免犯嘀咕,這兩者看起來有點風馬牛不相及吧?!
圖 1. ‌Peter DeSantis – SVP, AWS Utility Computing
事實上,在系統架構的儲存部分抽象出“單獨的”日誌模組,這樣的實踐在業界的資料庫產品中並不鮮見,無論是 AWS 的 Aurora DSQL/DynamoDB,Azure Socrates,還是國內的 OceanBase,Milvus,均做出了類似的架構選擇。需要強調的是這裡“單獨的”這一形容詞,從直觀上看,有人依賴 Paxos/Raft 這樣的共識庫來解決 durability 和 linearizability 問題,有人依賴 Kafka/Pulsar 這樣的日誌佇列來解決 durability 和 linearizability 問題。這個有區別嗎?答曰:此間差距,可謂雲泥之別,天下苦共識無統一標準久矣!筆者不才,先前在“共識協議的技術變遷 — 既要“高”容錯,又要“易”定序,還要“好”理解” 一文中有過些許拙論,感興趣的讀者不妨閱讀一下。一句話總結,日誌佇列提供了一個高度通用的抽象,解決了分散式系統中持久化、線性定序等公共的難點技術,可以成為各個有狀態產品爭相複用的公共元件。
圖 2. ‌迄今為止,業界對於共識最好的抽象 – 共享日誌(Shared Log)
不過,當我們縱覽業界各家資料庫產品的系統架構時,發現“單獨的”日誌模組大多選擇了自研,甚少依賴了已有的日誌佇列產品,這又是為什麼呢?先賣個關子,請繼續往下閱讀吧。本文的結構是:從思考日誌的本質開始,一覽業界對日誌使用的最佳實踐,然後嘗試給出分散式儲存場景下對日誌模組的需求抽象,最後是技術探索路上個人的一點點感悟。
二、日誌本質
有關日誌的本質以及在分散式系統中的各類應用,早在 2013 年,Kafka 之父 Jay Kreps 即在 – "The Log: What every software engineer should know about real-time data's unifying abstraction" 一文中有過非常專業的描述(這哥們的知識面實在是太廣泛了!)。如果用一段話來概括日誌對於分散式系統的意義,我認為應該是文章裡的這段:
How is a append-only sequence of records in any way related to data systems? The answer is that logs have a specific purpose: they record what happened and when. For distributed data systems this is, in many ways, the very heart of the problem.
也就是說,一個只追加的日誌系統記錄了分散式系統中什麼時間點發生了什麼事情。這個在分散式系統中意味著什麼?這意味著兩件極其美妙的事情:1)你可以重現完整歷史;2)你可以複製多份歷史。以資料庫系統為例,需要滿足 ACID 性質,分別指 Atomicity(原子性),Consistency(一致性),Isolation(隔離性),Durability(永續性)。相較於整體打包實現這四個特性的一個超級儲存體,一個只追加的日誌系統能夠提供其中的兩大性質 – Atomicity(原子性)與 Durability(永續性),而資料庫系統在此之上再做到資料一致性以及事務隔離性即可。
圖 3. ACID – 資料庫系統關鍵屬性的四分天下,Journal 可獨佔其二
當然日誌這個美妙的性質,人們很早就意識到了,日誌(訊息)佇列產品也是層出不窮,如 Kafka,Pulsar,RabbitMQ,RocketMQ 等等。我們做分散式系統的同學多少都接觸過計算-儲存分離的概念,雲原生資料庫的先驅者 – AWS Aurora,創新性地在資料庫領域採用了儲存計算分離架構。如圖 4 所示,再有日誌的這一層抽象,即進一步把日誌模組從儲存中抽離出來,那麼整個系統的架構就演進成了計算-日誌-儲存三級分離的架構。
圖 4. 隨著架構的演進,計算-日誌-儲存三級分離的架構開始逐步普及
書接上回,這裡我們探討一個問題:類似 Kafka/Pulsar 這樣的訊息佇列能否作為資料庫的日誌儲存元件?對於這個問題,我的回答是可以,也不可以。可以的原因是,即時分析資料庫 Rockset,向量資料庫 Milvus 應該都是直接依賴的開源訊息佇列 Kafka/Pulsar 做的日誌儲存,並且作者很是推崇“Replication as a Service” 這一趨勢。想一想這個其實也比較合理,對於外部資料庫創業公司而言,開源的 Kafka/Pulsar 提供了相當高的技術水準,能夠滿足我們對於日誌儲存絕大部分的想象,複用這樣成熟的元件,可以讓有限的人力聚焦在資料庫創新業務本身的邏輯。
為什麼說不可以呢?當然這個是對各雲計算大廠而言的。原因有二:作為資料庫產品依賴的核心元件,研發同學務必吃透它的每一行程式碼邏輯,才能滿足開發運維需求,而開源的訊息佇列如 Kafka/Pulsar 等功能追求大而全,程式碼細節可理解性並不強,此乃原因一也;分散式儲存場景下,日誌模組需要適配並支援業務的定製需求,譬如說基於分割槽排程模型的儲存產品 [4],分割槽的分裂合併是必須的業務需求,日誌模組如何保障分割槽分裂合併前後的資料一致性?再譬如說資料庫業務的跨域容災,日誌模組如何支援跨域部署,跨域同步複製?跨域就近訪問?再譬如說要支援跨區域的強一致性讀,日誌模組該如何支援 …… 這些資料庫乃至分散式儲存場景中的高階需求並非開源日誌佇列元件如 Kafka/Pulsar 等後繼發力的方向,所謂道不同不相為謀,此乃原因二也。
所以,假如(我是說假如)資料庫產品,包括雲端儲存產品需要一個公共的日誌元件,它應該長什麼樣子呢?吾輩皆非 Jay Kreps 這等大牛,面對此問,竟一時語塞 …… 所謂他山之石,可以攻玉,我們先看看業界同仁們的大作,嘗試從中提煉一下公共的需求吧。
三、環伺群雄
這個章節,我們看下 AWS 的 Aurora DSQL,DynamoDB,以及螞蟻的 OceanBase 等資料庫是如何從儲存模組中進一步抽象出日誌模組,並且這些日誌模組分別有哪些創新點(緊貼業務場景的高階需求)。
3.1. Aurora DSQL
圖 5 是 Aurora PostgreSQL 與 Aurora DSQL 的架構對比圖。不同於 Aurora PostgreSQL 中 Block 與 Log 放在一起的架構選擇,Aurora DSQL 從儲存模組中解耦出一個 Append-Only 的日誌模組。按照介紹,這是一個 AWS 內部被廣泛使用的公共日誌模組,建設已逾十載,使用場景包括 S3,Aurora DSQL,Kinesis,Lambda,以及下文的 DynamoDB 等等。
我們對比一下當抽象出一個日誌模組後,Aurora DSQL 相較於 Aurora PostgreSQL 在行為語義上產生了多麼大的差別。首先是對於多寫(Multi-Writer)的支援,都知道多寫定序是共識協議主推的技能棧,作為共識協議的通用抽象,日誌模組也是不遑多讓,必須是支援多寫定序的,因此 Aurora DSQL 自然而然也就能夠擁有多寫的能力。反觀 Aurora PostgreSQL,這是一種類似 Primary-Backup 這樣的架構,這樣的架構通常要麼只支援單寫(Single-Writer),要麼就是允許不同 AZ 同時寫,這樣會產生資料衝突,服務端再透過類似 Last-Writer-Wins 的策略自動處理資料衝突,保持資料的最終一致。這裡,Aurora PostgreSQL 最終選擇了只支援單寫(Single-Writer)。
然後是有關跨 AZ 的讀行為的一致性保證,Aurora PostgreSQL 提供了最終一致性的保證,這個跟它 Primary-Backup 的架構選擇是緊密相關的(我的意思是,PostgreSQL 當然可以在區域內多 AZ 之間直接上 Paxos/Raft 這樣的共識協議,實際的情況是業務並未做此選擇,可能是因為這樣會讓系統的複雜度變高,可能是因為這樣會讓系統的爆炸半徑變大,或者可能是因為這樣會讓寫效能變差,等等原因),產品選擇了在兩個 AZ 之間資料非同步複製。而基於一個共享的跨 AZ 日誌模組,Aurora PostgreSQL 則可以輕鬆提供跨 AZ 強一致讀的保障(具體機制在下面 DynamoDB 章節會詳細介紹)。
有關兩塊資料庫產品在衝突檢測(Locking vs. Optimistic Concurrency Control),以及是否保留本地快取這些方面的區別,更詳細的解讀大家可以參考文章 [7]。
圖 5. 牽一髮而動全身,Aurora 資料庫抽象出單獨日誌模組帶來的鉅變
此外,仔細地觀看一遍 re:Invent 2024 的 "Deep dive into Amazon Aurora DSQL and its architecture" 這個影片,相信你會有這樣的感覺:透過儘可能地解耦各個功能模組,諸如,Journal 模組負責 Atomicity 與 Durability,Adjudicator 模組負責 Isolation,Storage 模組負責 Querable 以及計算下沉,…… DSQL 就像在搭積木一般完成了這個 NB 產品的構建,並且保證了各層的極致擴充套件性,以及減少一切非必要的通訊開銷。此等清晰、優雅的架構設計實在是值得我們好好地觀摩借鑑。
圖 6. 儘可能解耦各功能模組,減少一切非必要通訊,可謂 DSQL 成功之法寶
總結一下有關 Aurora DSQL 的調研:分散式系統中,如何有效儲存與管理狀態,應該是最難的問題之一吧?如果存在一個公共的日誌模組,提供基礎的 Atomicity(原子性)與 Durability(永續性)的保障,這將使得狀態維護的功能模組在行為語義上有了更多想象空間(譬如這裡的支援多寫,跨域同步複製等等),以下轄多個儲存產品的儲存部門為例,這樣元件的存在也有利於快速拉齊各家的技術能力水平。
3.2. DynamoDB
DynamoDB 在 re:Invent'24 上重點推介了其 GlobalTable 產品形態的最新殺器 – 跨地域強一致。事實上,早在 2017 年 DynamoDB 就推出了 GlobalTable 這個產品形態,不過初期的架構是典型的多主非同步複製,資料最終一致(Multi-Region Eventual Consistency),支援多寫然後透過 Last-Writer-Wins 策略來處理衝突。今年的 re:Invent'24,DynamoDB 宣佈進一步支援跨域強一致模式(Multi-Region Strong Consistency)。
如圖 7 所示,DynamoDB GlobalTable 為了支援跨域讀的強一致性,思路上與 Aurora DSQL 如出一轍:首先引入公共的跨域部署的日誌元件 – mRSC(multi-Region Strong Consistency) LOG(沒錯,就是上面提到的在 AWS 內部被廣泛使用的公共日誌模組),譬如在地域 R1 收到一個寫請求,先將該寫請求的日誌寫入 mRSC LOG,在至少兩個區域都完成日誌持久化後,各地域開始依序應用(APPLY)寫請求日誌,當該寫請求在區域 R1 完成了應用(APPLY),即可返回使用者成功(返回碼 200)。
GlobalTable 的強一致性讀是怎麼實現的呢?所謂英雄所見略同(當年我們在做一款跨地域同步複製產品的時候,的的確確也提出了類似的機制),譬如在地域 R1 要做一個強一致性讀,這裡需要引入一種特殊的心跳請求,這個心跳請求也會像寫請求一樣,寫入日誌模組,端好板凳排好隊,依序追加(APPEND),但是心跳請求並不需要持久化(出於效能的考慮),最終,當這個心跳請求在地域 R1 被應用(APPLY)的時候,我們知道本地域資料已經追到最新,此時直接讀本地資料即為強一致性讀。
圖 7. DynamoDB GlobalTable 如何做到跨地域的強一致讀
總結一下有關 DynamoDB 的調研:資料庫場景存在實際的跨地域下強一致性讀的客戶需求(這方面國外市場似乎走的更前面一些?),這個功能並不容易實現。但是我們看到,AWS 內部有這麼一個公共的日誌模組,在該日誌模組上定製一個簡單的“特殊心跳”,很快,Aurora SQL,DynamoDB 都宣稱支援跨域一致性讀了。此外,從物理約束而言,跨域強一致讀是規避不了至少一跳(RTT)跨域訪問的,所以,DynamoDB 的這個實踐實在算得上是乾淨漂亮。
3.3. OceanBase
很早就聽說過螞蟻的 OceanBase 實現了純血的 Paxos,沒有太多可供參考的成例,OceanBase 能夠手搓祖師爺級共識協議- Paxos,並且成功地大規模應用於工業生產,作為 Raft 的擁躉,我是十分之敬佩。可惜一直以來都不知道 OceanBase 實現 Paxos 的實現細節。直到 2024 年,終於等來了解密時刻,他們在 VLDB'24 上發文章啦 [9]。
圖 8 是 OceanBase 的 WAL 日誌複製架構,可以看到同樣是“計算-日誌-儲存”三級分離的架構。這裡的日誌模組叫做 PALF – a Paxos-backed Append-only Log File System。就流程而言,應用(Application)提交的事務會直接修改記憶體態的儲存引擎(這樣可以拓展可支援的事務大小);日誌記錄隨即產生,並提交至 PALF。注意,這裡事務引擎的 Leader 會像使用本地檔案系統一樣使用 PALF,所以,它只關心這些日誌記錄是否 flushed;現在到 PALF 的工作時間了,它負責將事務引擎 Leader 端發生的修改記錄依序複製到其餘的 Followers。只要日誌提交(Committed)至 PALF,那麼事務引擎 Leader 明白該修改已經持久化,可以響應應用(Application),當然其餘的 Followers 也會依序執行 Leader 端發生的事務修改。
圖 8. OB 呀 OB,沒想到你這濃眉大眼的傢伙也投奔了“共享日誌”陣營
相信從 PALF 的字面意思,大家能看出來,作為一個單獨的日誌複製模組,OceanBase 自研的 Paxos 協議是其核心競爭力。為什麼說有必要抽象出一個日誌模組,並且有時候直接拿開源的 Kafka/Pulsar 還不夠呢。我們看下 PALF 在緊貼業務場景的高階需求之下做的一些獨到的創新吧。
首先討論選舉(Leader Election)的話題。在 Raft 的選舉演算法中,擁有最新事務日誌的節點才有可能被選舉為 Leader,這個無疑增加了誰會當選 Leader 這件事件的不確定性。通常情況下,每個節點都是同等的候選人,誰來當 Leader 沒什麼差別。但是假如我們無法平等看待每個候選節點呢?例如大名鼎鼎的兩地三中心場景,在這個場景中,我們自然希望 Leader 永遠留在本地域的兩個中心,而非另一個地域(這會引入跨地域的訪問延遲)。這裡 PALF 的創新是,選舉過程引入一個 “Reconfirmation” 階段,即從其餘節點拖拉最新資料,從而保證某個候選節點成為 Leader 時的日誌完整性。基於這個創新,PALF 支援所謂 “config version” 的優先順序設定,支援優先順序更高的節點一定能夠成為 Leader。這個功能真的非常實用。無論是兩地三中心場景,還是跨域同步複製場景,大體都會面臨類似的挑戰。
再討論一個三態(請求成功?失敗?)的話題。在 Raft 實現中,舊 Leader 下線會直接進入 Follower 狀態,這就可能存在一些舊 Leader 提交的日誌尚未被明確狀態,不知道實際是成功了還是失敗了,這些事情就交給下一任,新 Leader 上線後需要以當前 Term 提交一個日誌來間接地提交掉舊 Leader 的所有日誌。即使這樣,舊 Leader 之上的事務引擎卻是無法拿到狀態響應,只能透過再次詢問,甚至重試提交來明確狀態。這裡 PALF 的創新是,舊 Leader 下線時會進入所謂 Pending Follower 階段,舊 Leader 會繼續等待,直到所有舊 Leader 的日誌被確認,從而能夠給事務層返回明確狀態,然後才會進入真正 Follower 狀態。有了這個精巧的設計,PALF 就能夠給依賴其實現高可用性的事務層提供一個可靠的介面語義,更有利於事務引擎層的開發。
圖 9. OceanBase 中通用日誌模組 – PALF 的架構設計圖
總結一下有關 OceanBase 的調研:我們看到了業務側更多高階需求,兩地三中心,跨域同步複製等場景,如何減少跨域訪問成為了新的主要矛盾。OceanBase PALF 演進出了能夠指定 Leader 的能力,類似的,Aurora DSQL 的日誌佇列中也透過引入原子鐘服務(提供微秒級的精確時間戳,標記每條日誌),在跨域強一致性的場景下避免了不必要的跨域訪問。
四、日誌的抽象
所以,回到前面的問題,資料庫產品,包括雲端儲存產品需要一個公共的日誌元件,那麼這個日誌模組應該怎樣抽象呢?事實上,一旦開始做公共的日誌元件,免不了一直被拷問:你們這個元件跟 Kafka/Pulsar/RocketMQ 這些訊息佇列有什麼區別?可不可以直接複用現有的訊息佇列?
我們嘗試回答一下。如圖 10 所示,資料庫產品,包括雲端儲存產品需要的公共日誌元件,應該有三個境界。“昨夜西風凋碧樹,獨上高樓,望盡天涯路”,作為一個合格的分散式日誌系統,第一境界要具備以下四大性質:
  1. 永續性(durability),即寫入的日誌資料必須落盤,即使發生 failover,資料不丟;
  2. 可容錯(fault tolerant),即可以承受部分節點掛掉,整體服務不受影響;
  3. 唯一定序(uniquely ordered):不同的消費者,總能夠以唯一順序消費某個日誌序列,不能千人千面;
  4. 線性定序(linearizability):資料一致性有不同層次,作為儲存產品,我們面向業務側提供資料線性一致性的保證;
就這四大屬性而言,開源的 Kafka,Pulsar 都是具備的。要不怎麼說用這些開源訊息佇列元件,也沒啥大面上的問題。
圖 10. 分散式儲存場景下,通用複製日誌模組的抽象
到了第二重境界了 – “衣帶漸寬終不悔,為伊消得人憔悴”。這個境界是要貼合儲存業務場景做需求抽象。首先是支援複製狀態機模型,這裡就要引入明確的學習者(Learner)註冊機制。即日誌系統需要感知某個日誌佇列有哪些學習者,需要記錄這些學習者的學習進度。這樣一方面支援日誌的有效回收(GC,Garbage Collection),另一方面也有利於新加入的 Learner 尋找合適的現有 Leader 來拉起資料。然後是支援儲存業務的分割槽架構,對於通用的複製日誌模組,這個本質上是抽象出了 IO Fencing 以及 IO Flushing 的需求。
最後到了第三重境界,“眾裡尋他千百度,驀然回首,那人卻在燈火闌珊處”。這個境界是要做儲存業務的高階需求。作為儲存業務 IO 鏈路的依賴,必然要求日誌佇列本身要具備很強的擴充套件性。譬如 Kafka,計算儲存耦合在單個節點,因此就某個分割槽而言,單個節點容易成為其容量與效能的瓶頸。相較而言,Pulsar 的計算儲存分離的架構則在服務能力上提供了較強的水平擴充套件性。另外還是跨域這個話題。作為公共複製日誌模組,並且身處儲存業務 IO 鏈路的依賴,要儘可能避免引入跨域訪問的延遲。無論是 OceanBase 的 PALF 的實踐,Aurora DSQL 的原子鐘的創新,本質都是在直面這個高階需求。
好了,三重境界講完了,這些能力的抽象,更多是我們在配合業務做跨域同步複製功能過程中的一點經驗,提煉出來,算是拋磚引玉,幫助身處探索路上技術人看清前路吧。

參考資料

[1] The Log: What every software engineer should know about real-time data's unifying abstraction,https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying
[2] 聊聊雲原生資料庫的一致性,https://segmentfault.com/a/1190000041871981
[3] 共識協議的技術變遷 — 既要“高”容錯,又要“易”定序,還要“好”理解,https://mp.weixin.qq.com/s/UY9TPMcuf0O7xS0kuXTcVw
[4] 聊一聊分散式鎖的設計模型,https://mp.weixin.qq.com/s/uA26VVmYMTfs-dWcLOY04w
[5] Deep dive into Amazon Aurora DSQL and its architecture,https://www.youtube.com/watch?v=huGmR_mi5dQ
[6] Deep dive into Amazon Aurora and its innovations,https://www.youtube.com/watch?v=kVVdHezNTpw
[7] AWS re:Invent2024 Aurora 釋出了啥 — DSQL 篇,https://zhuanlan.zhihu.com/p/12796249884
[8] Socrates: The New SQL Server in the Cloud,https://www.microsoft.com/en-us/research/uploads/prod/2019/05/socrates.pdf
[9] PALF: Replicated Write-Ahead Logging for Distributed Databases,https://www.vldb.org/pvldb/vol17/p3745-xu.pdf
[10] OceanBase的一致性協議為什麼選擇 paxos而不是raft?https://www.zhihu.com/question/52337912/answer/3635459149
PAI部署多形態的Stable Diffusion WebUI服務
本方案提供了方便、高效的模型部署產品,並支援根據實際需求,配置不同的服務版本及服務引數。具有分鐘級部署上線,方便快捷、開箱即用,多版本部署方案,引數可定製化調整的優勢。詳細方案點此查詢    
點選閱讀原文檢視詳情。

相關文章