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


138

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가 쓰기 실패를 알지 못하여 데이터베이스 손상을 일으키는 기록을 잃어버린 경우를 발견했습니다. 재미 없어.


1
이 오류 조건을 저장하고 기억하기 위해 SystemFileTable에 추가 필드가 필요할 것 같습니다. 그리고 사용자 공간 프로세스가 후속 호출에서이를 받거나 검사 할 가능성. (fsync ()와 close ()는 이런 종류의 역사적 정보를 반환 합니까?)
joop

감사합니다. 방금 "write () needs close () 또는 fsync ( )는 내구성을 위해 "질문을 읽지 않고?
Craig Ringer

BTW : 커널 소스를 깊이 파고 들어야한다고 생각합니다. 저널링 된 파일 시스템은 아마도 같은 종류의 문제로 고통받을 것입니다. 스왑 파티션 처리는 말할 것도 없습니다. 이것들은 커널 공간에 있기 때문에 이러한 조건을 처리하는 것이 조금 더 어려울 것입니다. 사용자 공간에서 볼 수있는 writev ()도 볼 곳처럼 보입니다. [크레이그에서 : 네 becaus 나는 당신의 이름을 알고, 나는 당신이 완전한 바보가 아니라는 것을 안다;-]
joop

1
나는 그렇게 공평하지 않다는 데 동의합니다. 아아 당신의 대답은 매우 만족스럽지 않습니다. 나는 쉬운 해결책이 없다는 것을 의미합니다 (놀랐습니까?).
장 밥 티스트 Yunès

1
@ Jean-BaptisteYunès True. 내가 작업하고있는 DBMS의 경우 "충돌 및 재실행"이 허용됩니다. 옵션이 아니며 대부분의 앱은 동기 I / O의 끔찍한 성능을 견뎌야하거나 I / O 오류에서 잘못 정의 된 동작 및 손상을 수용해야 할 수도 있습니다.
Craig Ringer

답변:


91

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 메일 링리스트 쓰레드 .


3
lxr.free-electrons.com/source/fs/buffer.c?v=2.6.26#L598 은 {아직 예약되지 않은 I / O}가 아닌 {pending & scheduled I / O}를 기다리기 때문에 가능한 경쟁입니다. 이것은 분명히 장치에 대한 추가 왕복을 피하기위한 것입니다. (I / O가 예약 될 때까지 사용자 writes ()가 반환되지 않는다고 가정합니다. mmap ()의 경우 다릅니다)
joop

3
같은 디스크에있는 다른 파일에 대해 fsync에 대한 다른 프로세스의 호출에서 오류가 발생할 수 있습니까?
Random832

3
@ Random832 PostgreSQL과 같은 다중 처리 DB와 매우 관련이 있으므로 좋은 질문입니다. 아마도 것 같지만 이해하기에 충분히 커널 코드를 모른다. 어쨌든 둘 다 같은 파일이 열려 있으면 당신의 procs는 더 잘 협력했습니다.
Craig Ringer

1
@DavidFoerster : syscall은 음의 오류 코드를 사용하여 실패를 반환합니다. errno완전히 userspace C 라이브러리의 구성입니다. 오류 리턴 값이 어떤 시스템 (syscall 또는 C 라이브러리 함수)을 참조 하는지를 확실하게 식별하기 때문에 (위의 Craig Ringer와 같이) syscall과 C 라이브러리 간의 리턴 값 차이를 무시하는 것이 일반적 -1입니다. errno==EIO"는 C 라이브러리 함수를 나타내고" -EIO"는 syscall을 나타냅니다. 마지막으로 리눅스 매뉴얼 페이지 온라인 는 Linux 매뉴얼 페이지에 대한 최신 참조입니다.
공칭 동물

2
@CraigRinger : 최종 질문에 대답하려면 : "낮은 수준의 I / O 및 fsync()/ 또는 fdatasync()트랜잭션 크기가 완전한 파일 인 경우 mmap()/ msync()거래 크기가 페이지 정렬 된 레코드 인 경우 / 를 사용하는 경우와 낮은 수준의 I를 사용하는 경우 / O, fdatasync()및 여러 개의 동시 파일 디스크립터 (트랜잭션 당 하나의 디스크립터 및 스레드)를 동일한 파일로 전송하십시오 . Linux 관련 열린 파일 설명 잠금 ( fcntl(), F_OFD_)은 마지막 파일 에 매우 유용합니다.
공칭 동물

22

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

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

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

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


4
응, 손톱이다는 것 같아 이것은 실제로 응용 프로그램이에서 마지막으로 성공 fsync()했거나 close()파일을 -EIO가져온 write()경우 fsync()또는 에서 파일을 가져 오면 모든 작업을 다시 수행해야 함을 제안합니다 close(). 재미 있네요
Craig Ringer

1

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

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

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

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

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

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


4
예, 나는 그것이 fsync()필요 하다는 것을 알고 있습니다. 그러나 커널이 I / O 오류로 인해 페이지를 잃는 특정 케이스에 됩니다 fsync()실패? 그러면 어떤 상황에서 성공할 수 있습니까?
Craig Ringer

나는 커널 소스를 모른다. I / O 문제에 대한 fsync()수익 -EIO을 가정 해 봅시다 (그렇지 않으면 어떤 이점이 있습니까?). 따라서 데이터베이스는 일부 이전 쓰기가 실패했음을 알고 복구 모드로 전환 될 수 있습니다. 이것이 당신이 원하는 것이 아닙니까? 마지막 질문의 동기는 무엇입니까? 어떤 쓰기가 실패했는지 알고 싶거나 나중에 사용하기 위해 파일 디스크립터를 복구 하시겠습니까?
fzgregor

이상적으로 DBMS는 충돌을 피할 수있는 경우 응급 복구를 시작하지 않고 (모든 사용자를 시작하고 일시적으로 액세스 할 수 없거나 최소한 읽기 전용이되는) 것을 선호합니다. 그러나 커널이 "fd X의 4096 ~ 8191 바이트"를 말할 수 있다고해도 크래시 복구를 수행하지 않고 거기에 무엇을 다시 쓸 것인지 파악하기는 어렵습니다. 따라서 주요 질문은 다시 시도 하기 안전한 곳으로 fsync()돌아갈 수 있는 더 무고한 상황이 있는지 여부 와 차이를 말할 수 있는지 여부입니다. -EIO
Craig Ringer

확실한 응급 복구는 최후의 수단입니다. 그러나 이미 언급했듯이 이러한 문제는 매우 드물 것으로 예상됩니다. 따라서에 복구로 이동하는 데 문제가 없습니다 -EIO. 각 파일 디스크립터가 한 번에 하나의 스레드에서만 사용되는 경우이 스레드는 마지막으로 돌아가서 호출을 fsync()다시 실행할 수 write()있습니다. 그러나 여전히 해당 write()섹터의 일부만 쓰면 수정되지 않은 부분이 여전히 손상되었을 수 있습니다.
fzgregor

1
응급 복구로가는 것이 합리적 일 수 있습니다. 이 벌금을해야하므로 부분적으로 손상된 분야에 관해서는, DBMS (PostgreSQL은) :), 전체 페이지는 단지 그 이유로 주어진 체크 포인트 이후에 닿을 처음의 이미지를 저장
크레이그 벨소리

0

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

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


17
O_SYNC성능 악몽입니다. 이는 I / O 스레드가 생성되지 않으면 디스크 I / O가 발생하는 동안 응용 프로그램이 다른 작업을 수행 할 수 없음을 의미합니다 . 버퍼링 된 I / O 인터페이스는 안전하지 않으며 모든 사람이 AIO를 사용해야한다고 말할 수도 있습니다. 버퍼링 된 I / O에서 자동으로 손실 된 쓰기를 허용 할 수 없습니까?
Craig Ringer

3
( O_DATASYNC그 점에서 약간 더 낫다)
Craig Ringer

@CraigRinger 이 기능이 필요하고 성능이 필요한 경우 AIO 사용해야합니다. 또는 DBMS를 사용하십시오. 그것은 당신을 위해 모든 것을 처리합니다.
Demi

10
@Demi 여기 응용 프로그램은 dbms (postgresql)입니다. 버퍼 된 I / O 대신 AIO를 사용하도록 전체 응용 프로그램을 다시 작성하는 것은 실용적이지 않다고 생각할 수 있습니다. 필요하지도 않습니다.
Craig Ringer

-5

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


8
글쎄, 우리는 몇 초마다 파일을 open()ing하고 싶고 싶지 않습니다 close(). 그래서 우리는 fsync()...
크레이그 링거
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.