요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
Target 제어와 mark
log-writes.rst:54-100Constructor, status, userspace mark와 replay tool 위치를 설명합니다.
Replay 기반 검증
log-writes.rst:101-145fsync checksum 비교와 FUA 경계별 fsck로 durability와 consistency를 검사합니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
=============
dm-log-writes
=============
This target takes 2 devices, one to pass all IO to normally, and one to log all
of the write operations to. This is intended for file system developers wishing
to verify the integrity of metadata or data as the file system is written to.
There is a log_write_entry written for every WRITE request and the target is
able to take arbitrary data from userspace to insert into the log. The data
that is in the WRITE requests is copied into the log to make the replay happen
exactly as it happened originally.
Log Ordering
============
We log things in order of completion once we are sure the write is no longer in
cache. This means that normal WRITE requests are not actually logged until the
next REQ_PREFLUSH request. This is to make it easier for userspace to replay
the log in a way that correlates to what is on disk and not what is in cache,
to make it easier to detect improper waiting/flushing.
This works by attaching all WRITE requests to a list once the write completes.
Once we see a REQ_PREFLUSH request we splice this list onto the request and once
the FLUSH request completes we log all of the WRITEs and then the FLUSH. Only
completed WRITEs, at the time the REQ_PREFLUSH is issued, are added in order to
simulate the worst case scenario with regard to power failures. Consider the
following example (W means write, C means complete):
W1,W2,W3,C3,C2,Wflush,C1,Cflush
The log would show the following:
W3,W2,flush,W1....
Again this is to simulate what is actually on disk, this allows us to detect
cases where a power failure at a particular point in time would create an
inconsistent file system.
Any REQ_FUA requests bypass this flushing mechanism and are logged as soon as
they complete as those requests will obviously bypass the device cache.
Any REQ_OP_DISCARD requests are treated like WRITE requests. Otherwise we would
have all the DISCARD requests, and then the WRITE requests and then the FLUSH
request. Consider the following example:
WRITE block 1, DISCARD block 1, FLUSH
If we logged DISCARD when it completed, the replay would look like this:
DISCARD 1, WRITE 1, FLUSH
which isn't quite what happened and wouldn't be caught during the log replay.
Target interface
================
i) Constructor
log-writes <dev_path> <log_dev_path>
============= ==============================================
dev_path Device that all of the IO will go to normally.
log_dev_path Device where the log entries are written to.
============= ==============================================
ii) Status
<#logged entries> <highest allocated sector>
=========================== ========================
#logged entries Number of logged entries
highest allocated sector Highest allocated sector
=========================== ========================
iii) Messages
mark <description>
You can use a dmsetup message to set an arbitrary mark in a log.
For example say you want to fsck a file system after every
write, but first you need to replay up to the mkfs to make sure
we're fsck'ing something reasonable, you would do something like
this::
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
<run test>
This would allow you to replay the log up to the mkfs mark and
then replay from that point on doing the fsck check in the
interval that you want.
Every log has a mark at the end labeled "dm-log-writes-end".
Userspace component
===================
There is a userspace tool that will replay the log for you in various ways.
It can be found here: https://github.com/josefbacik/log-writes
Example usage
=============
Say you want to test fsync on your file system. You would do something like
this::
TABLE="0 $(blockdev --getsz /dev/sdb) log-writes /dev/sdb /dev/sdc"
dmsetup create log --table "$TABLE"
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
mount /dev/mapper/log /mnt/btrfs-test
<some test that does fsync at the end>
dmsetup message log 0 mark fsync
md5sum /mnt/btrfs-test/foo
umount /mnt/btrfs-test
dmsetup remove log
replay-log --log /dev/sdc --replay /dev/sdb --end-mark fsync
mount /dev/sdb /mnt/btrfs-test
md5sum /mnt/btrfs-test/foo
<verify md5sum's are correct>
Another option is to do a complicated file system operation and verify the file
system is consistent during the entire operation. You could do this with:
TABLE="0 $(blockdev --getsz /dev/sdb) log-writes /dev/sdb /dev/sdc"
dmsetup create log --table "$TABLE"
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
mount /dev/mapper/log /mnt/btrfs-test
<fsstress to dirty the fs>
btrfs filesystem balance /mnt/btrfs-test
umount /mnt/btrfs-test
dmsetup remove log
replay-log --log /dev/sdc --replay /dev/sdb --end-mark mkfs
btrfsck /dev/sdb
replay-log --log /dev/sdc --replay /dev/sdb --start-mark mkfs \
--fsck "btrfsck /dev/sdb" --check fua
And that will replay the log until it sees a FUA request, run the fsck command
and if the fsck passes it will replay to the next FUA, until it is completed or
the fsck command exists abnormally.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
실제 I/O 장치와 write journal 장치
1-12`dm-log-writes` target은 두 장치를 받습니다. 한 장치에는 모든 I/O를 정상적으로 전달하고, 다른 장치에는 모든 write operation을 기록합니다. Filesystem을 write하는 동안 metadata 또는 data integrity를 검증하려는 filesystem developer를 위한 기능입니다.
각 `WRITE` request마다 `log_write_entry` 하나를 기록하며, userspace가 임의 data를 log에 삽입할 수도 있습니다. Replay가 원래 실행과 정확히 같도록 `WRITE` request에 들어 있던 data 자체도 log에 복사합니다.
정상 I/O는 대상 장치로 전달하면서 write 내용과 ordering 정보는 별도 log 장치에 남깁니다.
실제 filesystem 상태와 재현용 journal을 물리적으로 분리합니다.
Completion과 REQ_PREFLUSH에 맞춘 기록 순서
13-40Write가 더 이상 cache에 없다고 확신한 뒤 completion 순서로 항목을 기록합니다. 따라서 일반 `WRITE` request는 실제로 다음 `REQ_PREFLUSH` request가 올 때까지 log에 기록되지 않습니다. Userspace replay가 cache 안의 상태가 아니라 disk의 상태와 대응하게 하여 잘못된 wait 또는 flush 처리를 찾기 쉽게 합니다.
각 `WRITE`가 완료되면 request를 목록에 연결합니다. `REQ_PREFLUSH`를 만나면 그 시점의 완료 목록을 해당 request에 splice합니다. `FLUSH`가 완료된 뒤 연결된 모든 `WRITE`를 먼저 기록하고 마지막에 `FLUSH`를 기록합니다. 전원 장애의 최악 조건을 모사하기 위해 `REQ_PREFLUSH` 발행 시점에 이미 완료된 `WRITE`만 포함합니다.
W1,W2,W3,C3,C2,Wflush,C1,Cflush
W3,W2,flush,W1....
예제에서 C3와 C2는 preflush 전에 끝났지만 C1은 이후에 끝나므로 다음 기록 구간으로 넘어갑니다.
이 순서는 disk에 실제로 존재할 수 있는 상태를 모사하므로, 특정 시점의 전원 장애가 inconsistent filesystem을 만드는 경우를 탐지할 수 있습니다.
`REQ_FUA` request는 device cache를 우회하므로 이 flush 대기 메커니즘도 건너뛰며, request가 완료되는 즉시 기록됩니다.
DISCARD와 WRITE의 상대 순서 보존
41-53`REQ_OP_DISCARD` request는 `WRITE` request와 같은 방식으로 처리합니다. 그렇지 않으면 모든 `DISCARD`, 모든 `WRITE`, 마지막 `FLUSH`의 순서로 뭉쳐 기록되어 실제 operation ordering이 달라질 수 있습니다.
WRITE block 1, DISCARD block 1, FLUSH
위 작업에서 `DISCARD`를 완료 즉시 별도로 기록하면 replay 순서는 다음처럼 바뀝니다.
DISCARD 1, WRITE 1, FLUSH
이 replay 결과는 실제 실행과 다르지만 log replay 검사에서는 차이를 잡지 못하게 됩니다. 따라서 `DISCARD`도 일반 `WRITE`와 같은 completion·flush ordering에 포함해야 합니다.
같은 block의 write와 discard 순서가 뒤집히면 최종 block 상태도 달라질 수 있습니다.
Constructor와 status
54-74Constructor는 정상 I/O를 받을 장치와 log entry를 저장할 장치의 경로를 차례로 받습니다.
i) Constructor
log-writes <dev_path> <log_dev_path>
============= ==============================================
dev_path Device that all of the IO will go to normally.
log_dev_path Device where the log entries are written to.
============= ==============================================
`log-writes` target을 구성하는 두 device path입니다.
Status는 지금까지 기록된 entry 수와 log 장치에서 할당된 가장 높은 sector를 보고합니다.
ii) Status
<#logged entries> <highest allocated sector>
=========================== ========================
#logged entries Number of logged entries
highest allocated sector Highest allocated sector
=========================== ========================
Log의 논리 entry 수와 물리 allocation 진행 위치를 함께 확인합니다.
Userspace mark와 자동 종료 mark
75-94`mark <description>` message를 사용하면 `dmsetup message`로 log의 임의 지점에 mark를 설정할 수 있습니다.
mark <description>
예를 들어 매 write 뒤에 filesystem을 `fsck`하되 먼저 `mkfs` 지점까지 replay해 검사 가능한 초기 filesystem을 만들고 싶다면 다음처럼 사용합니다.
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
<run test>
이 mark를 이용하면 log를 `mkfs`까지 먼저 replay하고, 그 이후부터 원하는 간격으로 replay하며 `fsck`를 수행할 수 있습니다. 모든 log의 끝에는 `dm-log-writes-end`라는 mark가 자동으로 존재합니다.
재현의 기준점과 검사 구간을 이름이 있는 mark로 분리합니다.
Replay userspace 도구
95-100Log를 여러 방식으로 replay하는 userspace tool이 제공되며 다음 project에서 구할 수 있습니다.
It can be found here: https://github.com/josefbacik/log-writes
`dm-log-writes` journal을 실제 block device에 다시 적용하는 도구입니다.
fsync 결과를 checksum으로 검증
101-123Filesystem의 `fsync` 동작을 시험하려면 `/dev/sdb`를 정상 I/O 장치, `/dev/sdc`를 log 장치로 하는 target을 만듭니다. Btrfs를 생성한 직후 `mkfs` mark를 남기고, mount한 filesystem에서 마지막에 `fsync`를 수행하는 test를 실행한 뒤 `fsync` mark와 원본 file의 `md5sum`을 기록합니다.
Target을 제거한 뒤 `replay-log`로 `/dev/sdc`의 log를 `fsync` mark까지 `/dev/sdb`에 replay합니다. 재현된 filesystem을 다시 mount해 같은 file의 `md5sum`을 비교하면 `fsync` 시점의 data가 올바르게 지속됐는지 확인할 수 있습니다.
TABLE="0 $(blockdev --getsz /dev/sdb) log-writes /dev/sdb /dev/sdc"
dmsetup create log --table "$TABLE"
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
mount /dev/mapper/log /mnt/btrfs-test
<some test that does fsync at the end>
dmsetup message log 0 mark fsync
md5sum /mnt/btrfs-test/foo
umount /mnt/btrfs-test
dmsetup remove log
replay-log --log /dev/sdc --replay /dev/sdb --end-mark fsync
mount /dev/sdb /mnt/btrfs-test
md5sum /mnt/btrfs-test/foo
<verify md5sum's are correct>
원래 실행의 fsync 시점과 replay된 같은 시점의 file checksum을 비교합니다.
복잡한 filesystem 작업 전 구간 검사
124-145또 다른 방법은 복잡한 filesystem operation 전체에서 filesystem consistency를 검사하는 것입니다. 예제는 target과 Btrfs를 만들고 `mkfs` mark를 남긴 뒤 `fsstress`로 filesystem을 dirty하게 만들고 Btrfs balance를 실행합니다.
먼저 log를 `mkfs` mark까지 replay하고 `btrfsck`로 초기 상태를 검사합니다. 그다음 `--start-mark mkfs`, `--fsck "btrfsck /dev/sdb"`, `--check fua`를 사용해 `FUA` request를 만날 때까지 replay하고 매 지점마다 fsck command를 실행합니다.
TABLE="0 $(blockdev --getsz /dev/sdb) log-writes /dev/sdb /dev/sdc"
dmsetup create log --table "$TABLE"
mkfs.btrfs -f /dev/mapper/log
dmsetup message log 0 mark mkfs
mount /dev/mapper/log /mnt/btrfs-test
<fsstress to dirty the fs>
btrfs filesystem balance /mnt/btrfs-test
umount /mnt/btrfs-test
dmsetup remove log
replay-log --log /dev/sdc --replay /dev/sdb --end-mark mkfs
btrfsck /dev/sdb
replay-log --log /dev/sdc --replay /dev/sdb --start-mark mkfs \
--fsck "btrfsck /dev/sdb" --check fua
각 fsck가 통과하면 다음 `FUA`까지 replay를 계속합니다. 전체 replay가 끝나거나 fsck command가 비정상 종료할 때 반복을 멈춥니다.
Durability boundary마다 replay를 멈추고 filesystem checker를 실행합니다.
Write journal과 ordering
log-writes.rst:1-53정상 I/O와 replay log를 분리하고 completion·flush 순서에 따라 WRITE·DISCARD·FUA를 기록합니다.