TL; DR: Jika kernel Linux kehilangan tulis I / O yang disangga , apakah ada cara bagi aplikasi untuk mengetahuinya?
Saya tahu Anda harus ke fsync()
file (dan direktori induknya) untuk daya tahan . Pertanyaannya adalah jika kernel kehilangan buffer kotor yang tertunda tulis karena kesalahan I / O, bagaimana aplikasi dapat mendeteksi ini dan memulihkan atau membatalkan?
Pikirkan aplikasi basis data, dll, di mana urutan ketahanan menulis dan menulis bisa menjadi sangat penting.
Hilang menulis? Bagaimana?
Lapisan blok kernel Linux dalam beberapa keadaan kehilangan permintaan I / O buffered yang telah berhasil dikirim oleh write()
, pwrite()
dll, dengan kesalahan seperti:
Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0
(Lihat end_buffer_write_sync(...)
dan end_buffer_async_write(...)
dalamfs/buffer.c
).
Pada kernel yang lebih baru kesalahannya malah akan mengandung "lost async page write" , seperti:
Buffer I/O error on dev dm-0, logical block 12345, lost async page write
Karena aplikasi write()
sudah kembali tanpa kesalahan, sepertinya tidak ada cara untuk melaporkan kesalahan kembali ke aplikasi.
Mendeteksi mereka?
Saya tidak begitu familiar dengan sumber kernel, tetapi saya pikir itu menetapkan AS_EIO
pada buffer yang gagal dituliskan jika itu melakukan penulisan async:
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
tetapi tidak jelas bagi saya apakah atau bagaimana aplikasi dapat mengetahui tentang hal ini ketika nanti fsync()
file untuk mengkonfirmasi itu pada disk.
Sepertinya wait_on_page_writeback_range(...)
dimm/filemap.c
kekuatan oleh do_sync_mapping_range(...)
difs/sync.c
yang belok disebut oleh sys_sync_file_range(...)
. Itu kembali -EIO
jika satu atau lebih buffer tidak bisa ditulis.
Jika, seperti yang saya tebak, ini menyebar ke fsync()
hasil, maka jika panik dan menalangi aplikasi jika mendapat kesalahan I / O fsync()
dan tahu bagaimana melakukan kembali pekerjaannya ketika dihidupkan ulang, apakah itu cukup perlindungan?
Mungkin tidak ada cara bagi aplikasi untuk mengetahui offset byte mana dalam file yang sesuai dengan halaman yang hilang sehingga dapat menulis ulang mereka jika mengetahui caranya, tetapi jika aplikasi mengulangi semua pekerjaan yang tertunda sejak keberhasilan terakhir fsync()
file, dan yang menulis ulang setiap buffer kernel kotor yang terkait dengan penulisan yang hilang terhadap file, yang seharusnya menghapus semua flag kesalahan I / O pada halaman yang hilang dan memungkinkan berikutnya fsync()
untuk menyelesaikan - benar?
Apakah ada keadaan lain yang tidak berbahaya di mana fsync()
mungkin kembali di -EIO
mana bailing out dan mengulang pekerjaan akan terlalu drastis?
Mengapa?
Tentu saja kesalahan seperti itu seharusnya tidak terjadi. Dalam hal ini kesalahan muncul dari interaksi yang tidak menguntungkan antara dm-multipath
default pengemudi dan kode akal yang digunakan oleh SAN untuk melaporkan kegagalan untuk mengalokasikan penyimpanan yang disediakan secara tipis. Tapi ini bukan satu-satunya keadaan di mana mereka bisa terjadi - saya juga melihat laporan dari LVM yang disediakan misalnya, seperti yang digunakan oleh libvirt, Docker, dan banyak lagi. Aplikasi kritis seperti basis data harus berusaha mengatasi kesalahan semacam itu, alih-alih membabi buta seolah-olah semuanya baik-baik saja.
Jika kernel berpikir tidak masalah untuk kehilangan penulisan tanpa mati karena kernel panik, aplikasi harus menemukan cara untuk mengatasinya.
Dampak praktisnya adalah saya menemukan kasus di mana masalah multipath dengan SAN menyebabkan penulisan hilang yang menyebabkan korupsi basis data karena DBMS tidak tahu bahwa penulisan gagal. Tidak menyenangkan.
sumber
Jawaban:
fsync()
kembali-EIO
jika kernel kehilangan penulisan(Catatan: bagian awal merujuk kernel yang lebih tua; diperbarui di bawah ini untuk mencerminkan kernel modern)
Sepertinya penghapusan buffer async
end_buffer_async_write(...)
gagal membuat-EIO
tanda pada halaman buffer kotor yang gagal untuk file :yang kemudian dideteksi oleh
wait_on_page_writeback_range(...)
yang disebut olehdo_sync_mapping_range(...)
yang disebut olehsys_sync_file_range(...)
yang disebut dengansys_sync_file_range2(...)
menerapkan panggilan C libraryfsync()
.Tapi hanya sekali!
Komentar ini di
sys_sync_file_range
menyarankan bahwa ketika
fsync()
kembali-EIO
atau (tidak terdokumentasi di halaman manual)-ENOSPC
, itu akan menghapus status kesalahan sehingga selanjutnyafsync()
akan melaporkan keberhasilan meskipun halaman tidak pernah ditulis.Cukup jelas
wait_on_page_writeback_range(...)
menghapus kesalahan bit saat mengujinya :Jadi, jika aplikasi berharap dapat mencoba kembali
fsync()
sampai berhasil dan percaya bahwa data di-disk, itu sangat salah.Saya cukup yakin ini adalah sumber dari korupsi data yang saya temukan di DBMS. Mencoba ulang
fsync()
dan berpikir semua akan baik-baik saja ketika berhasil.Apakah ini diizinkan?
Dokumen POSIX / SuS di
fsync()
tidak benar-benar menentukan cara ini:Halaman manual Linux
fsync()
hanya tidak mengatakan apa-apa tentang apa yang terjadi pada kegagalan.Jadi sepertinya arti
fsync()
kesalahan adalah "tidak tahu apa yang terjadi pada tulisan Anda, mungkin berhasil atau tidak, lebih baik coba lagi untuk memastikan".Kernel yang lebih baru
Pada 4,9
end_buffer_async_write
set-EIO
pada halaman, cukup viamapping_set_error
.Di sisi sinkronisasi saya pikir itu mirip, meskipun strukturnya sekarang cukup rumit untuk diikuti.
filemap_check_errors
dimm/filemap.c
sekarang tidak:yang memiliki banyak efek yang sama. Semua pemeriksaan error tampaknya harus
filemap_check_errors
dilakukan dengan cara test-and-clear:Saya menggunakan
btrfs
laptop saya, tetapi ketika saya membuatext4
loopback untuk pengujian/mnt/tmp
dan mengatur probe perf di atasnya:Saya menemukan tumpukan panggilan berikut di
perf report -T
:Pembacaan ulang menunjukkan bahwa ya, kernel modern berperilaku sama.
Ini tampaknya berarti bahwa jika
fsync()
(atau mungkinwrite()
atauclose()
) kembali-EIO
, file tersebut dalam keadaan tidak terdefinisi antara kapan Anda terakhir berhasilfsync()
atauclose()
tidak dan terakhirwrite()
sepuluh negara.Uji
Saya telah menerapkan uji kasus untuk menunjukkan perilaku ini .
Implikasi
DBMS dapat mengatasi hal ini dengan memasukkan crash recovery. Bagaimana mungkin aplikasi pengguna normal untuk mengatasi ini? The
fsync()
halaman manual tidak memberikan peringatan bahwa itu berarti "fsync-jika-anda-merasa-seperti-itu" dan aku berharap banyak aplikasi tidak akan mengatasi dengan baik dengan perilaku ini.Laporan bug
Bacaan lebih lanjut
lwn.net menyentuh ini dalam artikel "Peningkatan penanganan kesalahan blok-layer" .
utas milis postgresql.org .
sumber
errno
sepenuhnya merupakan konstruksi dari library C userspace. Adalah umum untuk mengabaikan perbedaan nilai kembali antara syscalls dan pustaka C seperti ini (seperti yang dilakukan Craig Ringer, di atas), karena nilai pengembalian kesalahan secara andal mengidentifikasi yang mana (fungsi syscall atau pustaka C) sedang dirujuk ke: "-1
denganerrno==EIO
"merujuk ke fungsi pustaka C, sedangkan"-EIO
"merujuk ke syscall. Akhirnya, halaman manual Linux online adalah referensi terbaru untuk halaman manual Linux.fsync()
/fdatasync()
ketika ukuran transaksi adalah file yang lengkap; dengan menggunakanmmap()
/msync()
ketika ukuran transaksi adalah catatan yang disejajarkan dengan halaman; dan dengan menggunakan level rendah I / O,,fdatasync()
dan beberapa deskriptor file konkuren (satu deskriptor dan untaian per transaksi) ke file yang sama jika tidak " . Kunci deskripsi file terbuka khusus Linux (fcntl()
,F_OFD_
) sangat berguna dengan yang terakhir.Saya tidak setuju.
write
dapat kembali tanpa kesalahan jika penulisan hanya diantrekan, tetapi kesalahan tersebut akan dilaporkan pada operasi berikutnya yang akan memerlukan penulisan aktual pada disk, itu berarti pada berikutnyafsync
, mungkin pada penulisan berikut jika sistem memutuskan untuk menyiram cache dan pada Setidaknya pada penutupan file terakhir.Itulah alasan mengapa sangat penting bagi aplikasi untuk menguji nilai balik dari dekat untuk mendeteksi kemungkinan kesalahan penulisan.
Jika Anda benar-benar harus dapat melakukan pemrosesan kesalahan pintar, Anda harus mengasumsikan bahwa semua yang ditulis sejak sukses terakhir
fsync
mungkin gagal dan bahwa dalam semua itu setidaknya ada sesuatu yang gagal.sumber
fsync()
atauclose()
file jika mendapat-EIO
dariwrite()
,fsync()
atauclose()
. Yah, itu menyenangkan.write
(2) memberikan kurang dari yang Anda harapkan. Halaman manual sangat terbuka tentang semantikwrite()
panggilan sukses :Kita dapat menyimpulkan bahwa yang berhasil
write()
hanya berarti bahwa data telah mencapai fasilitas buffering kernel. Jika tetap ada buffer gagal, akses selanjutnya ke deskriptor file akan mengembalikan kode kesalahan. Sebagai pilihan terakhir yang mungkinclose()
. Halaman manual dariclose
panggilan sistem (2) berisi kalimat berikut:Jika aplikasi Anda perlu mempertahankan data, hapuslah itu harus menggunakan
fsync
/fsyncdata
secara teratur:sumber
fsync()
wajib. Tetapi dalam kasus khusus di mana kernel kehilangan halaman karena kesalahan I / O akanfsync()
gagal? Dalam keadaan apa kemudian dapat berhasil setelah itu?fsync()
pengembalian-EIO
pada masalah I / O (Apa untungnya jika sebaliknya?). Jadi database tahu beberapa dari penulisan sebelumnya gagal dan bisa masuk ke mode pemulihan. Bukankah ini yang kamu inginkan? Apa motivasi dari pertanyaan terakhir Anda? Apakah Anda ingin tahu yang gagal menulis atau memulihkan deskriptor file untuk digunakan lebih lanjut?fsync()
dapat kembali ke-EIO
tempat yang aman untuk mencoba lagi, dan jika mungkin untuk mengatakan perbedaannya.-EIO
. Jika setiap deskriptor file hanya digunakan oleh satu utas pada satu waktu, utas ini dapat kembali ke yang terakhirfsync()
dan mengulangwrite()
panggilan. Tapi tetap saja, jikawrite()
itu hanya menulis bagian dari sektor, bagian yang tidak dimodifikasi mungkin masih korup.Gunakan bendera O_SYNC ketika Anda membuka file. Ini memastikan data ditulis ke disk.
Jika ini tidak memuaskan Anda, tidak akan ada apa-apa.
sumber
O_SYNC
adalah mimpi buruk bagi kinerja. Itu berarti aplikasi tidak dapat melakukan hal lain ketika I / O disk terjadi kecuali ia memunculkan thread I / O. Anda mungkin juga mengatakan bahwa antarmuka I / O buffered tidak aman dan semua orang harus menggunakan AIO. Tentunya penulisan yang hilang secara diam-diam tidak dapat diterima dalam buffer I / O?O_DATASYNC
Hanya sedikit lebih baik dalam hal itu)Periksa nilai balik dari penutupan. tutup dapat gagal sementara penulisan buffer tampaknya berhasil.
sumber
open()
ing danclose()
ing file setiap beberapa detik. itulah sebabnya kami memilikifsync()
...