f2fs GC 解密

名詞解釋

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

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

背景

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

f2fs GC的總體介紹

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

  • mkfs.f2fs 時的操做

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

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

f2fs GC模塊內部的原理

觸發時機ide

/*
                 * [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 spa

  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的速度可以遇上空間被消耗掉速度?
相關文章
相關標籤/搜索