[c] Linux에서 쓰기 손실을 유발하는 I / O 오류에 대처하기위한 프로그램 작성

TL; DR : 리눅스 커널이 버퍼 된 I / O 쓰기를 잃어버린 경우 , 응용 프로그램을 찾을 수있는 방법이 있습니까?

fsync()내구성 을 위해 파일 (및 상위 디렉토리)에 있어야한다는 것을 알고 있습니다 . 문제는 커널이 I / O 오류로 인해 쓰기 보류중인 더티 버퍼를 잃어 버린 경우 응용 프로그램이 어떻게이를 감지하고 복구하거나 중단 할 수 있습니까?

쓰기 순서와 쓰기 내구성이 중요한 데이터베이스 응용 프로그램 등을 생각하십시오.

글을 잃어 버렸습니까? 어떻게?

어떤 상황에서 리눅스 커널의 블록 층 캔은 잃을 성공적으로 제출 한 I / O 요청 버퍼 write(), pwrite()오류 등으로 등 :

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

(참조 end_buffer_write_sync(...)end_buffer_async_write(...)에서를fs/buffer.c ).

최신 커널에서는 오류에 “lost async page write”가 포함됩니다 .

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

응용 프로그램 write()이 이미 오류없이 리턴되었으므로 오류를 응용 프로그램에 다시보고 할 방법이없는 것 같습니다.

그들을 감지?

나는 커널 소스에 익숙하지 않지만 비동기 쓰기를 수행하는 경우 쓰기에 실패한 버퍼에 설정 한다고 생각 합니다 AS_EIO.

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

그러나 나중에 fsync()파일이 디스크에 있는지 확인할 때 응용 프로그램이 이것에 대해 알 수 있는지 또는 어떻게 알 수 있는지 확실하지 않습니다 .

그것은 모양 wait_on_page_writeback_range(...)mm/filemap.c 의해 힘 do_sync_mapping_range(...)fs/sync.c 의해 호출으로 돌아된다 sys_sync_file_range(...). -EIO하나 이상의 버퍼를 쓸 수없는 경우 반환 합니다.

내가 추측하는 것처럼 이것이 fsync()결과에 전파 되면 응용 프로그램이 패닉 상태에서 I / O 오류가 발생하고 응용 프로그램 fsync()을 다시 시작할 때 작업을 다시 수행하는 방법을 알면 구제되는 경우 충분한 보호 조치가 필요합니까?

앱 이 파일의 어떤 바이트 오프셋이 손실 된 페이지에 해당 하는지 알 수 있는 방법은 없을 것이므로 어떻게 알면 다시 작성할 수는 있지만 앱 fsync()이 파일 의 마지막 성공 이후에 보류중인 모든 작업을 반복 하고 다시 쓰는 경우 파일에 대한 쓰기 손실에 해당하는 더티 커널 버퍼, 손실 된 페이지에서 I / O 오류 플래그를 지우고 다음 fsync()을 완료해야합니다.

그런 다음 창백하고 재 작업하는 것이 너무 과감한 곳으로 fsync()돌아올 수 있는 다른 무해한 상황이 -EIO있습니까?

왜?

물론 이러한 오류는 발생하지 않아야합니다. 이 경우 오류는 dm-multipath드라이버 기본값과 SAN에서 씬 프로비저닝 된 스토리지 할당 실패를보고하기 위해 사용하는 감지 코드 간의 불행한 상호 작용으로 인해 발생했습니다. 그러나 이것이 일어날 있는 유일한 상황은 아닙니다. 예를 들어 libvirt, Docker 등에서 사용되는 씬 프로비저닝 된 LVM에서 보고서를 보았습니다. 데이터베이스와 같은 중요한 응용 프로그램은 모든 것이 제대로 된 것처럼 맹목적으로 수행하기보다는 이러한 오류에 대처해야합니다.

커널 이 커널 패닉으로 죽지 않고 쓰기를 잃어도 좋다고 생각 한다면 , 응용 프로그램은 대처할 방법을 찾아야합니다.

실질적인 영향은 SAN의 다중 경로 문제로 인해 쓰기 손실이 발생하여 DBMS가 쓰기 실패를 알지 못하여 데이터베이스 손상을 일으키는 기록을 잃어버린 경우를 발견했습니다. 재미 없어.



답변

fsync()-EIO커널이 쓰기를 잃은 경우 반환

(참고 : 초기 부분은 이전 커널을 참조하며 최신 커널을 반영하도록 아래에서 업데이트 됨)

실패시 비동기 버퍼 쓰기 가 파일의 실패한 더티 버퍼 페이지에 플래그를 end_buffer_async_write(...)설정하는-EIO 것처럼 보입니다 .

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

다음에 의해 감지되는 wait_on_page_writeback_range(...)호출로 do_sync_mapping_range(...)호출로 sys_sync_file_range(...)호출으로 sys_sync_file_range2(...)C 라이브러리 호출을 구현하는 fsync().

하지만 한 번만!

에 대한 의견 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.

fsync()반환 -EIO하거나 (맨 페이지에 문서화되어 있지 않은 경우 ) 오류 상태-ENOSPC지우 므로 fsync()페이지가 작성되지 않은 경우에도 후속 보고서에서 성공을보고합니다.

wait_on_page_writeback_range(...) 테스트 할 때 오류 비트를 충분히 지 웁니다 .

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;

따라서 응용 프로그램 fsync()이 성공할 때까지 재 시도 할 수 있고 데이터가 디스크에 있음을 신뢰할 수 있다고 예상 하면 매우 잘못됩니다.

이것이 DBMS에서 찾은 데이터 손상의 원인이라고 확신합니다. 다시 시도 fsync()하고 성공하면 모든 것이 잘 될 것이라고 생각합니다.

이것이 허용됩니까?

에 POSIX / SUS 문서는fsync() 정말이 방법을 지정하지 :

fsync () 함수가 실패하면 미해결 I / O 작업이 완료된 것은 아닙니다.

Linux의 맨 페이지는fsync() 장애 발생시 어떤 일도 일어나지 않습니다.

따라서 fsync()오류 의 의미 는 “당신의 글에 무슨 일이 일어 났는지, 전혀 작동하지 않았는지, 더 확실하게 다시 시도하는 것이 좋습니다”라는 것 같습니다.

최신 커널

를 통해 페이지의 4.9 end_buffer_async_write세트 -EIO에서 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);

동기화 측면에서는 구조가 비슷 하기는하지만 비슷하다고 생각합니다. filemap_check_errors에서 mm/filemap.c지금 수행합니다

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

이는 거의 같은 효과가 있습니다. 오류 검사는 모두 잘 수행 filemap_check_errors되는 것으로 보입니다 .

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

btrfs랩톱에서 사용 하고 있지만 ext4테스트를 위해 루프백을 만들고 /mnt/tmp성능 프로브를 설정할 때 :

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

다음과 같은 호출 스택을 찾습니다 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

읽기는 현대 커널이 동일하게 동작한다는 것을 암시합니다.

이 경우 있음을 의미하는 것 fsync()(또는 아마도 write()close()) 반환 -EIO, 파일이 마지막으로 성공적으로 할 때 사이에 정의되지 않은 상태에서 fsync()d 또는 close()그와 가장 최근에 거라고 write()열 상태를.

테스트

이 동작을 보여주기 위해 테스트 사례를 구현했습니다 .

시사점

DBMS는 응급 복구를 입력하여 이에 대처할 수 있습니다. 일반 사용자 응용 프로그램은이 문제에 어떻게 대처해야합니까? fsync()man 페이지는 뜻한다는 경고를주지 않는다 “fsync를-경우 – 당신 느낌 같은-은”내가이 기대하는 많은 응용 프로그램의이 문제에 잘 대처하지 않습니다.

버그 리포트

추가 자료

lwn.net은 “개선 된 블록 레이어 오류 처리”기사에서 이에 대해 언급했습니다 .

postgresql.org 메일 링리스트 쓰레드 .


답변

응용 프로그램의 write ()가 이미 오류없이 반환되었으므로 응용 프로그램에 오류를 다시보고 할 방법이없는 것 같습니다.

난 동의하지 않는다. write쓰기가 큐에 대기중인 경우 오류없이 반환 될 수 있지만, 디스크에 실제 쓰기가 필요한 다음 작업, 즉 fsync시스템이 캐시를 플러시하기로 결정한 경우 다음 쓰기에 대해 오류가보고됩니다. 최소한 마지막 파일을 닫을 때.

이것이 애플리케이션이 가능한 쓰기 오류를 감지하기 위해 close의 리턴 값을 테스트해야하는 이유입니다.

당신이 정말로 당신이 마지막으로 성공한 이후 기록 된 모든 것이 가정해야 처리 영리 오류를 할 수 있어야합니다 경우 fsync 실패하고 모든 적어도 뭔가 실패했음을 것이다.


답변

write(2) 예상보다 적게 제공합니다. 매뉴얼 페이지는 성공적인 write()호출 의 의미에 대해 매우 열려 있습니다 .

에서 성공적으로 복귀 write()한다고해서 데이터가 디스크에 커밋되었다는 보장은 없습니다. 실제로 일부 버그가있는 구현에서는 데이터를위한 공간이 성공적으로 예약되었음을 보장하지도 않습니다. fsync모든 데이터를 작성한 후 (2) 에 전화하는 것이 유일한 방법 입니다.

성공은 write()단순히 데이터가 커널의 버퍼링 기능에 도달했음을 의미 한다고 결론 지을 수 있습니다. 버퍼 유지가 실패하면 파일 디스크립터에 대한 후속 액세스가 오류 코드를 리턴합니다. 최후의 수단으로 close(). close(2) 시스템 호출 의 매뉴얼 페이지 에는 다음 문장이 포함되어 있습니다.

이전 write(2) 작업의 오류 가 최종 close()에 먼저보고 될 수 있습니다.

응용 프로그램에서 데이터 쓰기를 유지해야하는 경우 정기적으로 fsync/ 를 사용해야 fsyncdata합니다.

fsync()파일 디스크립터 fd에 의해 참조 된 파일의 모든 수정 된 인-코어 데이터 (즉, 수정 된 버퍼 캐시 페이지)를 디스크 장치 (또는 다른 영구 저장 장치)로 전송하여 모든 변경된 정보를 검색 할 수 있도록한다. 시스템이 고장 나거나 재부팅 된 후에도 여기에는 디스크 캐시를 쓰거나 플러시하는 것이 포함됩니다. 장치에서 전송이 완료되었다고보고 할 때까지 통화가 차단됩니다.


답변

파일을 열 때 O_SYNC 플래그를 사용하십시오. 데이터가 디스크에 기록되도록합니다.

이것이 당신을 만족시키지 못하면 아무것도 없을 것입니다.


답변

close의 반환 값을 확인하십시오. 버퍼링 된 쓰기가 성공하는 동안 close가 실패 할 수 있습니다.


답변