文章最早發表在 ACM Queue 期刊第 16 卷第 2 期,可經過 ACM 數字圖書館查閱(https://portal.acm.org/citation.cfm?id=3220266)。引用該文章:「Alex Petrov. 2018. Algorithms Behind Modern Storage Systems. Queue 16, 2, pages 30 (April 2018), 21 pages. DOI: https://doi.org/10.1145/3212477.3220266.」。html
應用處理的數據量在持續增加。數據的增加,對擴展存儲能力提出了挑戰。就此問題,每種數據庫管理系統都有其自身的權衡考慮。對於數據管理者而言,理解這些權衡因素很是關鍵,這有助於從多種方式中作出正確的選擇。mysql
從讀 / 寫工做負載平衡、一致性需求、延遲和訪問模式等方面看,應用是各異的。若是咱們能對數據庫和存儲內部設施架構決策瞭然於胸,那麼將有助於咱們理解系統行爲模式的緣由所在,一旦在問題時能解決問題,並能根據工做負載調優數據庫。算法
一個系統不可能在全部方面上都是最優的。確保無存儲開銷、提供最優讀寫性能的數據結構只存在於理想狀況下,在實踐中固然是不可能存在的。sql
本文詳細剖析了兩種被大多數現代數據庫使用的存儲系統設計方法,即針對讀優化的 B 樹1和針對寫優化的 LSM(日誌結構合併,log-structured merge)樹 5,並分別給出了兩種方法的一些用例和權衡考慮。數據庫
B 樹是一種廣爲使用的讀優化索引數據結構,是二叉樹的一種泛化。它具備多種變體,並已用於多種數據庫(包括 MySQL InnoDB4 和 PostgreSQL 7)和文件系統(例如,HFS+八、ext4 中的 HTrees 9)。B 樹中的「B」表示「Bayer」,指的是數據結構的最初創立者 Rudolf Bayer,也能夠說是 Bayer 彼時供職的波音公司(Boeing)。後端
二叉樹中,每一個節點有兩個子節點(分別稱爲左子節點和右子節點)。保存在左子樹和右子樹中的鍵(Key),其值分別小於和大於當前節點的鍵。爲維持樹的深度最小,二叉樹必須是平衡的。在添加隨機順序的鍵到樹中時,最終很天然會致使樹的一邊比另外一邊更深。緩存
一種二叉樹重平衡(rebalance)的法是稱爲「旋轉」(rotation)方法。旋轉方法實現節點的從新排列,它將更深子樹的父節點下推到其子節點之下,並上移子節點爲有效地置於父節點的原位置。圖 1 給出了一個旋轉方法的例子,實現了一個二叉樹的平衡。左圖的二叉樹在添加了節點「2」以後,是不平衡的。爲了平衡二叉樹,咱們以節點「3」爲軸心旋轉樹,而後以節點「5」爲軸線。節點「5」是原先的根節點,也是節點「3」的父節點,旋轉後成爲節點「3」的子節點。在完成旋轉後獲得右圖的樹,其中左子樹深度下降了 1,右子樹的深度增長了 1,而樹的最大深度下降了。性能優化
圖 1 例子:使用旋轉方法平衡二叉樹服務器
二叉樹是一種十分有用的內存數據結構。因爲平衡(即須要保持全部子樹的深度最小)和低扇出(每一個節點最多具備兩個指針)特性,二叉樹在磁盤上的性能並很差。B 樹容許每一個節點存儲兩個以上的指針,並可將節點大小調整爲適合頁面的大小(例如,4KB),所以可在塊設備上良好工做。當前,有一些實現中使用了更大規模的節點,甚至橫跨多個頁面。微信
B 數據有以下屬性:
排序:排序支持順序掃描,簡化了查找。
自平衡:插入和刪除操做無需重平衡樹。一個 B 樹節點在佔滿後,將分割(split)爲兩個節點。若是兩個近鄰節點的利用率(occupancy)降至某個閾值如下時,那麼節點會合並(merge)。這意味着,各個葉子節點與根節點間是等距的,在查找時可使用一樣的步數定位。
查找操做有對數時間複雜度保證。這一點使 B 樹成爲數據庫索引的很好選擇,由於在數據庫中,查找時間是很是關鍵的。
支持可變數據結構。插入、更新和刪除(以及隨後的節點分割和合並)是在磁盤上執行的,實現就地(in-depth)更新須要必定的空間開銷。B 樹能夠組織爲聚束索引,將實際數據存儲在葉子節點上,也可使用非聚束索引,將數據存儲爲堆文件。
本文還將介紹 B+ 樹 3。B+ 樹是 B 樹的一種變體,經常使用於數據庫存儲。與原始 B 樹相比,B+ 樹的不一樣之處在於:1. B+ 樹的葉子節點存儲值並造成一個額外的連接層。2.B+ 樹的內部節點並不存儲值。
下面咱們仔細查看 B 樹的構建模塊,如圖 2 所示。B 樹具備多種節點類型,包括根節點、內部節點和葉子節點。根節點(頂端)是沒有父節點的節點(即它不是任何其它節點的子節點)。內部節點(中間)具備父節點和子節點,它們鏈接了根節點和葉子節點。葉子節點(底端)保存數據,它沒有子節點。圖 2 顯示的 B 樹的分支因子(branching factor)爲 4,即具備四個指針,內部節點有三個鍵值,葉子節點有四個鍵值對。
圖 2 例子:B 樹
標識一個 B 樹,可以使用以下指標:
分支因子:即指向子節點的指針數(N)。考慮存在指針,根節點和內部節點最大可保存 N-1 個鍵值。
利用率:最大可用指針數中,當前有多少指向子項的指針在用。例如,若是樹的分支因子是 N,節點當前保持了 N/2 個指針,那麼利用率就是 50%。
高度:B 樹的層數,指明瞭在查找中需遍歷的指針個數。
樹中每一個非葉子節點最多保持 N 個鍵(索引項),將樹分割爲 N+1 個子樹,這些子樹可用相應的指針定位。在條目 Ki 中的指針 i 指向的子樹中,全部索引項是 Ki-1 <= Ksearched < Ki(其中 k 是一組鍵)。第一個和最後一個指針是特例,最左子節點指向的子樹中,全部的條目小於或等於 K0;最右子節點指向的子樹中,全部的條目大於 KN-1。葉子節點中包含的指針,可指向同一層中前一個或後一個節點,造成近鄰節點的雙向連接列表。全部節點中,鍵老是排序的。
在執行查找時,搜索將從根節點開始,沿內部節點遞歸下行至葉子層級。在每一層級,經過追隨子節點指針,搜索空間可縮減到子樹範圍(該子樹包括搜索值)。圖 3 顯示的是 B 樹中的一次查找,即一次沿着兩個鍵間的指針由根到葉子的遍歷,一個指針大於或等於搜索項,另外一個指針小於搜索項。執行一個點查詢(Point Query)時,搜索在定位到葉子節點後結束。在範圍搜索中,會遍歷所找到葉子節點的鍵和值,而後是近鄰的葉子節點,直到到達範圍的終點。
圖 3 單次由根到葉子的遍歷
從複雜性上看,B 樹保證了 log(n) 複雜度的查找,由於如何從節點中找到鍵中使用了二分查找法,如圖 4 所示。二分查找法易於解釋,當從字典中搜索具備某個首字母的單詞時,全部單詞是按字母順序排列的。首先選擇從確切的中間位置打開字典。若是搜索字母在字母序上要「小於」(先出現)打開的字母,那麼繼續在左半部份字典中搜索。不然,在詞典右半部份中搜索。而後繼續縮減剩餘頁面範圍,經過減半並選擇搜索方向,直到找到所需的字母。每步將搜索空間減半,使查找呈對數時間複雜度。B 樹中的搜索具備對數時間複雜度,由於節點層級鍵是排序的,並在查找匹配總使用了二分查找。這也是爲何在整個樹中保持高利用率和一致性是很是重要的。
圖 4 B 樹的二分查找
執行插入時,第一步是定位目標葉子節點。在此可以使用上面介紹的搜索算法。定位目標節點後,鍵和值將添加到該節點中。若是葉子節點的空間不夠用,這種狀況稱爲「溢出」(Overflow),葉子節點必須分割爲兩個。分割的實現是經過分配一個新葉子,將原葉子節點中的半數元素移動到新的葉子節點,並在父節點中分配一個指向新葉子節點的指針。若是父節點中也沒有空餘的空間,那麼就在父節點層級執行分割操做。操做將持續直至到達根節點。若是根節點溢出,節點內容在新分配節點間分割。而後根節點自身將被覆蓋,以免從新分配。這也意味着,樹(及樹的高度)的高度老是在分割根節點時增加。
日誌結構合併(LSM)樹是一種寫優化的數據結構,它是不可變的、駐留於磁盤的,適用於寫操做比查找和檢索記錄更爲頻繁的系統。因爲 LSM 樹消除了隨機插入、更新和刪除,所以它獲得了更多的關注。
爲支持順序寫,LSM 樹在一個駐留內存表(一般使用支持對數時間複雜度查找的數據結構實現,例如二分查找樹或跳錶)中批量寫入和更新,直至內存表規模達到一個設定的閾值,這時再寫入到磁盤,該操做稱爲「刷新」(flush)。檢索數據須要搜索樹駐留磁盤的全部部分,檢查駐留內存表,並在返回結果前合併內容。圖 5 顯示了一個 LSM 樹的結構,其中的駐留內存表用於寫入。一旦內存表達到了必定規模大,其中經排序的內容就要就寫入到磁盤。讀取時須要訪問駐留磁盤和駐留內存表,並須要一個合併過程去整合數據。
圖 5 LSM 樹的結構
現代多種系統中,例如 RocksDB 和 Apache Cassandra,將 LSM 樹的駐留磁盤表實現爲一種 SSTable(排序字符串表)。SSTable 具備簡單性(易於寫入、搜索和讀取)及合併屬性(在合併期間,源 SSTable 掃描和合並結果寫是順序操做)。
SSTable 是一種不可變的、駐留磁盤的排序數據結構。如圖 6 所示,SSTable 在結構上可分爲兩個部分,即數據塊和索引塊。數據塊是由順序寫入的惟一鍵值對組成,鍵值對按鍵排序。索引塊中的鍵包含映射到數據塊指針,指針指向實際記錄的位置。索引一般實現爲針對快速搜索優化的格式,例如 B 樹,或是對於點查詢使用哈希表。SSTable 中的每一個值項具備一個與之相關聯的時間戳。時間戳指定了插入和更新的寫入時間(一般不作區分),以及刪除的移除時間。
圖 6 SSTable 的結構
SSTable 具備一些很好的特性:
點查詢(即根據鍵找到一個值)可經過查找主索引快速完成。
掃描(即在指定鍵範圍內迭代全部鍵值對)能夠高效完成,僅經過在數據塊內順序讀取鍵值對。
SSTable 給出了一段時間內全部數據庫操做的快照。由於 SSTable 是由駐留內存表的刷新操做建立的,該表做爲此時期內對數據庫狀態操做的一個緩衝區。
檢索數據時,須要搜索磁盤上全部的 SSTable,檢查駐留內存表,並在返回結果前合併其中的內容。讀操做須要合併過程,由於所搜索的數據可能存在於多個 SSTable 中。
爲確保實現刪除和更新,也必需要合併步驟。刪除時,會在 LSM 樹中插入一個佔位符,一般稱爲「墓碑」(tombstone)。墓碑用於標記被刪除的鍵。相似地,更新時也僅是增長一個具備更遲時間戳的記錄。在讀取期間,將跳過被標記爲刪除的記錄,不返回給客戶。更新中也採起相似的作法,對於兩個具備同一鍵的記錄,只返回時間戳更晚的記錄。圖 7 顯示了合併是如何整合存儲在獨立表中具備同一鍵的數據。如圖所示,Alex 的記錄寫入的時間戳爲 100,更新了電話後記錄的時間戳爲 200。John 的記錄是被刪除的。其它兩個條目保持原狀,由於它們並未作標記。
圖 7 例子:合併步驟
爲減小需搜索的 SSTable 數量,避免由於搜索某個鍵而檢查每一個 SSTable,一些存儲系統使用了一種稱爲布隆濾波器 10 的數據結構。布隆濾波器是一種機率數據結構,可用於檢測一個元素是否屬於一個集合的成員。它會產生誤報匹配(即指出元素是集合的成員,可是事實上並非),可是不會產生漏報(即若是返回結果是不匹配,那麼該元素必定不是集合的成員)。換句話說,布隆濾波器可用於告知一個鍵是否「可能位於 SSTable 中」,或是「絕對不在 SSTable 中」。若是一個 SSTable 被布隆濾波器返回爲不匹配,那麼將在查詢中跳過。
鑑於 SSTable 是不可變的、是順序寫的,而且並未保留就地更改的空間。這意味着,插入、更新和刪除操做須要重寫整個文件。全部修改數據庫狀態的操做,是在內存駐留表中「批量處理」的。隨時間的推動,駐留磁盤表的數量將會增加(對應同一鍵的數據可能會位於多個文件、同一記錄的多個版本,或標記爲刪除的冗餘記錄中),讀取將繼續變得代價更爲昂貴。
爲下降讀取的代價、整合被標記的記錄空間並下降駐留磁盤表的數量,LSM 樹須要一個緊縮(compaction)過程。緊縮過程從磁盤讀取整個 SSTable,併合並它們。由於 SSTable 是按鍵排序的,緊縮過程的工做方式相似於歸併排序,因此該操做也是很是高效。記錄從多個數據源順序讀取,合併的輸出能夠即刻順序地附加到結果文件中。歸併排序的一個優勢是工做高效,即使是對於歸併沒有法放入內存中的大型文件。生成的表將保持原始 SSTable 的排序。
在緊縮過程當中,合併後的 SSTable 將被拋棄,並被更「緊縮」的表替代,如圖 8 所示。緊縮操做輸入爲多個 SSTable,輸出爲合併後的一個表。一些數據庫系統在邏輯上將同一規模的表分組爲同一「層級」,並在某個層級中的表數量足夠多時,開始合併過程。緊縮減小了必需要處理的 SSTable 數量,使查詢更加高效。
圖 8 緊縮過程
爲實現 I/O 操做數量減小和順序化,B 樹和 LSM 樹均在更新實際發生前作內存中的批處理。這意味着,一旦發生失敗,不能保證數據的完整性,並且也不能確保原子性(指一組更改的應用是原子化的,如同單個操做同樣,或者所有應用,或者全不該用)和持久性(確保在進程崩潰或掉電時,數據處於一致性存儲中)。
爲解決這個問題,不少現代存儲系統使用了 WAL 技術(預寫式日誌,Write-Ahead Logging)。WAL 的主要理念是全部數據庫狀態修改首先持久保持在位於磁盤上的只添加日誌中。一旦操做過程當中發生進程崩潰,就會重執行日誌,以確保沒有數據丟失,實現全部更改的原子化。
B 樹中,使用 WAL 可理解爲更改只有作日誌後,才寫到數據文件中。一般狀況下,B 樹存儲系統的日誌規模相對較小。一旦更改應用到持久存儲,就會被丟棄。WAL 做爲一種對未日誌化(in-flight)操做的備份機制,即應用到數據頁面的任何更改均可以從日誌記錄重作。
LSM 樹中,WAL 用於持久化那些操做了內存表可是並未徹底刷新到磁盤的更改。一旦內存表徹底刷新並切換,讀取操做能夠在新建立的 SSTable 上完成,就能夠丟棄保持了刷新內存表數據的 WAL 段。
B 樹和 LSM 樹結構上的最大差異之一,在於優化的目的,以及優化的意義。
下面對 B 樹和 LSM 樹作一個對比。總而言之,B 樹具備以下屬性:
B 樹是可變的,這支持經過引入一些空間開銷,以及更爲關聯的寫路徑,實現就地更新。B 樹並不須要徹底的文件重寫或多源合併。
B 樹是讀優化的。即 B 樹不須要從多個源讀取(所以也不須要此後的合併操做),這簡化了讀路徑。
寫可能會觸發節點的級聯分割,這會使一些寫操做更昂貴。
B 樹是針對分頁(塊存儲)環境優化的,其中不存在字節地址。They are optimized for paged environments (block storage), where byte addressing is not possible.
雖然也須要重寫,可是一般狀況下 B 樹存儲要比 LSM 樹存儲須要更少的維護。
併發訪問須要讀 / 寫隔離,其中一系列的鎖和閂(latch)。
LSM 樹具備以下特性:
LSM 樹是不可寫的。SSTable 是一次性寫入磁盤的,永不更新。緊縮操做經過從多個數據文件移除條目,併合並具備相同鍵的數據,實現空間的整合。在緊縮過程當中,已合併的 SSTable 將被丟棄,並在成功合併後移除。不可寫提供的另外一個有用特性,就是刷新後的表可併發訪問。
LSM 是寫優化的。這意味着寫入操做將被緩存,並順序地刷新到磁盤中,潛在地支持磁盤上的空間本地性。
讀操做可能須要從多個數據源訪問數據。由於不一樣時間寫入的具備相同鍵的數據,可能會落在不一樣的數據文件中。記錄在返回給客戶前,必須通過合併過程。
LSM 樹須要作維護和緊縮,由於緩存的寫入操做將被刷新到磁盤。
在存儲系統的開發中,老是須要考慮一些挑戰和因素。優化目標對存儲系統選擇有着切實的影響。若是能夠在寫操做上花費更多時間,那麼就能夠部署針對更高效讀操做的結構,預留額外的空間用於就地更新。這有利於實現更快的寫操做,並支持將數據緩存在內存中,以確保順序的寫操做。可是,全部這些是不可能一次性達成的。咱們理想中的存儲系統具備最低的讀代價、最低的寫代價,並無其它開銷。但在實踐中,數據結構需折衷考慮多個因素。理解這些折衷考慮是很是重要的。
哈佛大學 DASlab(數據系統實驗室)的研究人員總結了數據庫系統優化的三個關鍵參數:讀開銷、更新開銷和內存開銷,統稱爲「RUM」。對於特定的用例,理解這些參數中哪一個是最重要的,將對數據結構、訪問方法,甚至是特定工做負載的適用性產生影響,由於算法須要根據特定的用例作出調整。
「RUM 假說」(http://daslab.seas.harvard.edu/rum-conjecture/)2 指出,若是對 RUM 中的兩項設置上限,那麼也會對第三項設置下限。例如,B 樹是讀優化的,代價是寫開銷,以及預留了額外的空間(於是致使了內存開銷)。LSM 樹空間開銷更少,代價是在讀操做期間必須訪問多個駐留磁盤表,從而引入了讀開銷。這三個參數造成了一個徹底三角形,改進其中的一項,意味着對其它項的折衷考慮。圖 9 展現了 RUM 假說。
圖 9 RUM 假說
B 樹是針對讀性能優化的。索引的佈局方式使得遍歷樹所需的磁盤訪問次數最小化。定位數據時,只需訪問單個索引文件。這是經過保持索引文件可寫而實現的。可寫增長了寫入放大(Write Amplification)問題,該問題由節點分割、合併、重定位和碎片化 / 不平衡相關維護等致使。爲緩解更新的代價,並下降分割的次數,B 樹在各個層級的節點中預留了額外的空閒空間。這有助於推遲發生寫入放大問題,直至節點空間滿。簡而言之,B 樹是在更新和內存開銷間作了權衡,目的是實現更好的讀性能。
LSM 樹針對寫性能優化。不管更新或是刪除,都須要定位數據在磁盤上的位置(B 樹也須要)。LSM 樹經過在內存駐留表緩存全部插入、更新和刪除操做以保證順序寫。這樣作的代價是更高的維護代價、須要緊縮操做(緊縮操做只是一種緩解不斷增加的讀代價、減小駐留磁盤表數量的方式),以及更昂貴的讀(由於數據必須從多個源讀取併合並)。同時,LSM 樹不保持任何空閒空間,這消除了一些內存開銷(不一樣於 B 樹節點平均利用率爲 70%,就地更新須要必定的開銷)。因爲 LSM 樹最終文件是不寫的,爲實現更好的使用率,須要支持塊壓縮。簡而言之,LSM 樹是在讀性能和維護更好寫性能和低內存開銷間的權衡。
對於每種所需的特性,都會存在針對此特性優化的數據結構。若是使用相適應的數據結構以支持更好的讀性能,那麼代價是更高的維護代價。添加元數據有利於遍歷(例如分散層疊(fractional cascading)),這將影響寫的時間,並佔用空間,可是能夠改進讀的時間。使用壓縮技術 (例如,Gorilla 壓縮 六、delta encoding 等算法) 可優化內存效率,將對寫時數據打包和讀時數據解包添加一些開銷。有時,咱們能夠權衡功能和效率。例如,堆文件和哈希索引因爲文件格式的簡單性,能夠給出很好的性能保證,以及更小的空間開銷,但代價是隻能支持執行點查詢。咱們還也能夠經過使用近似數據結構,例如布隆濾波器、HyperLogLog、Count-Min sketch 等,權衡空間精度和效率。
讀、更新和內存這三種可調整的開銷,有助於咱們評估數據庫,並更深刻理解數據庫適合何種工做負載。三者很是直觀,很容易將存儲系統排序在一個桶中,給出執行狀況的猜想,進而經過深刻的測試去驗證這一假設。
固然,評估存儲系統時還存在其它一些重要的因素,例如維護代價、操做簡單性、系統需求、對頻繁更新和刪除的適用性、訪問模式等。RUM 假說僅是有助於給出直觀感受,並對最初方向提供經驗法則。理解咱們本身的工做負載,這是邁向構建可擴展的後端系統的第一步。
在不一樣的實現中,一些因素可能會發生變化。即使是使用相似存儲設計原則的兩個數據庫間,最終的表現也可能會徹底不一樣。數據庫是一個複雜的系統,其中有不少變更因素。數據庫也是不少應用中重要且不可分割的部分。性能上的權衡有助於咱們一窺數據庫的底層機制。瞭解底層數據結構及內部原理間的差別,有助於咱們從中作出最優的選擇。
Comer, D. 1979. The ubiquitous B-tree. Computing Surveys 11(2); 121-137;
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637.
哈佛大學 DASlab 實驗室. The RUM Conjecture;
http://daslab.seas.harvard.edu/rum-conjecture/.
Graefe, G. 2011. Modern B-tree techniques. Foundations and Trends in Databases 3(4): 203-402;
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.219.7269&rep=rep1&type=pdf.
MySQL 5.7 參考手冊. InnoDB 索引的物理結構 ;
https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html.
O'Neil, P., Cheng, E., Gawlick, D., O'Neil, E. 1996. The log-structured merge-tree. Acta Informatica 33(4): 351-385;
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.44.2782.
Pelkonen, T., Franklin, S., Teller, J., Cavallaro, P., Huang, Q., Meza, J., Veeraraghavan, K. 2015. Gorilla: a fast, scalable, in-memory time series database. Proceedings of the VLDB Endowment 8(12): 1816-1827;
http://www.vldb.org/pvldb/vol8/p1816-teller.pdf.
Suzuki, H. 2015-2018. The internals of PostgreSQL;
http://www.interdb.jp/pg/pgsql01.html.
Apple HFS Plus Volume 格式 ;
https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#BTrees
Mathur, A., Cao, M., Bhattacharya, S., Dilger, A., Tomas, A., Vivier, L. (2007). The new ext4 filesystem: current status and future plans. Proceedings of the Linux Symposium. Ottawa, Canada: Red Hat.
Bloom, B. H. (1970), Space/time trade-offs in hash coding with allowable errors, Communications of the ACM, 13 (7): 422-426
HP 實驗室 Goetz Graefe 的文章「五分鐘規則(https://queue.acm.org/detail.cfm?id=1413264):20 年後閃存將如何改變規則」。舊規則將持續演進,同時閃存給出了兩個新規則。
https://queue.acm.org/detail.cfm?id=1413264
Rick Richardson,「數據庫消岐」(https://queue.acm.org/detail.cfm?id=2696453)。使用針對用戶訪問模式構建的數據庫。
https://queue.acm.org/detail.cfm?id=2696453
Poul-Henning Kamp,「這樣作並不正確」(https://queue.acm.org/detail.cfm?id=1814327)。你是否定爲本身已經掌握瞭如何處理服務器性能問題?再考慮一下。
https://queue.acm.org/detail.cfm?id=1814327
Alex Petrov(http://coffeenco.de/,@ifesdjeen (GitHub), @ifesdjeen (Twitter))是 Apache Cassandra 項目的提交者,也是存儲系統技術愛好者。他在多家企業從事數據庫、構建分佈式系統和數據處理流水線方面的工做。
查看英文原文:
https://queue.acm.org/detail.cfm?id=3220266