Linux'ta kayıp yazmalara neden olan I / O hatalarıyla başa çıkmak için programlar yazma


138

TL; DR: Linux çekirdeği arabellekli bir G / Ç yazımını kaybederse , uygulamanın öğrenmesi için herhangi bir yol var mı?

fsync()Dayanıklılık için dosyaya (ve üst dizinine) sahip olduğunuzu biliyorum . Soru, çekirdek bir G / Ç hatası nedeniyle yazma bekleyen kirli arabellekleri kaybederse , uygulama bunu nasıl algılayabilir ve kurtarabilir veya iptal edebilir?

Yazma sırasının ve yazma dayanıklılığının önemli olabileceği veritabanı uygulamaları vb.

Kayıp yazılar? Nasıl?

Linux çekirdeğinin blok katmanı, bazı durumlarda , vb. Tarafından başarıyla gönderilen arabelleğe alınmış G / Ç isteklerini aşağıdaki gibi bir hata ile kaybedebilir :write()pwrite()

Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0

(Bkz. end_buffer_write_sync(...)Ve end_buffer_async_write(...)içerifs/buffer.c ).

Daha yeni çekirdeklerde hata bunun yerine "kayıp zaman uyumsuz sayfa yazma" yı içerecektir :

Buffer I/O error on dev dm-0, logical block 12345, lost async page write

Uygulamanın write()hatasız olarak geri döndüğünden, bir hatayı tekrar uygulamaya bildirmenin bir yolu yoktur.

Onları mı tespit ediyorsunuz?

Çekirdek kaynaklarına aşina değilim, ama async yazma yapıyorsa yazılmamış olan arabellek üzerinde ayarlandığını düşünüyorumAS_EIO :

    set_bit(AS_EIO, &page->mapping->flags);
    set_buffer_write_io_error(bh);
    clear_buffer_uptodate(bh);
    SetPageError(page);

ancak daha sonra fsync()dosyada diskte olduğunu onaylamak için uygulamanın bunu öğrenip öğrenemeyeceği net değil .

Öyle görünüyor wait_on_page_writeback_range(...)içindemm/filemap.c vasitasiyla do_sync_mapping_range(...)içindefs/sync.c tarafından çağrılan dönün hangi sys_sync_file_range(...). -EIOBir veya daha fazla arabellek yazılamazsa geri döner .

Tahmin ettiğim gibi, bunun fsync()sonucuna yayılırsa, uygulama paniği ve bir G / Ç hatası alırsa fsync()ve yeniden başlatıldığında işini nasıl yeniden yapacağını bilirse kurtarırsa, bu yeterli koruma mıdır?

Uygulamanın muhtemelen bir dosyadaki hangi bayt ofsetlerinin kayıp sayfalara karşılık geldiğini bilmesinin bir yolu yoktur, böylece nasıl olduğunu bilirse onları yeniden yazabilir, ancak uygulama fsync()dosyanın son başarılı olmasından bu yana tüm bekleyen çalışmalarını tekrarlarsa ve bu yeniden yazarsa kayıp sayfalardaki herhangi bir G / Ç hata işaretini temizlemeli ve bir sonrakinin fsync()tamamlanmasına izin veren dosyaya karşı kayıp yazmalara karşılık gelen kirli çekirdek arabellekleri - doğru mu?

Öyleyse, kurtarmanın ve yeniden çalışmaların çok şiddetli olacağı yerlere fsync()geri dönebilecek başka, zararsız durumlar var -EIOmı?

Neden?

Elbette bu tür hatalar olmamalıdır. Bu durumda hata, dm-multipathsürücünün varsayılan değerleri ile SAN tarafından ince-yetkilendirilmiş depolama alanı tahsis edemediğini bildirmek için kullanılan algılama kodu arasındaki talihsiz bir etkileşimden kaynaklanır . Ancak, bu olabilecekleri tek durum değildir - örneğin libvirt, Docker ve daha fazlası tarafından kullanılan ince provizyonlu LVM'den raporlar da gördüm. Bir veritabanı gibi kritik bir uygulama, her şey yolundaymış gibi körü körüne devam etmek yerine, bu tür hatalarla başa çıkmaya çalışmalıdır.

Eğer çekirdek çekirdek panik ölmeden kaybetmek yazım işlemine 's Tamam düşünür, uygulamalar başa çıkmak için bir yol bulmalıyız.

Pratik etki, bir SAN ile çok yollu bir sorunun DBMS yazmalarının başarısız olduğunu bilmediği için veritabanı bozulmasına neden olan kayıp yazmalara neden olduğu bir durum bulduğumdur. Eğlenceli değil.


1
Korkarım bu saklamak ve bu hata koşullarını hatırlamak için SystemFileTable ek alanları gerekir. Ve kullanıcı alanı sürecinin sonraki çağrılarda bunları alma veya inceleme olasılığı. (fsync () ve close () bu tür tarihi bilgileri döndürür mü?)
joop

@ joop Teşekkürler. Sadece neler olduğunu düşündüğüm bir cevap gönderdim, aklı bir sağlık kontrolüne sahip olduğum için, "yazma () ihtiyaçlarının yakın () veya fsync ( ) dayanıklılık için "sorusunu okumadan?
Craig Ringer

BTW: Bence çekirdek kaynaklarını gerçekten araştırmalısınız. Günlüklü dosya sistemleri muhtemelen aynı tür sorunlardan muzdarip olacaktır. Takas bölümü işleminden bahsetmiyorum bile. Bunlar çekirdek alanında yaşadığından, bu koşulların ele alınması muhtemelen biraz daha katı olacaktır. Kullanıcı alanından görülebilen writev () de bakılacak bir yer gibi görünüyor. [Craig'te: evet becaus isminizi biliyorum ve tam bir aptal olmadığını biliyorum; -]
joop

1
Katılıyorum, o kadar adil değildim. Ne yazık ki cevabınız çok tatmin edici değil, yani kolay bir çözüm yok (şaşırtıcı?).
Jean-Baptiste Yunès

1
@ Jean-BaptisteYunès True. Birlikte çalıştığım DBMS için "crash and redo enter" kabul edilebilir. Çoğu uygulama için bu bir seçenek değildir ve senkronize G / Ç'nin korkunç performansını tolere etmek veya yalnızca G / Ç hatalarında kötü tanımlanmış davranışı ve bozulmayı kabul etmek zorunda kalabilirler.
Craig Ringer

Yanıtlar:


91

fsync()-EIOçekirdek bir yazıyı kaybettiğinde döner

(Not: erken bölüm eski çekirdeklere referans verir; modern çekirdekleri yansıtacak şekilde aşağıda güncellenmiştir)

Hatalarda zaman uyumsuz arabellek yazımı , dosya için hatalı arabellek sayfasında end_buffer_async_write(...)bir -EIObayrak ayarladı gibi görünüyor :

set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);

O zamana tespit edildiği wait_on_page_writeback_range(...)tarafından çağrılan olarak do_sync_mapping_range(...)olarak adlandırdığı sys_sync_file_range(...)tarafından çağrılan olarak sys_sync_file_range2(...)C kütüphanesi çağrısının gerçekleştirilmesi fsync().

Ama sadece bir kez!

Bu yorum sys_sync_file_range

168  * SYNC_FILE_RANGE_WAIT_BEFORE and SYNC_FILE_RANGE_WAIT_AFTER will detect any
169  * I/O errors or ENOSPC conditions and will return those to the caller, after
170  * clearing the EIO and ENOSPC flags in the address_space.

ne zaman ileri sürmektedir fsync()döner -EIO(manpage belgelenmemiş) ya da -ENOSPCbu olacaktır hata durumunu temizlemek müteakip böylece fsync()irade rapor başarı sayfaları yazılı got asla rağmen.

wait_on_page_writeback_range(...) Test ettiklerinde hata bitlerini yeterince temizler :

301         /* Check for outstanding write errors */
302         if (test_and_clear_bit(AS_ENOSPC, &mapping->flags))
303                 ret = -ENOSPC;
304         if (test_and_clear_bit(AS_EIO, &mapping->flags))
305                 ret = -EIO;

Bu nedenle, uygulama fsync()başarılı olana kadar yeniden deneyebilir ve verilerin diskte olduğuna güvenebilirse, çok yanlıştır.

Bu DBMS bulduğum veri bozulması kaynağı olduğuna eminim. Yeniden dener fsync()ve başarılı olduğunda her şeyin iyi olacağını düşünür.

Buna izin veriliyor mu?

Üzerindeki POSIX / SuS dokümanlarıfsync() bunu her iki şekilde de belirtmez:

Fsync () işlevi başarısız olursa, bekleyen G / Ç işlemlerinin tamamlandığı garanti edilmez.

Linux'un man sayfasıfsync() , başarısızlık durumunda ne olduğu hakkında hiçbir şey söylemez.

Yani fsync()hataların anlamı "yazdıklarınıza ne olduğunu bilmiyorum, çalışmış olabilir ya da olmayabilir, emin olmak için tekrar denemek daha iyi" olabilir.

Daha yeni çekirdekler

Sayfada 4,9 end_buffer_async_writesette -EIO, sadece üzerinden mapping_set_error.

    buffer_io_error(bh, ", lost async page write");
    mapping_set_error(page->mapping, -EIO);
    set_buffer_write_io_error(bh);
    clear_buffer_uptodate(bh);
    SetPageError(page);

Senkronizasyon tarafında, bence benzer, ancak yapıyı takip etmek oldukça karmaşık. filemap_check_errorsiçinde mm/filemap.cşimdi yapar:

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;

aynı etkiye sahip. Hata kontrolleri tüm filemap_check_errorstestlerden geçiyor gibi görünüyor :

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;
    return ret;

btrfsDizüstü bilgisayarımda kullanıyorum , ancak ext4test etmek için bir geri döngü oluşturduğumda /mnt/tmpve üzerinde mükemmel bir prob oluşturduğumda:

sudo dd if=/dev/zero of=/tmp/ext bs=1M count=100
sudo mke2fs -j -T ext4 /tmp/ext
sudo mount -o loop /tmp/ext /mnt/tmp

sudo perf probe filemap_check_errors

sudo perf record -g -e probe:end_buffer_async_write -e probe:filemap_check_errors dd if=/dev/zero of=/mnt/tmp/test bs=4k count=1 conv=fsync

Aşağıdaki çağrı yığını bulmak perf report -T:

        ---__GI___libc_fsync
           entry_SYSCALL_64_fastpath
           sys_fsync
           do_fsync
           vfs_fsync_range
           ext4_sync_file
           filemap_write_and_wait_range
           filemap_check_errors

Bir okuma, evet, modern çekirdeklerin aynı şekilde davrandığını gösterir.

Bu, fsync()(veya büyük olasılıkla write()veya close()) dönerse -EIO, dosyanın başarılı bir şekilde son fsync()d veya close()d ile en son write()on durumu arasında tanımlanmamış bir durumda olduğu anlamına gelir .

Ölçek

Bu davranışı göstermek için bir test örneği uyguladım .

etkileri

Bir DBMS, çökme kurtarma işlemi girerek bununla başa çıkabilir. Normal bir kullanıcı uygulamasının bununla nasıl başa çıkması gerekiyor? fsync()Man sayfasında bununla "Sen-his-like-it fsync-if-" ve ben bir bekliyoruz demektir hiçbir uyarı verir sürü uygulamaları içeren bu davranışı ile ne olur.

Hata raporları

daha fazla okuma

lwn.net, "Geliştirilmiş blok katmanı hata işleme" makalesinde bu konuya değindi .

postgresql.org posta listesi iş parçacığı .


3
lxr.free-electrons.com/source/fs/buffer.c?v=2.6.26#L598 olası bir yarıştır, çünkü {henüz planlanmamış G / Ç} için değil, {beklemede ve zamanlanmış G / Ç} için bekler. Bu, cihaza ekstra gidiş gelişlerden kaçınmak içindir. (Kullanıcı yazmaları () I / O zamanlanana kadar geri dönmediğini varsayıyorum, mmap () için bu farklı)
joop

3
Başka bir işlemin aynı diskteki başka bir dosya için fsync'e çağrılması hata döndürmeyi alabilir mi?
Random832

3
@ Random832 PostgreSQL gibi çok işlemli bir DB için çok alakalı, bu yüzden iyi bir soru. Muhtemelen benziyor, ama çekirdek kodunu anlayacak kadar iyi bilmiyorum. Yine de her ikisinde de aynı dosya açıksa procs'larınızın birlikte çalışması daha iyi olurdu.
Craig Ringer

1
@DavidFoerster: Syscalls negatif errno kodları kullanarak hataları döndürür; errnotamamen kullanıcı alanı C kütüphanesinin bir yapısıdır. (Craig Zil yukarıdaki gibi) bir hata dönüş değeri güvenilir tanımlar on (sistem çağrısı veya Cı kütüphane işlevi) olarak ifade edildiği için, sistem çağrıları ve bunun gibi Cı-kütüphane arasındaki dönüş değeri farkları göz ardı etmek yaygındır: " -1ile errno==EIO"bir C kütüphanesi işlevine karşılık gelirken" -EIO"bir sistem çağrısına karşılık gelir. Son olarak, çevrimiçi Linux man sayfaları Linux man sayfaları için en güncel referanstır.
Nominal Hayvan

2
@CraigRinger: Son sorunuza cevap vermek için: "Düşük düzey G / Ç ve fsync()/ fdatasync()işlem boyutu tam bir dosya olduğunda; mmap()/ msync()işlem boyutu sayfaya göre hizalanmış bir kayıt olduğunda / kullanarak ve düşük düzey I kullanarak / O, fdatasync()ve aynı dosyaya birden çok eşzamanlı dosya tanımlayıcısı (işlem başına bir tanımlayıcı ve bir iş parçacığı) aksi takdirde " . Linux'a özgü açık dosya açıklama kilitleri ( fcntl(), F_OFD_) sonuncusunda çok kullanışlıdır.
Nominal Hayvan

22

Uygulamanın write () yöntemi hatasız olarak geri döndüğünden, bir hatayı uygulamaya geri bildirmenin bir yolu yoktur.

Katılmıyorum. writeyazma basitçe sıraya alınmışsa hata olmadan geri dönebilir, ancak hata disk üzerinde gerçek yazıyı gerektiren bir sonraki işlemde rapor edilir, yani bir sonraki yazmada fsync, sistem önbelleği temizlemeye karar verirse ve muhtemelen en az son dosya kapatıldığında.

Bu nedenle, uygulamanın olası yazma hatalarını algılamak için close'un dönüş değerini test etmesi önemlidir.

Gerçekten akıllı hata işleme yapabilmeniz gerekiyorsa, son başarılıdan beri yazılan her şeyin başarısız fsync olabileceğini ve en azından bir şeyin başarısız olduğunu varsaymalısınız .


4
Evet, bence bu çivi. Bu gerçekten uygulama son doğruladı-başarılı beri tüm çalışmalarını yeniden yapması gerektiğini öneririm fsync()veya close()bir alırsa dosyanın -EIOgelen write(), fsync()ya da close(). Bu eğlenceli.
Craig Ringer

1

write(2) beklediğinizden daha az sağlar. Başarılı bir write()aramanın semantiği hakkında kılavuz sayfası çok açık :

Başarılı bir dönüş, write()verilerin diske kaydedildiğini garanti etmez. Aslında, bazı hatalı uygulamalarda, alanın veriler için başarıyla ayrıldığını bile garanti etmez. Emin olmanın tek yolu, fsynctüm verilerinizi yazdıktan sonra (2) 'yi aramaktır.

Başarılı write()olmanın sadece verinin çekirdeğin tamponlama tesislerine ulaştığı anlamına geldiği sonucuna varabiliriz . Arabelleğe devam etmek başarısız olursa, dosya tanımlayıcısına sonraki erişim hata kodunu döndürür. Son çare olarak olabilir close(). close(2) sistem çağrısının kılavuz sayfası aşağıdaki cümleyi içerir:

Önceki write(2) işlemdeki hataların ilk önce finalde close() bildirilmesi oldukça olasıdır .

Uygulamanızın veri yazmaya devam etmesi gerekiyorsa düzenli olarak fsync/ kullanmalıdır fsyncdata:

fsync()fd dosya tanımlayıcısı tarafından belirtilen dosyanın tüm değiştirilmiş çekirdek verilerini (yani, değiştirilmiş arabellek önbellek sayfalarını) disk aygıtına (veya başka bir kalıcı depolama aygıtına) aktarır ("temizler"), böylece değiştirilen tüm bilgiler alınabilir sistem çöktükten veya yeniden başlatıldıktan sonra bile. Bu, varsa bir disk önbelleği aracılığıyla yazma veya temizleme işlemini içerir. Cihaz aktarımın tamamlandığını bildirene kadar arama engellenir.


4
Evet, bunun fsync()gerekli olduğunun farkındayım . Ama çekirdek bir I / O hatası nedeniyle sayfaları kaybeder spesifik durumda olacaktır fsync()başarısız? Daha sonra hangi koşullar altında başarılı olabilir?
Craig Ringer

Çekirdek kaynağını da bilmiyorum. G / Ç sorunlarının fsync()getirilerini varsayalım -EIO(Aksi takdirde neye yarar?). Böylece veritabanı, önceki bir yazmanın başarısız olduğunu ve kurtarma moduna geçebileceğini bilir. İstediğin bu değil mi? Son sorunuzun motivasyonu nedir? Hangi yazma işleminin başarısız olduğunu bilmek mi yoksa daha fazla kullanım için dosya tanımlayıcısını kurtarmak mı istiyorsunuz?
fzgregor

İdeal olarak bir DBMS, büyük olasılıkla önleyebiliyorsa kilitlenme kurtarmaya girmemeyi tercih eder (tüm kullanıcıları tekmelemek ve geçici olarak erişilemez veya en azından salt okunur olmak). Ancak çekirdek bize "fd X'in 4096 ila 8191 byte'ını" söyleyebilse bile, hemen hemen çökme kurtarma işlemi yapmadan oraya ne yazacağımızı anlamak zor olurdu. Ben ana soru artık masum durum ortaya çıkıp çıkmadığını olduğunu tahmin Yani fsync()döndürebilir -EIOnerede olduğunu yeniden deneme güvenli ve farkı söylemek mümkün olup olmadığını.
Craig Ringer

Emin kaza kurtarma son çare. Ancak daha önce de söylediğin gibi bu konuların çok nadir görülmesi bekleniyor. Bu nedenle, herhangi bir zamanda iyileşme konusunda bir sorun görmüyorum -EIO. Her dosya tanımlayıcı bir seferde yalnızca bir iş parçacığı tarafından kullanılıyorsa, bu iş parçacığı sonuncuya geri dönebilir fsync()ve write()çağrıları yeniden yapabilir . Ama yine de, eğer bu write()sektörler sadece bir sektörün bir kısmını yazarsa, değiştirilmemiş kısım hala bozuk olabilir.
fzgregor

1
Çökme kurtarma sürecine girmenin makul olması muhtemeldir. Kısmen bozuk sektörlere gelince, DBMS (PostgreSQL), herhangi bir kontrol noktasından sonra ilk kez dokunduğunda tüm sayfanın bir görüntüsünü saklar, bu yüzden iyi olmalıdır :)
Craig Ringer

0

Dosyayı açarken O_SYNC bayrağını kullanın. Verilerin diske yazılmasını sağlar.

Bu sizi tatmin etmiyorsa, hiçbir şey olmayacak.


17
O_SYNCperformans için bir kabus. Bu, G / Ç iş parçacıklarını oluşturmadıkça disk G / Ç gerçekleşirken uygulamanın başka bir şey yapamayacağı anlamına gelir . Arabelleğe alınan G / Ç arayüzünün güvensiz olduğunu ve herkesin AIO kullanması gerektiğini söyleyebilirsiniz. Kesinlikle sessizce kaybolan yazılar arabelleğe alınmış G / Ç'de kabul edilemez mi?
Craig Ringer

3
( O_DATASYNCbu konuda sadece biraz daha iyidir)
Craig Ringer

@CraigRinger Sen gerektiğini bu ihtiyaç ve performans her türlü gerekiyorsa AIO'yu kullanın. Veya sadece bir DBMS kullanın; sizin için her şeyi halleder.
Demi

10
@Demi Buradaki uygulama bir dbms'dir (postgresql). Arabellek I / O yerine AIO kullanmak için tüm uygulamayı yeniden yazmanın pratik olmadığını hayal edebileceğinizden eminim. Ne de gerekli.
Craig Ringer

-5

Kapatın dönüş değerini kontrol edin. arabelleğe alınan yazma işlemleri başarılı görünürken close başarısız olabilir.


8
Eh, biz neredeyse olmak istiyorum open()ing ve close()birkaç saniyede dosyasını ing. bu yüzden elimizde fsync()...
Craig Ringer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.