在研究云系統提供的持久性時,想確保自己了解基本知識。首先閱讀NVMe規范,以了解disks提供的保證(https://www.evanjones.ca/durability-nvme.html)。簡單來說,你應該假設,在發出寫入到刷新或強制數據單元訪問寫入完成之間,數據已損壞。
大多數程序使用系統調用來寫入數據。本文著眼于linux文件API提供的保證。看起來這應該很簡單:程序調用write()并且完成后,數據是持久的。然而,write()僅將數據從應用程序復制到內存中的內核緩存中。為了強制數據持久,您需要使用一些其他機制。
本文知識的凌亂集。(簡單來說使用fdatasync或使用O_DSYNC打開)。更好更清晰的概述,請參見LWN的文章(https://lwn.net/Articles/457667/)。
write()的語義
在IEEE POSIX標準中,將write系統調用定義為嘗試將數據寫入文件描述符。成功返回后,即使是由其他進程或線程讀取或寫入的,也需要進行讀取以返回已寫入的字。
常規文件操作的線程交互:“如果兩個線程各自調用這些函數之一,則每個調用應看到另一個調用的所有指定效果,或者都不看到。” 這表明所有文件I / O必須有效地持有一個鎖。
這是否意味著寫是原子的?從技術上講,是的:將來的讀取必須返回寫入的全部內容,或者不返回任何內容。但是,寫入并不一定要完成,只允許傳輸部分數據。例如,有兩個線程,每個線程將1024個字節附加到一個文件描述符中。兩次寫入到每次只寫入一個字節是可以接受的。這仍然是“原子的”,但也會導致不希望的交錯輸出。有一個很棒的StackOverflow答案,有更多細節。https://stackoverflow.com/questions/42442387/is-write-safe-to-be-called-from-multiple-threads-simultaneously/42442926#42442926
fsync / fdatasync
在磁盤上獲取數據的最直接方法是調用fsync()。它要求操作系統將緩存中所有修改的塊以及所有文件元數據(例如訪問時間,修改時間等)傳輸到磁盤。元數據很少有用,因此除非你知道需要元數據,否則應使用fdatasync。該fdatasync是flush盡可能多的元數據作為必要“的后續數據讀取要正確處理”,這才是多數應用關心的事,。
一個問題是不能保證可以再次找到該文件。特別是,第一次創建文件時,需要在包含該文件的目錄上調用fsync,否則在失敗后該文件可能不存在。原因基本上是在UNIX中,由于硬鏈接,一個文件可以存在于多個目錄中,因此,當你在文件上調用fsync時,無法確定應該寫出哪個目錄(https://www.quora.com/When-should-you-fsync-the-containing-directory-in-addition-to-the-file-itself)。ext4實際上可能會自動同步目錄,但是對于其他文件系統可能并非如此。
實施方式會因文件系統而異。使用blktrace來檢查ext4和xfs使用了哪些磁盤操作。他們都對文件數據和文件系統日志發出正常的磁盤寫操作,使用高速緩存刷新,然后對日志進行FUA(Force Unit Access)寫操作,這可能表示操作已提交。在不支持FUA的磁盤上,這涉及兩次緩存刷新。實驗表明,fdatasync比fsync快一點,而blktrace顯示fdatasync傾向于寫入更少的數據(ext4:fsync為20 kiB,fdatasync為16 kiB)。實驗還表明,xfs的速度比ext4稍快,并且blktrace再次表明它傾向于清除較少的數據(xfs:fdatasync為4 kiB)。
用O_SYNC / O_DSYNC打開
系統要求耐久性。另一種選擇是在open()系統調用中使用O_SYNC或O_DSYNC選項。這將導致每個寫入的語義與寫入后分別帶有fsync / fdatasync的語義相同。POSIX規范將此稱為“同步I / O文件完整性完成”和“數據完整性完成”。這種方法的主要優點是,你只需要單個系統調用,而不是先寫入后跟fdatasync。最大的缺點是使用該文件描述符的所有寫入都將被同步,這可能會限制應用程序代碼的結構。
使用O_DIRECT的直接I / O
open()系統調用具有O_DIRECT選項,該選項旨在繞過操作系統的緩存,而直接對磁盤進行I / O。這意味著在許多情況下,應用程序的寫調用將直接轉換為磁盤命令。但是,通常這不能替代fsync或fdatasync,因為磁盤本身可以自由延遲或緩存那些寫入。更糟糕的是,在某些情況下,意味著O_DIRECT I / O會退回到傳統的緩沖I / O上。最簡單的解決方案是也使用O_DSYNC選項打開,這意味著在每次寫入后都將有效地跟隨fdatasync。
sync_file_range
Linux還具有sync_file_range,它可以允許將文件的一部分刷新到磁盤而不是整個文件,并觸發異步刷新,而不是等待它。但是,手冊頁指出它“極度危險”,因此不鼓勵使用它。用sync_file_range最好地描述了某些差異和危險,這是Yoshinori Matsunobu的有關其工作原理的文章。
http://yoshinorimatsunobu.blogspot.com/2014/03/how-syncfilerange-really-works.html
系統要求持久的I / O
結論是,持久性I / O基本上有三種方法。所有這些都要求在首次創建文件時在包含目錄上調
用fsync()。
- 寫入后使用fdatasync或fsync(最好使用fdatasync)。
- 寫在用O_DSYNC或O_SYNC(最好是O_DSYNC)打開的文件描述符上。
- 具有RWF_DSYNC或RWF_SYNC標志的pwritev2(首選RWF_DSYNC)。
一些隨機性能觀察
它們許多差異很小。
- 覆蓋比追加快(快2-100%):追加涉及其他元數據更新,即使在進行系統邏輯調用之后,但效果的大小也有所不同。我的建議是為獲得最佳性能,請調用fallocate()來預分配所需的空間,然后將其顯式零填充并進行fsync。這樣可以確保在文件系統中將塊標記為“已分配”,而不是“未分配”,這是一個很小的改進(?2%)。此外,某些磁盤在首次訪問某個塊時可能會降低性能,這意味著零填充會導致較大的改進(?100%)。值得注意的是,這可能發生在AWS EBS磁盤(不是官方的,尚未確認)和GCP永久磁盤(官方的;已確認)。
- 更少的系統調用更快(快5%):與O_DSYNC一起使用open或與RWF_SYNC一起使用pwritev2似乎要快一些,而不是顯式調用fdatasync。懷疑這是因為系統調用開銷稍少(一個調用而不是兩個)。但是,兩者之間的差異很小。
更多閱讀:https://www.evanjones.ca/durability-filesystem.html