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);
그런 다음 C 라이브러리 호출을 구현하기 위해 호출 한대로 호출 한 사람에 wait_on_page_writeback_range(...)의해 감지됩니다 .do_sync_mapping_range(...)sys_sync_file_range(...)sys_sync_file_range2(...)fsync()
하지만 한 번만!
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's man-page for fsync() just doesn't say anything about what happens on failure.
So it seems that the meaning of fsync() errors is "dunno what happened to your writes, might've worked or not, better try again to be sure".
Newer kernels
On 4.9 end_buffer_async_write sets -EIO on the page, just via 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);
On the sync side I think it's similar, though the structure is now pretty complex to follow. filemap_check_errors in mm/filemap.c now does:
if (test_bit(AS_EIO, &mapping->flags) &&
test_and_clear_bit(AS_EIO, &mapping->flags))
ret = -EIO;
which has much the same effect. Error checks seem to all go through filemap_check_errors which does a test-and-clear:
if (test_bit(AS_EIO, &mapping->flags) &&
test_and_clear_bit(AS_EIO, &mapping->flags))
ret = -EIO;
return ret;
I'm using btrfs on my laptop, but when I create an ext4 loopback for testing on /mnt/tmp and set up a perf probe on it:
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
I find the following call stack in 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
A read-through suggests that yeah, modern kernels behave the same.
This seems to mean that if fsync() (or presumably write() or close()) returns -EIO, the file is in some undefined state between when you last successfully fsync()d or close()d it and its most recently write()ten state.
Test
I've implemented a test case to demonstrate this behaviour.
Implications
A DBMS can cope with this by entering crash recovery. How on earth is a normal user application supposed to cope with this? The fsync() man page gives no warning that it means "fsync-if-you-feel-like-it" and I expect a lot of apps won't cope well with this behaviour.
Bug reports
- https://bugzilla.kernel.org/show_bug.cgi?id=194755
- https://bugzilla.kernel.org/show_bug.cgi?id=194757
Further reading
lwn.net touched on this in the article "Improved block-layer error handling".
postgresql.org mailing list thread.
Since the application's write() will have already returned without error, there seems to be no way to report an error back to the application.
I do not agree. write can return without error if the write is simply queued, but the error will be reported on the next operation that will require the actual writing on disk, that means on next fsync, possibly on a following write if the system decides to flush the cache and at least on last file close.
That is the reason why it is essential for application to test the return value of close to detect possible write errors.
If you really need to be able to do clever error processing you must assume that everything that was written since the last successful fsync may have failed and that in all that at least something has failed.
write(2) provides less than you expect. The man page is very open about the semantic of a successful write() call:
A successful return from
write()does not make any guarantee that data has been committed to disk. In fact, on some buggy implementations, it does not even guarantee that space has successfully been reserved for the data. The only way to be sure is to callfsync(2) after you are done writing all your data.
We can conclude that a successful write() merely means that the data has reached the kernel's buffering facilities. If persisting the buffer fails, a subsequent access to the file descriptor will return the error code. As last resort that may be close(). The man page of the close(2) system call contains the following sentence:
It is quite possible that errors on a previous
write(2) operation are first reported at the finalclose().
If your application needs to persist data write away it has to use fsync/fsyncdata on a regular basis:
fsync()transfers ("flushes") all modified in-core data of (i.e., modified buffer cache pages for) the file referred to by the file descriptor fd to the disk device (or other permanent storage device) so that all changed information can be retrieved even after the system crashed or was rebooted. This includes writing through or flushing a disk cache if present. The call blocks until the device reports that the transfer has completed.
Use the O_SYNC flag when you open the file. It ensures the data is written to the disk.
If this won't satisfy you, there will be nothing.
Check the return value of close. close can fail whilst buffered writes appear to succeed.
'Programming' 카테고리의 다른 글
| Scala 프로젝트에서 sbt vs maven 사용의 장단점 (0) | 2020.06.23 |
|---|---|
| 사용자 정의 필드 용 데이터베이스를 설계하는 방법은 무엇입니까? (0) | 2020.06.23 |
| 왜 Kotlin에서 Java 정적 필드를 대체하기 위해“companion object”를 사용합니까? (0) | 2020.06.23 |
| PDO가있는 PHP에서 최종 SQL 매개 변수화 쿼리를 확인하는 방법은 무엇입니까? (0) | 2020.06.23 |
| 브라우저에서 마우스 휠 속도 정상화 (0) | 2020.06.23 |