BP-Wrapper:無鎖競爭的緩存替換算法系統框架


BP-Wrapper:無鎖競爭的替換算法系統框架

最近看了一個golang的高性能緩存ristretto,該緩存可以很好地實現如下功能:

  1. Concurrent
  2. High cache-hit ratio
  3. Memory-bounded (limit to configurable max memory usage)
  4. Scale well as the number of cores and goroutines increases
  5. Scale well under non-random key access distribution (e.g. Zipf).

在官方的introducing-ristretto-high-perf-go-cache一文中提到了一個名為BP-Wrapper的(幾乎)無鎖競爭的框架,用於提升緩存的擴展性。本文就是該框架的論文。

譯自:bp_wrapper

概要

在高端數據庫系統中,多處理器環境中的並發級別隨着並行事務的增加以及多核處理器的引入而不斷攀升。這給緩存管理帶來了新的挑戰,如何在保證擴展性的同時滿足高並發數據處理的需求。如果緩存管理中的頁替換算法不支持擴展,則可能會嚴重降低系統的性能。大多數替換算法會使用鎖來保護數據結構,但並發訪問也會引起大量鎖競爭。通常的做法是通過修改替換算法來降低競爭,如使用時鍾算法來近似實現LRU替換。不幸的是,這種修改通常會破壞原始算法的緩存命中率。在低並發環境下可能不會存在(或可以容忍)這個問題,因此可能會導致該問題長時間不被重視。本論文不會在替換算法的高命中率和低鎖競爭之間進行權衡。在這里,我們提出了一個系統框架,稱為BP-Wrapper,該框架幾乎可以幫助所有替換算法消除鎖競爭,而無需對算法作任何改動。在BP-Wrapper中,我們使用批處理和預加載技術降低了鎖競爭,並獲得高命中率。8.2版本的PostgreSQL僅使用300行C代碼就實現了BP-Wrapper。與使用鎖競爭的替換算法相比,當運行TPC-C類型和TPC-W類型的負載時,它可以將吞吐量增提升兩倍。

I.簡介

現代數據庫管理系統通常會管理TB級別的數據,並在多處理器系統上並行處理成百上千條事務。這類數據庫系統的緩存管理要求能夠高效降低磁盤I/O操作所需要的時間,並能夠隨並發事務數量以及底層處理器數量的增加進行擴展。緩存管理的核心是數據替換算法,用來確定哪些數據頁應該被緩存到內存中,以高效響應上層事務處理線程對磁盤數據的請求。替換算法通常會維護復雜的數據結構來跟蹤線程對數據的訪問歷史,這樣就可以依賴數據結構中的原始信息來執行替換算法。在高端生產系統中,會有大量線程頻繁地並行訪問數據頁,這時會要求替換算法同時兼具高效性和可擴展性。

磁盤I/O操作變得越來越昂貴。為了最小化磁盤訪問,出現了多種替換算法以及針對數據庫、虛擬內存和I/O 緩存的實現,主要通過組織和管理深度頁訪問歷史來提升命中率。如LIRS [1]、2Q [2]、和 ARC [3]。這些算法通常會在每次I/O訪問時采取動作(命中或未命中緩存,以及對記錄數據訪問歷史的數據結構的一系列更新)。這些操作非常頻繁,因此通常被設計為簡單而高效的,避免造成訪問開銷。

為了保證數據結構的完整性,替換算法會以一種序列化的方式采取操作,來響應頁訪問。因此為了在多任務系統上實現這些算法,通常會用到同步鎖,換句話說,在采取動作前必須使用排他鎖進行保護。當一個事務處理線程持有鎖時,其他請求該鎖的線程必須使用busy-waiting(自旋鎖)或上下文切換的方式達到互斥效果。

在大型系統中,鎖競爭會導致嚴重的性能問題,針對該問題的研究已經持續了很多年(即[4], [5], [6])。在我們的實驗中,在16個處理器系統上,由於替換算法造成的鎖競爭使得吞吐量下降了2倍。兩種因素導致了性能降級:一個是頻繁地請求鎖,目前通過在所有事務請求線程中共享鎖來調節頁訪問,線程每次訪問頁時都會請求鎖;另一個因素是獲取鎖的代價,包括改變鎖狀態,busy-waiting,和/或上下文切換。與花費在鎖保護下的操作相比,獲取鎖的代價可能更高。隨着支持DBMS系統的多處理器上多核處理器的增加,上述兩個因素造成的性能降級也愈發嚴重。

由於替換算法設計者沒有關注鎖競爭造成的問題,因此在實際系統中,算法的好處(包括高命中率)可能會大打折扣,例如,一些普遍使用的DBMS系統(如postgreSQL)沒有采用高級的替換算法,相反訴諸於基於時鍾的近似LRU替換算法。其他DBMS系統(如Oracle Universal Server [7]和ADABAS [8])則使用分布式鎖來解決鎖競爭問題。

相比於對應的原始算法(分別對應LRU,LIRS或ARC),基於時鍾的近似算法,如CLOCK [9],CLOCK-PRO [10] 和CAR [11]等通常不會獲得高命中率。它們將緩存頁組織為環形列表,並使用引用位或引用計數來記錄每個緩存頁的訪問信息。當命中緩存中的一個頁時,基於時鍾的近似算法會設置引用位或增加計數,而不會修改環形列表本身。由於這些操作並不需要時鍾,因此緩存性能是可擴展的。然而,基於時鍾的算法只能記錄有限的歷史訪問信息,如是否訪問某個頁或對該頁的訪問次數,但無法知道訪問順序。歷史信息的缺失可能會影響命中率。很多成熟的替換算法並不會采用基於時鍾的數據結構,原因是無法使用時鍾結構來近似表達它們需要的信息。如SEQ[12]算法和DB2[13] 使用的緩存替換策略,它們需要知道緩存頁的訪問順序,並以此來檢測順序以及訪問順序/隨機訪問模式。

通常,可以使用分布式鎖降低鎖粒度( [4], [5], [6], [14])的方式來減少鎖競爭。但這種方式並不能有效地解決替換算法中的鎖問題。在分布式鎖中,會將緩存分成多塊,每一塊都使用一個本地鎖進行保護。使用輪詢或哈希方式將數據頁均勻分布到各塊。由於只有訪問相同塊的緩存才會用到相同的鎖,因此可以改善鎖競爭。但由於記錄的歷史信息位於本地的各個緩存塊,因此全局歷史信息的缺少可能會對替換算法的性能造成影響。例如,如果算法需要檢測訪問順序,但此時相同順序中的頁被分布到多個緩存塊中,此時無法保證性能優勢。

總之,現有的對DBMS系統的研究和開發都聚焦在如何在高命中率和低鎖競爭之間進行權衡。相比於如何在這兩個方面采取妥協,我們的目標是在保持高級替換算法性能優勢的同時,提供一種有效的框架來消除(大部分)替換算法中的鎖競爭。通過為每個DBMS事務處理線程維護一個小的FIFO隊列,我們的框架提供了兩種主要的、可以普遍應用於所有替換算法的擴展。一種是批量執行,通過批量的頁訪問來改善鎖競爭開銷;另一個是預加載,通過將替換算法需要的數據預加載到處理器緩存中來降低平均所加載時間。我們將這種引入批量(Batching)和預加載(Prefetching)的框架稱為BP-Wrapper。在8.2.3 版本的PostgreSQL 引入該框架后,通過消除TPCW 和TPCC負載中與緩存頁替換有關的(幾乎所有)鎖競爭,使吞吐量提升了兩倍。

本論文剩余的結構如下:在II章節中,我們簡單描述了典型數據庫系統中的替換算法和鎖扮演的角色。在III章節中,我們描述了BP-Wrapper。第IV章節針對BP-wrapper進行了全面評估。第V和VI章節總結了相關的工作。

II. DBMSs中的緩存管理背景

在一個DBMS系統中,它的緩存在內存空間中保存了DBMS中所有事務處理線程共享的固定大小的緩存頁。從磁盤讀取的數據頁會被緩存起來,以此避免(不久之后)重復獲取數據頁的I/O操作造成的開銷。緩存管理器會使用數據結構(如鏈接的列表或哈希表)來組織緩存頁的元數據。元數據包含緩存的數據頁的標識,頁狀態以及構成鏈接列表或哈希表的指針。

圖1展示了典型的緩存管理器。當一個線程請求數據頁時,該線程會查詢包含數據頁的緩存頁。通常會使用一個哈希表來加速查找。如果找到(命中)緩存頁,替換算法會執行一個操作來更新數據結構,以此反映頁訪問。例如,當請求一個緩存頁時,LRU替換算法會從LRU列表中移除緩存頁,並將其插入到列表末尾的MRU。然后返回緩存頁,結束請求。如果沒有在緩存中找到請求的數據頁(緩存未命中),替換算法會選擇一個犧牲頁,然后淘汰該頁中的數據,以此來為需要加載的數據騰出空間。LRU算法總是選擇LRU列表末尾的緩存頁作為犧牲頁。當數據頁讀入內存后,緩存頁會轉移到LRU列表的首部,並返回給請求。

因為緩存管理器是所有事務處理線程的請求經常使用的中央組件,因此必須以某種方式控制同時更新數據結構,以保證數據的完整性。DBMS使用排他鎖解決該問題。

image

圖1.典型的DBMS中的緩存管理器。虛線矩形中的緩存管理器可以同時查找哈希表,而陰影塊表示的替換算法則必須序列化執行

使用鎖並不會影響哈希表搜索的系統擴展性,因為: (1)在哈希表中,緩存頁的元數據均勻分布在哈希桶中。通過為每個桶提供一個鎖(而不是提供全局鎖)來控制對桶的訪問。為了保證查找時間,通常會使用大量桶,這樣就降低了多個線程訪問相同桶的概率。(2)如果多個線程不修改桶,則多個線程可以同時訪問該桶。哈希桶很少會發生變化,只有當發生發生未命中以及當兩個哈希桶因為同一個緩存未命中(一個桶保存犧牲頁,另一個保存新頁)時才會發生變化。在具有大容量內存的DBMS中,只有一小部分訪問會發生緩存未命中,因此我們不考慮哈希表查找中的鎖競爭場景。

相反,替換算法會因為鎖競爭而嚴重影響性能,因為:(1)一個替換算法會為它的數據結構使用一個單獨的鎖,這是一個集中式的訪問熱點。(2)大多數替換算法會在每次頁訪問時更新它們的數據結構,因此一個線程必須在每次頁訪問請求時獲取鎖,並執行替換算法操作。高度的鎖競爭可能會嚴重降低多處理器系統上DNMS系統的性能。

III. 使用BP-WRAPPER最小化鎖競爭

在一個替換算法中,與鎖使用有關的開銷分為兩部分,稱為獲取鎖開銷和鎖預熱開銷。獲取鎖開銷是指當請求的鎖被另一個線程持有時,線程本身阻塞所造成的時間開銷。取決於具體實現,這類開銷可能是處於busy-waiting鎖占用的CPU周期,和/或上下文切換占用的CPU周期,開銷的多少由鎖競爭的激烈程度而定,如果一個服務上有很多處理器,且並發處理大量事務,則獲取鎖所需要的時間可能會大大超過小型系統所需要的時間。換句話說,開銷直接與替換算法的擴展性關聯,因為每次頁訪問時都需要獲取一個鎖才能在鎖保護的共享數據結構中保存訪問歷史記錄。

鎖預熱開銷是指在處理器緩存中,為准備運行關鍵代碼段所需的數據造成的開銷,或從非關鍵代碼段(事務處理的代碼)進入關鍵代碼段(用於更新替換算法的共享數據結構的代碼)的過程中發生的處理器緩存未命中懲罰。當獲取到鎖后,處理器緩存中可能沒有描述鎖的數據以及關鍵代碼段需要訪問的數據集,因此在緩存預熱過程中可能會發生一系列緩存未命中。重點是在一個線程獲取到鎖,而其他線程等待該鎖時的未命中懲罰可能會被放大。遵循最小化關鍵代碼段開銷的原則,我們主要消除預熱開銷。

為了降低兩種潛在的鎖開銷,我們在BP-Wrapper中設計並集成了兩種方法,這兩種方法都由替換算法本身實現,這樣任何替換算法都可以直接通過BPWrapper獲得高擴展性。

A 使用批量技術降低鎖請求開銷

正如前面提到的,當一個線程進入關鍵代碼段后,在代表替換算法執行其操作前必須獲取鎖。通常不需要關心請求鎖造成的缺頁,因為與I/O操作造成的開銷相比,鎖請求開銷通常可以忽略不計,此外,其緩存未命中數也大大低於數據庫系統。主要的挑戰是大部分替換算法(特別是最近提出的算法[1], [2], [3])的命中率都非常低,每次頁請求時都會訪問鎖保護的數據(即使數據已經存在於緩存中),當鎖競爭激烈時,鎖請求所造成的開銷也會增加,並成為系統擴展的性能瓶頸。

現有數據庫系統中的替換算法在每次頁訪問時都需要獲取鎖。獲取鎖的總開銷會隨着訪問頻率的增加而增加(通常發生在高並發的數據庫系統中)。我們使用一種稱為批量的技術來分攤並降低多個頁訪問造成的開銷。基本的思路是,當頁訪問累積到一定數目后,周期性地請求鎖,然后在持有鎖的周期內執行相應的替換操作。為了解該技術的效果如何,我們在一個具有16個處理器的系統上(詳細的配置參見Section IV)測量2Q替換算法中的鎖請求以及一個線程處理一定數目的(針對鎖保護的數據結構的)頁訪問所占用的(鎖持有)時間(包括請求鎖產生的開銷)。批量大小范圍為1-64(即,獲取鎖前累計的頁訪問數目為1到64)。圖2展示了獲取鎖和持有鎖的時間隨批量大小變化的平均值。我們發現,雖然多個訪問涉及的關鍵代碼段的執行時間與訪問次數成正比,該測量結果展示了批處理技術的有效性,同時得出,當使用小批量時,如64,可以顯著降低獲取鎖的開銷。

image

圖2. 每次頁訪問涉及的鎖獲取和持有鎖時間的平均值隨批量數目的變化(1-64)。圖中的兩個軸都為對數標度,負載為DBT-1,處理器數目為16。

雖然每次頁訪問都需要操作鎖保護的數據,但根據替換算法的要求,通常不必在頁面訪問之后立即執行操作。替換算法有兩個特性為我們提供了使用批量技術顯著降低鎖請求頻率的機會。首先,延遲替換算法(如LRU棧或LIRS棧[1])操作數據結構的操作,這樣就不會影響線程從緩存中獲取正確的數據,因此也不會影響事務處理的正確性。其次,在具有百萬級別的頁的系統上(如我們實現的服務使用了64GB內存或上百萬個8KB的頁),延遲在鎖保護的數據中記錄最近的頁訪問信息的操作(如64個頁訪問)可能會對分頁算法的性能造成一些影響,此外,批量操作的執行順序不會因為采納批量技術而發生變化。

在批量技術中,我們為每個事務處理線程設置了一個FIFO隊列。當一個訪問與某個線程關聯時,會將本次的訪問信息記錄在該線程的隊列中。特別地,當一個線程請求的頁位於緩存中時,會在線程的FIFO隊列中記錄指向該頁的指針。圖3展示了使用批量技術的緩存管理器。當隊列滿或隊列中的訪問記錄達到預設的閾值時(稱為批量閾值),我們會獲取到鎖並以批量的方式為隊列中的所有訪問執行替換算法定義的操作,該過程稱為提交訪問記錄。在提交之后,隊列會變為空。通過FIFO隊列,一個線程可以允許訪問多個頁,而無需為頁替換算法請求鎖(或不存在獲取鎖開銷)。

在批量技術的設計中,一種替代方案是在多個線程間共享FIFO隊列。但最終我們選擇為每個線程配置獨立的隊列,原因如下:

  • 私有隊列可以更精確地保證相應線程的頁訪問順序。訪問順序對某些頁替換算法(如SEQ[12])來說至關重要,因為它們需要順序信息來檢測訪問模式。
  • 在私有FIFO隊列中記錄訪問信息降低了同步訪問的概率和整體開銷以及一致性成本,而這些開銷正是多線程共享FIFO隊列時,填充和清空隊列時所需要的。
image

圖3.使用批量技術的緩存管理器

圖4的偽代碼描述了批量技術,包括頁命中(hit())和未命中(miss())時的相關鎖操作。在偽代碼中,當出現頁命中時,會首先在隊列中記錄此次訪問(Queue[])。然后,有兩個條件會觸發提交過程,並執行實際的替換算法。第一個條件是一個隊列中有足夠數量的訪問(不能少於批量閾值),且可以獲取鎖(TryLock()成功)。由於獲取鎖的開銷通常比較高,因此不應該為少量的訪問付出此類開銷。TryLock()會嘗試獲取鎖。如果此時其他線程持有該鎖,該操作會失敗,但不會阻塞調用線程。反之調用線程會以非常低的開銷獲取鎖。雖然TryLock()的開銷小,但也不能在每個頁訪問時調用,否則會造成大量鎖競爭,並降低TryLock()獲取到鎖的機會。第二個條件是隊列滿。這種場景下,必須明確請求鎖(13行的Lock())。當兩種條件都不滿足時,替換算法會為隊列中的每個訪問記錄涉及的鎖保護的數據執行延遲簿記工作。以LRU算法為例,它會將每個訪問涉及的頁轉移到隊列末尾的MRU中。注意,偽代碼實際描述了一個使用批量技術的框架,該框架可以給所有算法使用,以高效訪問鎖保護的數據(替換算法本身獨立於批量技術)。現有的替換算法並沒有在提高其命中率上花費很多精力,必須做出一定修改來適應該技術,降低鎖開銷。與將算法轉換為其時鍾近似的算法相比,后者減少了鎖競爭,但在替換性能上做了一定妥協。

    /* a thread’s FIFO queue whose maximum size is S */
1   Page *Queue[S];
    /* the minimal number of pages in Queue[] to trigger a committing 
2   #define batch_threshold T
    /* current queue position to receive next page */
3   int Tail = 0;
    /* called upon a page hit */
4   void replacement_for_page_hit(Page *this_access) {
5       Queue[Tail] <-- this_access;
6       Tail = Tail + 1;
7       if (Tail >= batch_threshold)
            /* a non-blocking attempt to acquire lock */
8           trylock_outcome = TryLock();
9       if(trylock_outcome is a failure) {
10          if( Tail < S)
11              return;
12          else
13              Lock();
14      }
        /* A lock has been secured. Now commit the pages */
15      For each page P in Queue[]{
16          do what is specified by the replacement
                algorithm upon a hit on P;
17      }
18      UnLock();
19      Tail = 0;
20  }
    /* called upon a page miss */
21  void replacement_for_page_miss(Page *this_access) {
22      Lock();
23      for each page P in Queue[] {
24          do what is specified by the replacement
                algorithm upon a hit on P;
25      }
26      do what is specified by the replacement
            algorithm upon a miss on ’this_access’;
27      UnLock();
28      Tail = 0;
29  }

圖4. 獨立於替換算法的框架偽代碼,使用批量技術為算法提供了高效訪問鎖保護的數據的能力

B 使用預加載技術降低鎖預熱開銷

image

圖5. 使用預加載技術將緩存未命中懲罰轉移到鎖持有階段

我們使用預加載技術降低鎖預熱開銷(此為鎖持有時間的一部分)。在該技術中,我們會在請求鎖前讀取替換算法的關鍵代碼段所需要立即訪問的數據。以LRU算法為例,我們會讀取在向頁列表末尾的MRU移動時涉及的前/后指針,以及鎖數據結構字段。讀取的一個副作用是需要將數據加載到處理器緩存中,但消除了關鍵代碼段的緩存未命中懲罰。圖5中示了該技術的潛在好處。

在無鎖的共享數據上執行預加載操作並不會對替換算法中的全局數據結構的完整性造成影響。預加載(讀)操作只會將數據加載到處理器緩存中,不會修改任何數據。同時,如果預加載的數據在線程使用前已經被其他線程進行了修改,則處理器中的某些硬件機制會自動讓這些緩存失效,或使用最新的值進行更新,以保證數據的一致性。

IV. 性能評估

我們已經在8.2.3版本的postgreSQL數據庫系統上實現了PB-Wrapper框架,包括兩個降低鎖競爭的技術。先前版本的PostgreSQL使用的是LRU和2Q替換算法,但最終因為擴展性問題而被廢棄。從8.1版本開始,為了提升多處理器系統上緩存管理的擴展性,postgreSQL采用了時鍾替換算法。時鍾替換算法在訪問命中時不需要鎖,消除了鎖競爭,並提供了最佳的擴展性。

在評估的第一部分,我們只關注擴展性問題。評估結果得出,將高級替換算法(如2Q)和BP-Wrapper配合使用,可以獲得跟時鍾替換算法相同的擴展性。在實驗中,我們將緩存配置的足夠大來保存所有性能測試中的工作集,並對緩存進行預熱。這樣無論使用哪種替換算法都不會發生未命中的情況。使用不同緩存管理器的PostgreSQL系統之間的性能差異完全來自其實現的可擴展性上的差異,而非命中率。因此可擴展性越高,觀察到的性能越好。由於時鍾算法的擴展性最好,因此當系統擴展時,其性能最佳。后面我們會對實現的擴展性進行測試,衡量其性能與時鍾實現的差異。Section IV-D展示了測試結果。

在實驗的第一部分,我們主要如下問題:(1)在降低替換算法的鎖競爭上,那種技術更好,批量還是預加載?(2)與時鍾算法相比,使用這些技術降低了多少鎖競爭?(3)這些技術的性能受多處理器平台以及多核平台的影響?

目前存在很多高級替換算法可以在命中率方面提供(比時鍾算法更)出色的性能,但除了LRU之外,其他算法很難轉換為時鍾算法,如果無法轉換,這類算法就不適宜運行在高並發環境中。在評估的第二部分中可以看到,如果沒有采取降低鎖競爭的動作,在大型系統中,這類高級算法的在命中率上的性能優勢將會大打折扣。同時,評估也給出了我們的技術在幫助保持算法優勢的同時,提升了擴展性。

A. 被測系統

我們修改了8.2.3版本的postgreSQL,使用2Q算法(提供高命中率的高級替換算法)替換了原來的時鍾算法。我們將修改后的系統稱為postgreSQL-2Q或簡稱為pg2Q(未針對較低的鎖爭用進行優化),在比較中作為基線系統。然后使用BP-Wrapper框架增強該基線系統。我們分別啟用了批量技術和預加載技術,並將啟動這些技術的系統分別命名為pgBatching和pgPref,在另一個系統上同時啟用批量技術和預加載技術,並將該系統命名為pgBat-Pre。原始的postgreSQL 8.2.3稱為pcClock。表T對這些系統進行了概括。我們還使用LIRS [1] 和 MQ [15]替換算法分別替代了圖標中最后四個使用2Q算法的系統。在實驗中,我們不會觀察使用這些算法的系統與對應的使用2Q算法的系統之間的性能差異。

image

表I

B. 實驗問題

僅需要對基線系統作有限修改就可以實驗批量和預加載。我們在postgreSQL 8.2.3中添加的代碼不超過300行。大部分修改都位於一個單獨的文件中(src/backend/storage/buffer/freelist.c),該文件包含替換算法的源碼。在實現中,FIFO隊列中的每一項都包含兩個字段:一個指向緩存頁的元數據(BufferDesc structure)的指針,另外一個保存BufferTag(用於區分數據頁)。在表項提交前,我們會將表項中的BufferTag與緩存頁的元數據中的BufferTag進行比較,保證該數據頁是有效的或未被淘汰。如果緩存中存在有效的數據頁,我們會在頁訪問時,運行替換算法來更新該數據結構。在2Q算法中,如果頁的結構是Am列表,更新后的頁會轉移到列表末尾的MRU中;在LIRS算法中,它會在LIR或HIR列表中轉移;在MQ算法中,它會在多個FIFO隊列中轉移。

C. 實驗配置和負載

我們會在傳統的單核SMP平台和多核平台上進行實驗。單核SMP平台使用的是一個 SGI Altix 350 SMP服務器,使用16個1.4GHz的Intel Itanium 2處理器,64GB內存。存儲為一個位於IBM FAStT600 turbo存儲子系統上的2TB大小的LUN,該LUN包含9個250GB的SATA磁盤,使用8+PRAID5進行配置。操作系統為 Red Hat Enterprise Linux AS release 3(SGI ProPack 3 SP6);多核平台是一個DELL PowerEdge 1900服務器,含2個2.66GHz的四核Xeon X5355處理器。為了方便,我們將每個計算核認為是一個處理器。這樣PowerEdge 1900服務器有8個計算核,或8個處理器。內存大小為16GB。存儲為使用5個15K RPM SCSI磁盤的RAID5陣列,操作系統為Red Hat Enterprise Linux AS release 5。

我們使用DBT-1測試套件和來自OSDL數據庫測試套[16]的DBT2測試套件,以及一個構造的基准TableScan對系統進行測試。DBT-1模擬web用戶在一個在線書店瀏覽並下單的行為,它生成具有與(1.7版本的)TPC-W基准規范相同特征的數據庫工作負載[17]。數據庫會生成100,000個條目,以及290萬個客戶。DBT2衍生自(5.0 [18]版本的)TPC-C規范,提供在線事務處理(OLTP)負載。在實驗中,我們將數倉的數目設置為50,TableScan會模擬順序掃描(數據庫的常用操作之一)。它會執行並行請求,每個請求都會掃描整張表,每張表包含800,000行,每行大小為128個字節。

在實驗中,我們會通過設置postgreSQL后端進程的CPU親和性掩碼來變更postgreSQL使用的處理器數目,即負責PostgreSQL處理事務的線程。由於PostgreSQL的后端進程會在因為競爭而無法獲得鎖時阻塞自身並喚醒其他處理器。因此我們會通過設置postgreSQL的后端進程數大於處理器數來使處理器保持工作。實驗中,我們將FIFO隊列數設置為64,pgBatching 和 pgBat-Pre的批量閾值為32。

D. 擴展性實驗

在實驗中,我們評估了5個不同的postgreSQL分別在3種負載下的擴展性(在Altix 350服務器上,我們將處理器數量從1增加到16。在PowerEdge 1900服務器上,我們將處理器數量從1增加到8).為了消除負載運行過程中的缺頁,我們調整了共享緩存的大小,確保內存中總是持有整個負載集。我們會采集事務的吞吐量(每秒的事務)以及平均響應時間。對於系統pg2Q、pgBatching、pgPref和pgBat-Pre,我們還會計算平均鎖競爭。當無法即時滿足一個鎖請求,並發生上下文切換時,就產生了一個鎖競爭。在負載運行過程中,平均鎖競爭定義為每毫秒內的訪問頁產生的鎖競爭數。圖6和圖7中展示了不同系統下,三個負載的吞吐量、平均響應時間和平均鎖競爭。

然后增加處理器的數目,與預期相符,pgClock的吞吐量隨處理器的數目線性增加,且DBT-1和TableScan負載下的平均響應時間適度增加。但在DBT2負載下,pcClock的吞吐量亞線性增加,而平均響應時間則大大增加。這是因為產生了鎖競爭(如發生預寫式日志(Write-Ahead-Logging)活動時),使鎖競爭隨處理器的數目增加而愈發激烈。

當處理器數目小於4時,系統pg2Q可以維持其擴展性。在Altix 350 服務器上,DBT-1和TableScan的處理器的數目大於8,或DBT-2的處理器大於4時,吞吐量達到飽和狀態,並且在進一步增加處理器時,平均響應時間也會大大增加。對於TableScan負載,當處理器的數目從8增加到16后,其吞吐量下降了12.7%。當使用16個處理器時,DBT1, DBT-2 和 TableScan負載下的吞吐量分別為pcClock的61.1%、56.5% 和 66.5%,其平均響應時間分別為pcClock的1.6、1.5 和 1.8倍。通過檢查平均鎖競爭的曲線,我們發現pg2Q每毫秒頁訪問的鎖競爭數目最多,並隨着處理器數目快速上升(注意,數字以對數刻度顯示)。因此這些實驗表明鎖競爭是系統性能降級的元凶。

在PowerEdge 1900服務器上,我們觀察到類似的現象。TableScan負載下吞吐量更早地達到飽和狀態(或當處理器的數目達到4)。平均鎖競爭表明在多核的PowerEdge 1900上鎖競爭要比Altix 350更加激烈(特別是針對TableScan負載的基准測試)。當使用8個處理器時,PowerEdge 1900上的3個負載的平均鎖競爭分別為Altix 350上相應負載的產生的平均鎖競爭的74.4%、18.5% 和 270.2%。因此,相比 Altix 350,鎖競爭對PowerEdge 1900的性能影響更大。使用8個處理器時,PowerEdge 1900上的系統pg2Q的吞吐量為pgClock吞吐量的37.9%、52.1% 和 57.2%,是Altix 350上的系統pgClock的吞吐量的30.1%、51.5% 和 32.6%。類似地,當使用系統pg2Q時,在PowerEdge 1900上鎖競爭提高的工作負載的平均響應時間百分比要大於在ALTIX 350上提升的工作負載的平均響應時間百分比。

PowerEdge 1900上的鎖競爭更激烈的原因歸咎於系統使用的處理器。PowerEdge 1900上的Xeon X5355處理器具有緩存預加載模塊,該模塊可以通過預測,將數據加載到最后一級緩存,從而加速順序內存訪問。而Itanium 2處理器則沒有這樣的硬件支持,因此在PowerEdge 1900服務器上,預加載模塊可以加快關鍵段外的計算(通常順序訪問內存),但被鎖保護的替換算法的操作通常會隨機訪問內存,幾乎不能被(預加載模塊)加速。因此PowerEdge 1900 服務器上的鎖競爭更激烈,花在關鍵部分上的時間比例要大於 Altix 350。

與pg2Q相比,pgPref在Altix 350系統上的平均鎖競爭減少33.7%至82.6%,在PowerEdge 1900上的平均鎖競爭減少20.8%至87.5%。這是因為預加載降低了鎖持有時間,並相應增加了獲取到鎖的機會。最終,pgPerf的吞吐量比pg2Q高26.1%,而平均響應時間則小 25.2%。我們注意到在Altix 350上運行預加載技術比PowerEdge 1900更有效,這是因為在X5355處理器上運行長流水線且無序的事務,增加了處理緩存未命中的負擔。基於此觀察,我們可以預見預加載技術在大型多核處理器系統(如Sun Niagara 2處理器和Azul vega處理器)上可以更有效地降低鎖競爭。這些處理器在一個芯片內有很多順序的用於支持並發線程計算核。因此相比於具有少量無序計算核的處理器(如Xeon 5355處理器),緩存未命中對此類系統的性能影響更大。

image

圖6. Altix 350上,5種postgreSQL系統(pgClock、pg2Q、pgPref、pgBatching 和 pgBat-Pre)在DBT-1、DBT-2 和 TableScan負載下的吞吐量、平均響應時間和平均鎖競爭(處理器數從1增加到16)。注意吞吐量和平均鎖競爭曲線的Y軸為對數刻度。僅使用一個處理器時,我們沒有展示平均鎖競爭,這是因為這種情況下,鎖競爭非常少,無法在圖中展示出來。

在平均鎖競爭的曲線中可以看到,系統pgPref的擴展性要弱於pg2Q,這是因為批量技術無法顯著降低鎖競爭,特別 是在使用大於4個處理器時。例如,當在 Altix 350服務器上使用兩個處理器時,在3個負載下,平均降低了pg2Q上82.4%的平均鎖競爭,當處理器數目為4時,降低的鎖競爭的百分比減少到60.2%;當處理器數目為8時,降低的鎖競爭的百分比減少到46.3%;當處理器數目為16時,降低的鎖競爭的百分比減少到38.6%。pgPref中的批量技術降低了鎖競爭,並提升了系統的吞吐量。但隨着吞吐量的增加,請求的鎖也愈發頻繁,抵消了降低鎖競爭帶來的好處。在處理器數目較多的系統上,該現象更加明顯。

在實驗中,系統pgBatching展示了與pgClock幾乎相同的擴展性,為擴展性最佳的算法。當處理器數目增加時,它的吞吐量曲線和平均響應時間曲線與pgclock非常契合。在平均鎖競爭曲線圖中可以看到,系統pgBatching將鎖競爭數從9000多降低了127倍,提升了擴展性。我們還注意到,使用16個處理器的pgBatching的平均鎖競爭要遠低於使用2個處理器的pgPref和pg2Q。

image

圖7. DELL PowerEdge 1900上,5種postgreSQL系統(pgClock、pg2Q、pgPref、pgBatching 和 pgBat-Pre)在DBT-1、DBT-2 和 TableScan負載下的吞吐量、平均響應時間和平均鎖競爭(處理器數從1增加到8)。注意吞吐量和平均鎖競爭曲線的Y軸為對數刻度。僅使用一個處理器時,我們沒有展示平均鎖競爭,這是因為這種情況下,鎖競爭非常少,無法在圖中展示出來。

同時使用批量和預加載技術時,相比pgBatching,系統pgBat-Pre可以進一步降低鎖競爭。然而,如圖所示,降低鎖競爭並不會提高吞吐量或降低平均響應時間,這是因為pgBatching的平均鎖競爭非常少,因此對性能的影響范圍也不大。當使用16個處理器時,在每100萬個頁訪問中,pgBatching和pgBatPre的鎖競爭在400個(或少於400個)左右。當繼續增加處理器的數目時,在常見的多核處理器中,鎖競爭會變得更嚴重,這種情況下,pgBat-Pre可以幫助進一步降低鎖競爭,提升系統性能。

E. 參數敏感性研究

系統pgBatching使用一個小的FIFO隊列來記錄每個postgreSQL后端進程的訪問歷史來分攤鎖請求開銷。批量技術中有兩個主要參數,一個是FIFO隊列的大小,另一個是批量閾值,或觸發提交時隊列中的最少元素個數。在本節中,我們將會在包含16個處理器的 Altix 350系統上對兩個參數的敏感性進行研究。
首先在保證批量閾值為隊列大小一半的前提下,逐步將隊列大小從2增加到64,並使用3個負載運行paBatching,表II中展示了對應的吞吐量和平均鎖競爭。然后將隊列大小固定為64,並使用3個負載運行paBatching,表III中展示了對應的吞吐量和平均鎖競爭。

表 II .當隊列長度從2增加到64,且批量閾值為1/2的隊列長度時,在系統pgBatching在負載DBT-1, DBT-2 和 TABLESCAN下的吞吐量和平均鎖競爭

image

表 III. 批量閾值從1增加到64時,DBT-1,DBT-2和TABLESCAN負載下系統pgBatching的吞吐量和平均鎖競爭

image

增加隊列大小可以降低平均鎖競爭並提升吞吐量,因為此時可以一次性提交更大批的訪問歷史。當隊列大小從2增加大8時,DBT-1的平均鎖競爭降低了477倍,DBT-2降低了32倍,TableScan降低了130倍,對應的吞吐量則大幅上升。當隊列大小超過8時,增加隊列大小仍然可以降低平均鎖競爭,但很難將這部分提升轉換為吞吐量(鎖競爭已經被大幅降低了)。同時,我們發現,即使隊列非常小(2),Pgbatching也優於PGPref。
直觀上,選擇一個小的批量閾值可以讓postgreSQL后端進程更容易獲取鎖(無需在FIFO隊列滿之前多次阻塞調用TryLock()獲取鎖),因此可以降低鎖競爭的概率。但表III的數據顯示出,只有當批量閾值的數目小於32時才會出現這種趨勢。當將批量閾值從1增加到32,我們發現其平均競爭降低了,且吞吐量增加。這是因為較低的閾值可以提前觸發提交動作(或可以以小批量提交訪問歷史),並增加線程通過TryLock()獲取鎖的概率。

image

圖8. PowerEdge 1900服務器上,在3個postgreSQL系統(pgClock, pg2Q 和pgBat-Pre)中使用DBT-1和DBT-2負載時的命中率和歸一化吞吐量(處理器數目為8)。吞吐量為與pgClock系統的歸一化值。

當批量閾值超過32后,后端進程批量提交的訪問歷史會大於32(個頁訪問),通過這樣方式有效分攤獲取鎖開銷。上面展示了非阻塞獲取鎖(TryLock())的優勢。使用相對較小的閾值,在FIFO隊列滿之前會多次調用TryLock(),且在不阻塞自身的前提下獲取到鎖的概率也非常高。因此,我們可以看到,當批量閾值從32增加到64時,平均鎖競爭增加,吞吐量下降。
當批量閾值設置為隊列大小(64)時,后端進程獲取TryLock()的概率將非常小。最終,可以看到平均鎖競爭的大幅上升以及吞吐量的下降(盡管使用大批量分攤了獲取鎖的成本)。實驗表明為了獲得TryLock()的優勢,有必要讓批量閾值足夠小於隊列的大小。

F. 總體性能

本節中,我們將評估當PowerEdge 1900上的處理器數為8時,3個系統pgClock, pg2Q 和 pgBat-Pre的整體性能。我們已經評估了在緩存大小等於負載數據大小情況下各種系統的擴展性。實驗結果表明,結合批量和預加載技術,可以有效降低鎖競爭。然而,真實系統中,緩存大小通常遠小於數據大小。因此,通過提升命中率來降低I/O操作的開銷對整體性能來說非常重要。在本次實驗中,我們將緩存大小從32MB增加到1024MB,並讓系統發起直接I/O來繞過操作系統的緩存。DBT-1 和 DBT2的數據量分別為6.8GB 和 5.6GB,因此緩存無法滿足所有的訪問。
圖8展示了3個系統中的命中率吞吐量。當內存比較小時(小於256MB),系統是I/O密集型的。通過維持更好的高命中率,系統pg2Q和pgBat-Pre產生了比系統pgClock更高的吞吐量。但隨着內存大小的增加,系統的整體性能主要取決於其擴展性,系統pg2Q的命中率優勢則不那么明顯。當緩存大小增加到1GB時,整體性能將低於系統pgClock。同時,系統pgBat-Pref在提升擴展性的同時,維持了其性能優勢。同時也可以注意到,pg2Q個pgBatPref的命中率曲線重疊率非常高。表明,我們的技術並沒有影響命中率。

V. 相關工作

在系統和數據庫社區中,已經積極開展了應對鎖競爭引起的性能降級的研究。通常可以通過如下方式降低鎖競爭:

A. 使用分布式鎖降低鎖的粒度

降低鎖粒度是降低鎖競爭的常用方法。使用多個細粒度鎖來替換全局鎖可以移除單點競爭。Oracle Universal Server [7], ADABAS [8] 和 Mr.LRU 的替換算法[19]使用多個列表來管理緩存,每個列表由獨立的鎖保護。當一個新頁進入緩存后,Oracle Universal Server會將該頁插入無鎖列表的首部,而ADABAS會以輪詢的方式選擇一個列表。它們都允許從一個列表中淘汰頁,並將其插入另一個列表。因此很多替換算法,如2Q[2]和LIRS[1]將無法工作。為了解決該問題,Mr.LRU會通過哈希選擇列表,保證每次從磁盤加載一個頁時,該頁都能進入相同的列表。
(包括Mr.LRU使用的)分布式鎖方式有如下嚴重缺陷:(1)無法用於實現檢測訪問順序的替換算法,如SEQ[12],因為相同順序的頁可能被分布到多個列表中。(2)雖然可以將頁均勻地分布到多個列表中,但對緩存頁的訪問則可能並不均勻。具有熱點頁的列表,如頂級索引或用於並行連接(parallel join)的小型表中的頁,仍然會受制於鎖競爭。(3)為了降低競爭,需要將緩存切分為成百上千個列表。這樣每個列表的大小要遠小於緩存的大小。使用小型列表時,需要對這些頁進行特殊處理,防止被淘汰,如臟頁和索引頁就有可能被永久從緩存中淘汰。相比之下,我們的框架能夠實現所有的替換算法,而無需切分緩存。

B. 降低鎖持有時間

從前面可知,鎖的持有時間越長,競爭越嚴重。降低鎖持有時間是另一種有效降低鎖競爭的方式。
Charm中使用的TSTE(二階段事務執行)策略將磁盤I/O和鎖獲取分成兩個互斥的階段,從而保證一個事務所需要的所有數據頁在鎖定前就已經存在於內存中。通過這種方式TSTE將磁盤事務處理系統中的鎖競爭延遲降低到與內存事務系統相同的水平。
在2.4版本的Linux內核中,調度器會遍歷使用自旋鎖保護的全局隊列中的task結構體,並從中選擇一個任務運行。論文[21]表明,在遍歷過程中,硬件緩存中的不必要的沖突未命中可能會增加遍歷時間,進而惡化自旋鎖上的競爭。通過在內存中仔細布局task結構,減少遍歷所需的時間,可以避免大部分沖突未命中,並且可以大大減少鎖競爭。相比之下,我們的框架可以通過預加載技術減少在執行替換算法時,用在降低硬件緩存未命中中的鎖持有時間,以及在批量模式下,為多個訪問執行替換算法時的鎖持有時間。

C. Wait-Free 同步和事務內存

由於鎖同步會導致性能降級和優先級翻轉等問題。wait-free同步[22]通過確保一個事務能夠在有限的步驟內完成訪問共享資源的操作(無需關心其他事務的執行進度)來解決這些問題。然而,很難設計采納這種技術的程序,同時有可能找不到實現了wait-free的算法。再者,wait-free同步要求硬件支持原子語義。例如,雙邊隊列的wait-free實現要求實現Double CompareAnd-Swap (DCAS),而大多數處理器不支持這類實現。

事務內存是另一種解決這類鎖競爭的方式。在事務內存中,一個事務被認為是基於共享資源的一系列操作。由實現事務內存的硬件[23]或軟件[24]保證執行的原子性。它通過啟用樂觀並發控制[25],[26]來提升系統的擴展性。雖然硬件事務內存還沒有實現,但已經有各種軟件事務內存(STM)實現。對STM和鎖同步的性能比較發現,只有當事務處理[27]不會頻繁修改共享資源時,STM才會優於鎖。因為替換算法中的數據結構可以(基於每個頁訪問)被頻繁修改,事務內存幾乎無法提升替換算法的擴展性。相比之下,可以在軟件中簡單實現BP-Wrapper中的批量和預加載技術,且不需要硬件支持。

VI. 總結和后續工作

在本論文中,我們解決了DBMS系統中高級替換算法由於鎖競爭導致的擴展性問題。我們提出了一種有效且可擴展的框架,BP-Wrapper,該框架中的批量和預加載技術可以在不修改算法的前提下適用於所有替換算法。由於不需要對算法進行修改,因此可以保證原始替換算法的性能優勢,並節省人力。該框架的唯一開銷是為每個事務處理線程分配一個小型的FIFO隊列,該隊列中保存了線程最近的訪問信息。
我們已經在postgreSQL 8.2.3中實現了該框架,並使用一個TPC-W-like負載,一個TPC-C-like負載以及一個組合負載進行測試。我們的性能評估表明,與未修改的替換算法(如LRU和2Q)相比,BP-Wrapper可以增加近兩倍的系統吞吐量,以及獲得如同在命中時不使用鎖的算法一樣的擴展性(如時鍾算法)。后續,我們計划在大型系統上(特別是具有更多個多核處理器的系統)以及使用真實負載的生產環境上評估BP-Wrapper。

引用

[1] S. Jiang and X. Zhang, "LIRS: an efficient low inter-reference recency set replacement policy to improve buffer cache performance,"in SIGMETRICS '02: Proceedings of the 2002 ACM SIGMETRICS international conference on Measurement and modeling of computer systems, 2002, pp. 31–42.

[2] T. Johnson and D. Shasha, "2Q: A low overhead high performance buffer management replacement algorithm," in VLDB ’94: Proceedings of the 20th International Conference on Very Large Data Bases. Morgan Kaufmann Publishers Inc., 1994, pp. 439–450.

[3] N. Megiddo and D. S. Modha, ”ARC: A self-tuning, low overhead replacement cache,“ in FAST ’03: Proceedings of the 2nd USENIX Conference on File and Storage Technologies, 2003, pp. 115–130.

[4] T. E. Anderson, “The performance of spin-lock alternatives for sharedmemory multiprocessors,” in IEEE Transactions on Parallel and Distributed Systems, vol. 1, no. 1, 1990, pp. 6–16.

[5] J. M. Mellor-Crummey and M. L. Scott, “Algorithms for scalable synchronization on shared-memory multiprocessors,” ACM Trans. Comput. Syst., vol. 9, no. 1, pp. 21–65, 1991.

[6] X. Zhang, R. Castaneda, and E. W. Chan, “Spin-lock synchronization on ˜ the butterfly and KSR1,” IEEE Parallel Distrib. Technol., vol. 2, no. 1, pp. 51–63, 1994.

[7] W. Bridge, A. Joshi, M. Keihl, T. Lahiri, J. Loaiza, and N. MacNaughton, “The oracle universal server buffer manager,” in VLDB’97, Proceedings of 23rd International Conference on Very Large Data Bases, August 25-29, 1997, Athens, Greece, M. Jarke, M. J. Carey, K. R. Dittrich, F. H. Lochovsky, P. Loucopoulos, and M. A. Jeusfeld, Eds. Morgan Kaufmann, 1997, pp. 590–594.

[8] H. Schoning, “The ADABAS buffer pool manager,” in ¨ VLDB ’98: Proceedings of the 24rd International Conference on Very Large Data Bases. Morgan Kaufmann Publishers Inc., 1998, pp. 675–679.

[9] F. J. Corbato, “A paging experiment with the multics system,” in In Honor of Philip M. Morse, Feshbach and Ingard, Eds. Cambridge, Mass: MIT Press, 1969, p. 217.

[10] S. Jiang, F. Chen, and X. Zhang, “CLOCK-Pro: An effective improvement of the CLOCK replacement,” in Proceedings of the Annual USENIX Technical Conference ’05, Anaheim, CA, 2005.

[11] S. Bansal and D. S. Modha, “CAR: Clock with adaptive replacement,” in FAST ’04: Proceedings of the 3rd USENIX Conference on File and Storage Technologies, 2004, pp. 187–200.

[12] G. Glass and P. Cao, “Adaptive page replacement based on memory reference behavior,” in SIGMETRICS ’97: Proceedings of the 1997 ACM SIGMETRICS international conference on Measurement and modeling of computer systems, 1997, pp. 115–126.

[13] “DB2 for z/OS: DB2 database design,” 2004, URL: http://www.ibm.com/developerworks/db2/library/techarticle/dm0408whitlark/index.html.

[14] M. Blasgen, J. Gray, M. Mitoma, and T. Price, “The convoy phenomenon,” SIGOPS Oper. Syst. Rev., vol. 13, no. 2, pp. 20–25, 1979.

[15] Y. Zhou, J. Philbin, and K. Li, “The multi-queue replacement algorithm for second level buffer caches,” in Proceedings of the General Track: 2002 USENIX Annual Technical Conference, 2001, pp. 91–104.

[16] The Open Source Development Laboratory, “OSDL - database test suite,” URL: http://old. linux-foundation.org/lab activities/kernel testing/ osdl database test suite/.

[17] Transaction Processing Performance Council, “TPC-W,” URL: http://www.tpc.org/tpcw.

[18] Transaction Processing Performance Council., “TPC-C,” URL: http://www.tpc.org/tpcc.

[19] W. Wang, “Storage management for large scale systems,” Ph.D. dissertation, Department of Computer Science, University of Saskatchewan, Canada, 2004.

[20] L. Huang and T. cker Chiueh, “Charm: An I/O-driven execution strategy for high-performance transaction processing,” in Proceedings of the General Track: 2002 USENIX Annual Technical Conference, 2002, pp. 275–288.

[21] S. Yamamura, A. Hirai, M. Sato, M. Yamamoto, A. Naruse, and K. Kumon, “Speeding up kernel scheduler by reducing cache misses,” in Proceedings of the FREENIX Track: 2002 USENIX Annual Technical Conference, 2002, pp. 275–285.

[22] M. Herlihy, “Wait-free synchronization,” ACM Trans. Program. Lang. Syst., vol. 13, no. 1, pp. 124–149, 1991.

[23] M. Herlihy and J. E. B. Moss, “Transactional memory: Architectural support for lock-free data structures,” in Proceedings of the 20th Annual International Symposium on Computer Architecture, 1993, pp. 289–300.

[24] N. Shavit and D. Touitou, “Software transactional memory,” in PODC ’95: Proceedings of the fourteenth annual ACM symposium on Principles of distributed computing, 1995, pp. 204–213.

[25] H. T. Kung and J. T. Robinson, “On optimistic methods for concurrency control,” ACM Trans. Database Syst., vol. 6, no. 2, pp. 213–226, 1981.

[26] D. Gawlick and D. Kinkade, “Varieties of concurrency control in IMS/VS fast path,” IEEE Database Eng. Bull., vol. 8, no. 2, pp. 3–10, 1985.

[27] B. Saha, A.-R. Adl-Tabatabai, R. L. Hudson, C. C. Minh, and B. Hertzberg, “McRT-STM: a high performance software transactional memory system for a multi-core runtime,” in PPoPP ’06: Proceedings of the eleventh ACM SIGPLAN symposium on Principles and practice of parallel programming, 2006, pp. 187–197.

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM