f2fs GC 解密

名詞解釋

cleaning 是append文件系統中常用術語,和trim有聯繫,但也需要注意他們之間的區別和聯繫:

cleaning: LFS 刪去無效數據的過程
trim: SSD 內部刪把dirty塊設置成invalid,以備後續使用的過程

背景

鑑於之前評測的nvme SSD場景下 f2fs 整體性能表現出色,有必要分析其背後的根本原因。f2fs作爲一種append模式的文件系統,
cleaning流程的設計和實現起着影響性能的重要的作用。爲此有必要深入瞭解f2fs的GC的原理和實現。

f2fs GC的整體介紹

f2fs GC模塊和其他模塊的交互(接口)

  • mkfs.f2fs 時的操作

在磁盤格式化成f2fs的時候,會做一遍全盤trim。

  • trim API
    f2fs提供了標準的POSIX trim接口,供外部直接觸發trim。

f2fs GC模塊內部的原理

觸發時機

                /*
                 * [GC triggering condition]
                 * 0. GC is not conducted currently.
                 * 1. There are enough dirty segments.
                 * 2. IO subsystem is idle by checking the # of writeback pages.
                 * 3. IO subsystem is idle by checking the # of requests in
                 *    bdev's request list.
                 *
                 * Note) We have to avoid triggering GCs frequently.
                 * Because it is possible that some segments can be
                 * invalidated soon after by user update or deletion.
                 * So, I'd like to wait some time to collect dirty segments.
                 */

後臺GC機制的觸發時機:

        /*
         * Background GC is triggered with the following conditions.
         * 1. There are a number of invalid blocks.
         * 2. There is not enough free space.
         */

響應機制

  1. foreground cleaning

當free sections不夠的時候:會觸發 foreground cleaning

  1. background cleaning
    f2fs文件系統有一個內核線程會週期地檢查並執行cleaning 過程

src的選擇

  1. greedy 策略

每次選擇valid blocks 數量最小的section, 用在foreground cleaning,
目的是爲了減小latency(因爲需要搬遷的valid數據量少);

  1. cost-best 策略
    每次不僅僅考慮section的利用率,還考察其age。 f2fs 根據一個section內的segments
    的平均age來決定其age。每個segment的age可以在SIT更新的時候記錄下來。

分情況進行處理:

  1. 如果是foreground cleaning:
    從parent node 的索引裏獲取一個新的free log,然後把上面選定的section中valid blocks數據搬移到free log裏面去;

  2. 如果是background cleaning:

並不是馬上觸發IO。而是把上面選定的section中valid blocks數據讀到page cache,並且設置爲dirty,這樣能合併小塊的寫,
然後讓page cache週期刷到新的空閒塊上。

f2fs GC的特色
上面操作做完之後,上面選定的section被標記爲pre-free, 只有等到checkpoint做完之後,上面的狀態才變爲free,可以被再次
分配出去。原因如下:如果沒有做checkpoint 就直接把那個塊釋放掉,如果它被別人使用之後突然掉電之後再次拉起來,之前的數據會丟。

  1. f2fs 如何判斷用戶IO是否繁忙
    思路:根據一段時間內:用戶提交的請求;page cache刷回的數據的次數

不同策略下cost 的比較方法

爲了統一cost-best 和greedy 策略,f2fs 爲這兩種方法都提供了get_cost接口,分別實現如下:

優先選擇cost成本低的segment去做GC:

greedy 算法

cost 是segment中有效數據block的數量


static inline unsigned int get_gc_cost(struct f2fs_sb_info *sbi,
                        unsigned int segno, struct victim_sel_policy *p)
{
        if (p->alloc_mode == ×××)
                return get_seg_entry(sbi, segno)->ckpt_valid_blocks;

        /* alloc_mode == LFS */
        if (p->gc_mode == GC_GREEDY)
                return get_valid_blocks(sbi, segno, true);
        else
                return get_cb_cost(sbi, segno);
}

cost-best 算法

計算比較複雜: 基於時間局部性原理,最近更新的可能馬上還有更新,爲此降低其做GC的優先級,對應提高其做GC cost;反之,
對應降低其做GC cost。


static unsigned int get_cb_cost(struct f2fs_sb_info *sbi, unsigned int segno)
{
        struct sit_info *sit_i = SIT_I(sbi);
        unsigned int secno = GET_SEC_FROM_SEG(sbi, segno);
        unsigned int start = GET_SEG_FROM_SEC(sbi, secno);
        unsigned long long mtime = 0;
        unsigned int vblocks;
        unsigned char age = 0;
        unsigned char u;
        unsigned int i;

        for (i = 0; i < sbi->segs_per_sec; i++)
                mtime += get_seg_entry(sbi, start + i)->mtime;
        vblocks = get_valid_blocks(sbi, segno, true);

        mtime = div_u64(mtime, sbi->segs_per_sec);
        vblocks = div_u64(vblocks, sbi->segs_per_sec);

        u = (vblocks * 100) >> sbi->log_blocks_per_seg;

        /* Handle if the system time has changed by the user */
        if (mtime < sit_i->min_mtime)
                sit_i->min_mtime = mtime;
        if (mtime > sit_i->max_mtime)
                sit_i->max_mtime = mtime;
        if (sit_i->max_mtime != sit_i->min_mtime)
                age = 100 - div64_u64(100 * (mtime - sit_i->min_mtime),
                                sit_i->max_mtime - sit_i->min_mtime);

        return UINT_MAX - ((100 * (100 - u) * age) / (100 + u));
}

上面gc 策略計算公式 UINT_MAX - ((100 * (100 - u) * age) / (100 + u)) 的說明如下:

u:    單個block內部的使用率(有效塊的數量佔block內部所有塊的比例)
age:  當前block的訪問時間距離所有block最近一次訪問的時間間隔(sit_i->max_mtime -  mtime),在所有block最久遠和最近訪問時間間隔(sit_i->max_mtime - sit_i->min_mtime)中
的分爲佔比百分數
cost :  UINT_MAX - ((100 * (100 - u) * age) / (100 + u))
u固定的情況下,距離最近訪問的時間越久遠(age越大),cost 值越小,越可能被選中;
age固定的情況下,上述公式等價於 2u/(1+u),意味着 騰挪數據越陳本越低的block 越優先被選取。

參考:
Greedy算法

固件需要維護一張Block屬性表,記錄每個Block當前的Valid Page數量。假設每次GC處理8個Block,查表挑出Valid Page最少的8個Block進行GC,這樣做的好處是複製Valid Page的開銷最小。

Cost-Benefit算法

u代表valid page在該Block中的比例,age代表該Block距離最近一次修改的時間。

1-u是對這個Block進行GC以後能夠獲得Free Page的數量

2u是對這個Block進行GC的開銷,讀取Valid Page(1個u)然後寫入到新的Block(再1個u)

(1-u)/2u可以理解爲投入產出比

固件需要維護的Block屬性表裏,需要記錄每個Block最後一次被寫入的時間,GC時選擇更久沒有被修改的Block(冷數據)

該策略就是選擇投入產出比更高,未修改時間更長的Block進行GC,兩者相乘數字更大的優先被GC

CAT算法

CAT的全稱是Cost Age Times,在Benefit-Cost算法的基礎上,增加了對數據壽命和擦除次數的考慮。

μ代表一個Block裏Valid Page的比例;

μ/(1-μ)理解爲爲了釋放出(1-μ)的free page必須付出遷移μ的valid page,也就是整體的Cost;

1/age代表Hot degree跟Age成反比

NumberOfCleaning代表Hot degree跟Block的PE Cycle成正比

對每個Block進行計算,選擇那些結果最高的Block進行GC過程。

觸發trim的條件

需要前端GC配合,尋找得哪些segment需要被trim.

trim的實現

nvme SSD 內部的trim

看論文的啓發

  1. 是否需要支持normal logging 和threaded logging ?
  2. 爲了避免SSD內部GC的影響,測試的時候 只使用部分SSD(大約一半空間);
  3. 測試用例: 參考論文

看代碼啓發

  1. GC 也不能太頻繁,因爲有的數據可能會很快被無效掉(發生了覆蓋寫或者刪除操作)

  2. 如果不用thread logging 的方法,在磁盤空間接近用滿的時候,如果爲了繼續保持append,是不是就需要繼續從一個比較大的聯繫空閒塊開始寫才行(怎麼纔不影響性能?)和從啓始地址開始,有何區別?
  3. 如何保證GC的速度能夠趕上空間被消耗掉速度?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章