天天看點

multi-stream SSD 介紹NVMe Multi-stream

NVMe Multi-stream

1. 原理介紹

1.1. flash 寫操作

在介紹 multi-stream 之前,首先簡單介紹一下 flash 寫操作的特性。

  • SSD 中寫操作(write)的單元為 page,page 的大小通常為 2 ~ 64 KB
  • NAND flash 在對 page 寫操作之前,必須對 page 執行擦除操作(erase),但是擦除操作的單元為 block,一個 block 通常包含 32 ~ 256 個 page

由于擦除操作相對耗時,因而在對某個 page 進行修改操作時,通常将修改的資料直接寫入一個新的已經擦除過的 page,而将原來的舊的 page 設定為 invalid 狀态,此時該 page 的修改操作就算完成了,之後 SSD FTL (Flash Translation Layer) 會執行垃圾回收(garbage collection)算法,回收處于 invalid 狀态的 page。

FTL 需要對 invalid page 執行擦除操作以回收這些 invalid page,而擦除操作的機關為 block,一個 block 中包含多個 page,其中既包含 invalid page,同時也包含 valid page。因而當 FTL 需要回收 block A 中的 invalid page 時,就必須先将 block A 中的 valid page 先拷貝到其他新的 block 例如 block B 中,并将 block A 中的 valid page 設定為 invalid 狀态,此時 block A 中的所有 page 均為 invalid 狀态,FTL 可以安全地對 block A 執行擦除操作。

在以上操作過程中,FTL 需要對回收的 block 中的 valid page 進行額外的拷貝操作,進而使得裝置實際執行的IO數量大于使用者送出的IO數量,這一特性稱為 寫放大(Write Amplification)

SSD 使用 WAF (Write Amplification Factor) 參數描述這一特性,該參數的值為

WAF = Amount of writes committed to flash / Amount of writes that arrived from the host           

由于 FTL 需要對 valid page 進行額外的拷貝操作,WAF 參數的值通常大于1。(當裝置支援 compression 特性時,WAF 參數的值是有可能小于1的。)

WAF 參數會影響 SSD 的使用壽命以及性能:

  • WAF 參數會影響 SSD 的壽命,WAF 的值越大,SSD 的實際有效壽命越短;
  • WAF 參數還會影響 SSD 讀寫操作的性能,垃圾回收操作通常在背景運作,因而當負載較輕時,垃圾回收操作一般不會對讀寫操作造成影響;然而當負載較重時,讀寫操作會與垃圾回收操作并行運作而競争資源,此時就會降低寫性能,增大讀延時。

1.2. multi-stream 原理

以上問題的根源在于兩種不同生命周期(lifetime)的資料存儲在同一個block中。

假設目前存在兩種生命周期的資料,hot data 與 cold data,其中 hot data 相對 cold data 會更為頻繁地進行更新。

在傳統的 SSD FTL 實作中,作業系統向 SSD 送出寫操作請求後,FTL 會将作業系統送出的資料依次寫入可用的 block 中,例如上圖中作業系統送出寫入H1、C1、H2、C2、C3、H3、H4、C4時,FTL 将這些資料依次寫到 block 1、block 2 中(假設一個 block 包含 4 個 page)。

之後作業系統送出對H1、H2、H3、H4進行修改時,必須将修改後的資料寫到 block 3 中,并将 block 1 中的 H1、H2 page,block 2 中的 H3、H4 page 設定為 invalid,之後當 garbage collection 需要回收 block 1、block 2 時,就必須對其中的 C1、C2、C3、C4 page 執行額外的拷貝。

multi-stream 特性則是将不同更新頻率的資料寫到不同的 block 中,進而盡可能地減小 garbage collection 中引入的額外的資料拷貝操作,進而提高 SSD 的有效生命周期,并提升寫性能。

當SSD FTL 支援 multi-stream 特性時,作業系統可以将寫入的資料與某個 stream 相綁定,例如将 hot data 與 stream x 綁定,将 cold data 與 stream y 綁定,此時 FTL 在受理作業系統送出的寫請求時,将不同 stream 的資料分别寫到不同的 block,例如将 stream x (hot data)全部寫到 block 1,将 stream y(cold data)全部寫到 block 2。

之後作業系統送出對 H1、H2、H3、H4 進行修改時,FTL 将修改後的資料寫到 block 3 中,此時 block 1 中不包含任何 valid page,之後 garbage collection 就可以直接對 block 1 執行擦除操作,而不會帶來額外的拷貝操作。

1.3. multi-stream 實作

multi-stream 的本質是針對 flash 特殊的寫特性,提供一種機制,使得不同更新頻率的資料寫到不同的 block 中。multi-stream 機制中使用 stream 抽象不同更新頻率的資料,并使用 stream id 辨別不同的 stream。

SSD 裝置本身掌握的資訊很少,其對資料的更新頻率基本沒有概念,隻有資料的生産者即上層軟體(包括使用者程式、作業系統)才了解資料的更新頻率,因而上層軟體負責寫入的資料與 stream id 的映射,而 SSD FTL 隻負責将不同 stream id 的資料寫入不同的 block。

以下依次介紹 SSD 接口協定、SSD 裝置驅動、檔案系統、應用程式如何适配 multi-stream 機制。

1.3.1. 接口協定

SSD 裝置支援多種接口協定,目前 SCSI(T10 (SCSI) standard)與 NVMe 1.3 已經正式支援 multi-stream 特性。

下面以 NVMe 1.3 為例介紹其對 multi-stream 特性的支援。

SSD 的 FTL 負責作業系統使用的 LBA (Logical Block Address) 與 SSD 内部使用的 physical address 之間的映射。

傳統的不支援 multi-stream 的 SSD FTL 通常隻維護一個 log structure,它隻會根據作業系統送出的 IO 請求的先後順序,将這些 IO 請求依次存儲到可用的存儲空間中。

而支援 multi-stream 的 SSD FTL 則會維護多個 log structure,其中為每個 stream 維護一個單獨的 log structure,FTL 以 Stream Granularity Size (SGS) 為機關配置設定存儲空間,即 FTL 一開始會為每個 stream 預先配置設定 SGS 大小的存儲塊,之後該 stream 的資料都會存儲到這一預配置設定的 SGS 大小的存儲塊中。當這一存儲塊的空間用盡時,FTL 則再次配置設定一個 SGS 大小的存儲塊。stream 的 log structure 會維護該 stream 配置設定的所有存儲塊,而正是所有的這些 SGS 大小的存儲塊構成了一個 stream。

NVMe 1.3 标準中以 directive 的形式支援 multi-stream,directive 機制用于實作 SSD 裝置與上層軟體之間的資訊溝通,stream 隻是 directive 的一個子集,目前 directive 也隻實作 stream 這一個子集。

其中與 stream 相關的指令有

  1. 上層軟體可以向 SSD 裝置發送 identify 指令,裝置在接收到該指令之後在回複的 identify data structure 中描述該裝置支援的相關特性,其中的 OACS (Optional Admin Command Support) filed 的 bit 5 描述該裝置是否支援 directive 特性;即當 SSD 裝置支援 directive 特性時,其在接收到 identify command 時,在傳回的 identify data structure 中,必須設定 OACS 的 bit 5,以表明該裝置支援 directive 特性。
  2. 對于支援 directive 特性的 SSD 裝置,使用者必須顯式調用 directive send command 指令執行 enable stream 操作,以開啟 multi-stream 特性。
  3. 之後在 write command 的 Directive Type 字段設定為 Streams (01h),同時 Directive Specific 字段設定為對應的 stream id,即可以将該 write command 寫入的資料與該 stream id 描述的 stream 相綁定。

1.3.2. 應用程式

應用程式是資料的主要生産者,因而我們自然會想到在應用層實作寫入資料與 stream id 的映射。

例如現有實作中可以通過 fcntl() 或 fadvise() 系統調用将特定 inode / file 與 stream id 進行綁定,這樣不同檔案的資料,或者同一檔案但不同程序生産的資料就會在 SSD 中分開存儲。

1.3.3. 檔案系統

在應用層實作寫入資料與 stream id 的綁定,可以最為準确地描述寫入資料的特性,然而這一實作需要顯式地修改應用程式的代碼。

除了應用程式之外,檔案系統也是資料的生産者,檔案系統需要維護各種中繼資料(例如檔案的 inode 資訊等),此外日志型檔案系統還需要寫入日志資料,因而檔案系統可以将不同類型的中繼資料與日志資料與 stream id 進行綁定,進而實作這些資料的分開存儲。

例如 Samsung 提出的 Fstream based Ext4 檔案系統中, 為 journal、inode、directory、inode/block bitmap and group descriptor 等中繼資料分别綁定不同的 stream id

此外還支援根據檔案的名稱或字尾,将檔案資料綁定不同的stream id,實作垂直優化。

FStream: Managing Flash Streams in the File System

1.3.4. 裝置驅動

SSD 裝置驅動也可以實作寫入資料與 stream id 的綁定,例如現有實作 AutoStream 中通過統計各個 SSD block 的 access time 等資料推測 block 中存儲的資料是 hot data 還是 cold data,進而實作 hot data 與 cold data 的分開存儲。

AutoStream: Automatic Stream Management for Multi-streamed SSDs

2. Fstream 原型實作

Fstream 是三星提出的一個 multistream based Ext4,其在 Ext4 檔案系統的基礎上,在檔案系統這一層将各種中繼資料與檔案資料映射到不同的 stream id,進而使這些資料在 SSD 上分開存儲。

由于相關代碼未開源,因而本文作者實作了一個簡單的原型。目前 Linux mainline (從 4.13.6 開始) 已經支援 multi-stream 特性,因而我們在 mainline 4.13.6 核心版本的基礎上實作這一原型。

2.1. Ext 4 中繼資料

Ext 4 檔案系統中的中繼資料包括

  • superblock
  • group descriptors
  • inode bitmap
  • data block bitmap
  • inode table
  • directory
  • journal

将這些中繼資料的更新頻率分為 4 個級别,即

stream data
journal stream
inode stream
directory stream
misc stream superblock, group descriptors, inode/block bitmap

2.2. write hint

/*
 * Write life time hint values.
 */
enum rw_hint {
        WRITE_LIFE_NOT_SET      = 0, 
        WRITE_LIFE_NONE         = RWH_WRITE_LIFE_NONE,
        WRITE_LIFE_SHORT        = RWH_WRITE_LIFE_SHORT,
        WRITE_LIFE_MEDIUM       = RWH_WRITE_LIFE_MEDIUM,
        WRITE_LIFE_LONG         = RWH_WRITE_LIFE_LONG,
        WRITE_LIFE_EXTREME      = RWH_WRITE_LIFE_EXTREME,
};           

使用 write hint 描述資料的更新頻率,即按照資料更新的相對頻率,将資料的更新頻率分為 4 個級别:short、medium、long與extrem。

當SSD裝置支援的 stream 的數量達到或超過 4 時

  • short 對應 stream id 1
  • medium 對應 stream id 2
  • long 對應 stream id 3
  • extrem 對應 stream id 4

當使用者未顯式指定資料的更新頻率時,其最終預設使用 stream id 0

2.3. write hint of file data

使用者程式可以通過 fcntl() 設定特定檔案的 write hint

struct inode {
+       enum rw_hint        i_write_hint;           

inode 中增加 i_write_hint 字段,使用者程式調用 fcntl() 時,即将使用者設定的 write hint 儲存在檔案對應的 inode 的 i_write_hint 字段

2.4. write hint of metadata

檔案系統使用 block buffer 作為接口向generic block layer送出中繼資料以及使用者資料的IO請求,因而在 struct buffer_head 中增加一個字段描述該 block buffer 緩存的資料對應的 write hint

struct buffer_head {
+       enum rw_hint b_write_hint;
};           

該字段的初始值為 WRITE_LIFE_NOT_SET,即預設使用 stream id 0

struct buffer_head *alloc_buffer_head(gfp_t gfp_flags)
        struct buffer_head *ret = kmem_cache_zalloc(bh_cachep, gfp_flags);
        if (ret) {
+               ret->b_write_hint = WRITE_LIFE_NOT_SET;           

在将磁盤上存儲的中繼資料拷貝到記憶體中時,設定對應的 buffer head 的 b_write_hint 字段,将 EXT 4 檔案系統的各種中繼資料分别映射到之前描述的 4 個級别

write hint of buffer head stream id
user data WRITE_LIFE_NOT_SET
JBD2_WRITE_LIFE 1
EXT4_WRITE_LIFE_INODE 2
EXT4_WRITE_LIFE_MISC 3
block bitmap
EXT4_WRITE_LIFE_DIR 4
#define JBD2_WRITE_LIFE         WRITE_LIFE_SHORT        /* stream for journaling */           
enum ext4_write_hint {
    EXT4_WRITE_LIFE_INODE       = WRITE_LIFE_MEDIUM,    /* stream for inode table */
    EXT4_WRITE_LIFE_MISC        = WRITE_LIFE_LONG,      /* stream for superblock, inode bitmap, block bitmap and group descriptors */
    EXT4_WRITE_LIFE_DIR         = WRITE_LIFE_EXTREME,   /* stream for directory */
};           

2.5. pass write hint to generic block layer

Ext 4 檔案系統會調用 block_write_full_page() 将 file data 寫到SSD,此時 file data 對應的 page cache 的 buffer head 的 write hint 為預設的 WRITE_LIFE_NOT_SET,因而對于 file data 實際使用 inode 中存儲的 write hint

使用者程式可以通過 fcntl() 設定特定檔案的 write hint,當使用者未顯式設定時,file data 實際使用 stream id 0

int __block_write_full_page(struct inode *inode, struct page *page,
            get_block_t *get_block, struct writeback_control *wbc,
            bh_end_io_t *handler)
{
+   enum rw_hint write_hint = bh->b_write_hint == WRITE_LIFE_NOT_SET ? inode->i_write_hint : bh->b_write_hint;      

+   submit_bh_wbc(REQ_OP_WRITE, write_flags, bh, write_hint, wbc);
           

Ext 4 檔案系統會調用 submit_bh()/block_write_full_page() 将 metadata 回寫到 SSD 中,此時使用的 write hint 即為設定的各個 metadata 對應的write hint

int submit_bh(int op, int op_flags, struct buffer_head *bh)
 {
+       return submit_bh_wbc(op, op_flags, bh, bh->b_write_hint, NULL);
 }           

最終将 write hint 儲存到 bio 中,即将 write hint 傳遞給 generic block layer

static int submit_bh_wbc(int op, int op_flags, struct buffer_head *bh, 
                         enum rw_hint write_hint, struct writeback_control *wbc)
{
        bio->bi_write_hint = write_hint;
        ...
        submit_bio(bio);
        return 0;           

2.6. write hint in generic block layer

generic block layer 中對 multi-stream 的适配有

  • 隻有同一個 stream 内的 bio 之間才可以合并
  • 将 bio 的 write hint 傳遞給 request
void blk_init_request_from_bio(struct request *req, struct bio *bio)
{
    req->write_hint = bio->bi_write_hint;
}           

2.7. write hint in NVMe layer

最終 write hint 傳遞到 NVMe layer,并儲存在 request 的 write_hint 字段

此時調用 nvme_assign_write_stream()将 write hint 映射為對應的 stream id,并将 stream id 儲存在 write command 的 directive specific 字段,之後将該 write command 交給SSD裝置處理,之後的處理均由 SSD 裝置上的 FTL 實作。

static inline blk_status_t nvme_setup_rw(struct nvme_ns *ns, 
                struct request *req, struct nvme_command *cmnd)
{
        if (req_op(req) == REQ_OP_WRITE && ctrl->nr_streams)
                nvme_assign_write_stream(ctrl, req, &control, &dsmgmt);
        ...
        cmnd->rw.control = cpu_to_le16(control);
        cmnd->rw.dsmgmt = cpu_to_le32(dsmgmt);
        return 0;
}
           
/*
 * Check if 'req' has a write hint associated with it. If it does, assign
 * a valid namespace stream to the write.
 */
static void nvme_assign_write_stream(struct nvme_ctrl *ctrl,
                                     struct request *req, u16 *control,
                                     u32 *dsmgmt)
{
        enum rw_hint streamid = req->write_hint;

        if (streamid == WRITE_LIFE_NOT_SET || streamid == WRITE_LIFE_NONE)
                streamid = 0;
        else {
                streamid--;
                if (WARN_ON_ONCE(streamid > ctrl->nr_streams))
                        return;

                *control |= NVME_RW_DTYPE_STREAMS;
                *dsmgmt |= streamid << 16;
        }

        if (streamid < ARRAY_SIZE(req->q->write_hints))
                req->q->write_hints[streamid] += blk_rq_bytes(req) >> 9;
}           

3. Samsung PM963 multi stream test

3.1. prepare

三星在内部測試中使用 PM963 480GB SSD 進行測試

SSD: Samsung PM963 480GB, with the allocation granularity 1 of 1.1GB
from "FStream: Managing Flash Streams in the File System"           

我們自己的測試環境中使用的 SSD 為 PM963 1920GB (MZQLW1T9HMJP-00003)。

三星在網上公布的資料均顯示 PM963 支援 multi-stream 特性,然而 NVMe 從 1.3 标準開始添加對 multi-stream 的支援,而 PM963 隻支援 1.2 标準。

同時測試發現我們使用的 PM963 不支援 NVMe 1.3 中規定的 directive 相關的指令

  1. 其 OACS 字段的 bit 5 為 0,即不支援 directive 特性。
  2. 不支援 directive send 指令,在手動發送該指令時,傳回結果顯示裝置不支援該指令。

盡管如此,我們考慮到 PM963 在 NVMe 1.3 之前釋出,因而可能使用非标準的方法實作 multi-stream 特性,我們還是希望使用 fio 對 multi-stream 特性開啟前後,裝置的讀寫性能進行一次測試。

3.2. fio測試

測試環境

  1. AliOS Linux 4.9.93 with multi-stream enabled
  2. Samsung PM963 1920GB (MZQLW1T9HMJP-00003)
  3. fio-2.18

3.2.1. prepare

在每次測試前

  1. 執行

    nvme --format /dev/nvme8n1 -s 1

    ,對全盤執行擦除操作
  2. mkfs.ext4 .dev.nvme8n1

    ,檔案系統格式化
  3. 使用 fio 建立一個 1500GB 的檔案,使SSD裝置的占用率達到 90%,這樣可以加重之後的測試中垃圾回收算法的負載

3.2.2. fio IO test

  • 6 randread thread, block size 4KB, aio depth 64
  • 4 sequential write thread, block size 128KB, aio depth 1
  • run 2 hours

其中 4 個寫線程具有不同的資料更新頻率,分别為 1x, 2x, 3x, 10x。(我們使用 fio 的 thinktime 參數來實作不同的資料更新頻率。)

測試分為兩次進行

  1. 第一次測試中不使用 multi-stream 特性
  2. 第二次測試中開啟 multi-stream 特性,使用 fio 的 fadvise_stream 參數為四個寫線程綁定不同的 stream ID,其中
write thread data lifetime stream ID
10x
3x
2x
1x

注意 該測試中我們沒有對我們實作的 Fstream 原型進行測試,而是直接使用 mainline 4.13.6,在使用者層通過 fcntl() syscall 實作不同更新頻率的使用者資料與stream ID的映射。即該測試中我們隻對使用者程序産生的檔案資料分流存儲,而沒有對檔案系統的中繼資料進行分流存儲。

3.2.3. fio 測試結果

test write throughput read latency read average latency
multi-stream disabled 617 MB/s 82.9 K 772 us
multi-stream enabled 616 MB/s 82.8 K 773 us
multi-stream SSD 介紹NVMe Multi-stream

上圖描述兩次測試過程中,裝置的實時 write throughput,其中

  • 在一開始裝置的寫性能達到峰值,這是因為裝置經過全盤擦除後,free page數量充足,該現象符合預期
  • 之後裝置的寫性能出現斷崖式的下降,我們分析這是因為随着寫操作的進行,裝置的free page數量不斷減小,當下降到某一門檻值時使用者程序的寫操作必須等待垃圾回收算法釋放free page,此時裝置的寫性能出現下降
  • 之後裝置的寫性能一直處于相對穩定的狀态

從以上現象我們可以得出一個結論,即測試過程中垃圾回收算法一直處于相對較重的負載當中。

從以上圖表我們可以發現,在 multi-stream 特性開啟前後,裝置的讀寫性能并沒有出現明顯變化。

3.3. 測試結果分析

目前使用 NVMe 1.3 标準的方法不能開啟 PM963 的 multi-stream 特性,即測試環境中使用的 PM963 SSD 不支援 directive 相關的指令,同時在 write command 中“強行”添加 stream id 時,寫性能也沒有明顯的提升效果。

之後我們咨詢熟悉硬體的同學,了解到目前公司使用的 PM963 均不支援 multi-stream 特性,而支援該特性的 SSD 要等到明年才能上線。

此外目前測試環境也沒有 SAS SSD 裝置(SCSI 也支援 multi-stream 特性),因而目前尚不能對 multi-stream 特性的實際效果進行測試。

4. 總結與讨論

4.1. multi-stream 适用場景分析

multi-stream 的原理是盡可能将相同更新頻率的資料存儲到同一個 block,進而在回收 block 時減小資料的拷貝操作,但是該機制的性能提升效果會受以下因素的影響。

4.1.1. 适用于高負載的應用場景

這裡的高負載具有兩重含義,即

  1. SSD 的空間占用率達到一定程度
  2. SSD 的寫負載較重

multi-stream 隻有在 SSD 的空間占用率達到一定程度時才會展現性能提升的效果。當使用一塊全新的 SSD 時,每次寫操作時總是有充足的 free block 可寫,因而就不會運作 garbage collection, multi-stream 也就不會發揮作用。

在 SSD 的空間占用率達到一定程度的基礎上,當同時寫負載較重時,multi-stream 才能提升性能。這是因為 multi-stream 主要提升 garbage collection 算法的性能,而該算法通常在背景運作;當寫負載較輕時,garbage collection 的需求較小,此時帶來的性能提升效果不明顯;隻有當裝置可用空間較小,同時寫負載較重時,garbage collection 算法的負載才會較重,此時 multi-stream 才會帶來明顯的性能提升效果。

但值得注意的是,以上描述的是隻有在寫負載較重時才可以展現 multi-stream 對寫操作的性能提升,而無論寫負載的高低,multi-stream 機制都可以降低 WAF 參數,進而提高 SSD 的有效生命周期。

4.1.2. 更适用于小塊資料的随機寫操作

由于 multi-stream 的原理,其更加适用于資料庫這類小塊資料的随機寫入操作,因為小塊資料更容易與其他不同類型的資料存儲在同一個 block 中,同時其随機寫操作也更容易使同一個 block 同時包含 valid page 與 invalid page,因而也就給 multi-stream 更多的提升性能的空間。

例如上圖為三星發表的論文中 Fstream 的性能測試結果,其中 Fileserver 測試程式主要執行大塊資料的順序寫操作,Cassandra 測試程式主要執行小塊資料的随機寫操作,測試結果顯示,multi-stream 機制對于小塊資料的寫操作具有更為明顯的性能提升。

4.2. stream id 配置設定機制

stream id 的配置設定機制會直接影響 multi-stream 的效果,當同屬于一個 stream 的所有資料具有完全相同的更新頻率時,multi-stream 能帶來最大的性能提升,即上層程式需要準确地判斷資料的更新頻率,并依此為其配置設定合适的stream id。

4.2.1. exclusive stream

一種最簡單同時最有效的方案就是為每一類資料都配置設定一個單獨的 stream id,即這一類資料會獨占一個 stream,此時該 stream 的 block 中隻存儲一類資料,那麼自然可以将該 stream 在垃圾回收時帶來的額外的資料拷貝操作降到最低,進而具有最好的性能效果。

這種方案同時也是一種理想的方案,現實中 SSD 裝置支援的 stream 的個數是有限的,因而不可能為每一類資料都配置設定一個單獨的 stream,因而當将兩種資料配置設定到同一個 stream 中時,這兩種資料的更新頻率勢必存在差異,因而無法達到最為理想的性能提升效果。

需要注意的是,三星的論文中描述的性能測試,即是為每個更新頻率的資料配置設定單獨的 stream,因而論文中描述的性能提升是理想環境下的最大性能提升效果。

4.2.2. shared stream

既然 stream 的數量是有限的,那麼就必須提供某種機制,按照資料的更新頻率,将資料綁定到合适的 stream 上。

目前 Linux mainline 中提供的機制是,抽象 short、medium、long、extrem 這四個級别描述資料的更新頻率,該抽象隻是描述資料更新頻率的相對值,而非絕對值。

之後 SSD 裝置驅動需要将這四個級别映射到對應的 stream id,例如 short 映射到 stream 1,extrem 映射到 stream 4,依此類推。

該機制可能存在的問題有

  1. 該機制最多隻使用 4 個 stream,當裝置支援的 stream 個數超過 4 個時,stream 資源不能有效利用,及其 scalability 不好。
  2. 該機制中開發者必須依靠自己對資料更新頻率的了解,将資料綁定到不同的 stream 上,然而不同開發者對資料更新頻率的了解是不同的,例如同時映射為 short 的兩類資料,其更新頻率可能存在很大的差異。

4.2.3. stream of user data

目前存在兩種途徑為 user file data 綁定 stream

  1. 應用程式調用 fcntl() 手動設定使用者資料的 write hint,由于應用程式自身對資料更新頻率具有最為準确的了解,因而這種方法設定使用者資料的 write hint,最為準确,同時也最為靈活,但是需要顯式地修改應用程式的代碼。此外由于應用程式的開發者與核心開發者對資料更新頻率的了解不同,同為 short write hint 的 file data 與 metadata 其實際的更新頻率可能存在較大的差異,進而影響 multi-stream 的性能。
  2. 取消使用者态設定 write hint 的接口,由檔案系統依據檔案的名稱、字尾資訊推測檔案資料的更新頻率,進而自動為檔案資料綁定對應的 stream,該方法不需要應用程式顯式地修改代碼,但是由于檔案系統掌握的檔案的相關資訊是非常有限的,通過檔案名稱推測檔案資料更新頻率的方法,其準确性與靈活性都存在很大的限制。

因而上層實作需要根據不同的應用場景,選擇上述的其中一種方法,或是将這兩者相結合。

4.2.4. seperated stream of user/kernel

由于應用程式的開發者與核心開發者對資料更新頻率的了解可能存在不同,同為 short write hint 的 file data 與 metadata 其實際的更新頻率可能存在較大的差異,進而影響 multi-stream 的性能。

因而另一種方案是,為核心與使用者态程式提供相隔離的 stream id,例如

  • 仍然使用資料的更新頻率的相對值作為 write hint,例如将 file data 與 metadata 的更新頻率分為 short、medium、long、extrem 多個級别
  • 當 SSD 裝置一共支援 N 個 stream 時
  • 核心預留其中的 M 個 stream id,所有核心态送出的資料(包括 Fstream 的 metadata),按照其 write hint,映射為這 M 個 stream
  • 使用者态使用剩餘的 (N - M) 個 stream id,使用者态使用 fcntl()/fadvise() 接口設定檔案的 write hint,之後檔案系統送出該檔案寫入的資料時,将該檔案映射到這 (N - M) 個 stream

該方案可以保證 file data 與 metadata 在 SSD 上絕對是分開存儲的。

5. 附錄

1. NVMe 1.3 2. FStream: Managing Flash Streams in the File System 3. AutoStream: Automatic Stream Management for Multi-streamed SSDs 4. write stream patch

multi-stream 特性由 Jens Axboe 于 2017 年添加到 Linux 4.13.6,适配内容主要包括

  • 令 SCSI 與 NVMe 驅動支援 multi-stream 特性
  • 對應用層提供 fcntl() 接口,實作檔案資料與 stream 的映射
  • 令檔案系統、通用塊裝置層适配 multi-stream 特性,即将 fcntl() 中綁定的 stream id 傳遞到 SCSI / NVMe 驅動