AIO fsync가 dpkg 성능을 향상시킬 수 있습니까?

AIO fsync가 dpkg 성능을 향상시킬 수 있습니까?

dpkg데비안 패키지 관리자인 가 sync_file_range() + fsync() 대신 AIO fsync() 작업 중 하나를 사용하여 눈에 띄는 성능 향상을 얻을 수 있습니까 ?

[제안된] fsync2() API는 동기식이라는 점과 애플리케이션이 이를 피하고 싶어한다는 점을 제외하면 기존 AIO_FSYNC/AIO_FDSYNC API와 기본적으로 동일합니다.

AIO_FSYNC [사용]에 대해 내가 제시한 유일한 주장은 "구현은 단지 작업 대기열일 뿐이다"라는 것입니다. 이는 파일 시스템 구현에 독립적이지만 실행된 모든 fsync 작업의 자동 커널 측 병렬화를 허용하기 때문에 대체로 의미가 없습니다. 이를 통해 파일 시스템은 동시 fsync 작업을 완료할 때 불필요한 저널 쓰기를 자동으로 최적화할 수 있습니다. XFS, ext4 등은 사용자 응용 프로그램이 많은 프로세스/스레드에서 동시에 fsync()를 실행할 때 이미 이 작업을 수행합니다....

이 간단한 구현을 통해 XFS에서 간단한 "aio fsync로 untar" 워크로드(예: "많은 4kB 파일과 aio_fsync()를 일괄적으로 작성하고 새 일괄 처리를 보내기 전에 완료된 fsync()를 폐기") 워크로드를 다음에서 이동할 수 있습니다. 약 2,000개 파일/초(동기 쓰기 IO 대기 시간 제한)에서 40,000개 파일/초 이상(백엔드 스토리지에 바인딩된 쓰기 IOPS)입니다.

--데이브 치너

예제 워크로드는 apt-get install또는 와 유사합니다 dpkg -i(부분적으로는 설치된 패키지의 파일 크기에 따라 다름 :-). dpkg압축이 풀린 모든 파일의 이름을 제자리로 바꾸기 전에 효과적으로 fsync()해야 합니다.

dpkgTed T'so의 조언을 사용하여 최적화되었습니다. 최적화는 특정 지점에서 sync_file_range()에 대한 호출을 추가하는 것입니다. 이 시스템 호출은~ 아니다fsync()와 동일한 보장을 제공합니다. 다음에 대한 설명서를 읽어보십시오.sync_file_range()눈에 띄는 경고를 확인하세요 :-).

이러한 작업 중 어느 것도 파일의 메타데이터를 작성하지 않습니다. 따라서 애플리케이션이 이미 인스턴스화된 디스크 블록 덮어쓰기를 엄격하게 수행하지 않는 한 충돌 후 데이터를 사용할 수 있다는 보장은 없습니다.

dpkg를 사용하여 각 파일을 쓴 후 즉시 데이터 쓰기 저장을 트리거합니다 SYNC_FILE_RANGE_WRITE. 먼저 패키지의 모든 파일을 작성합니다. 그런 다음 파일을 통해 두 번째 전달이 이루어지며, 를 사용하여 데이터 쓰기 저장을 기다리고 SYNC_FILE_RANGE_WAIT_BEFORE호출 fsync()하고 마지막으로 파일 이름을 제자리로 바꿉니다.

커밋을 확인하세요:

내 가설은 fsync() 작업을 병렬화하면 더 효율적인 일괄 처리를 허용하여 성능을 향상시킬 수 있다는 것입니다.메타데이터쓰기, 특히 온디스크 메타데이터가 항상 일관되도록 보장하는 데 필요한 관련 장벽/디스크 캐시 플러시를 일괄 처리합니다.

편집: 적어도 ext4 파일 시스템을 사용할 때 내 가설은 너무 단순한 것 같습니다.

두 번째 일련의 sync_file_range() 호출은 SYNC_FILE_RANGE_WAIT_BEFORE이전에 시작된 쓰기 저장이 완료될 때까지 차단됩니다. 이는 기본적으로 지연된 할당이 해결되었음을 보장합니다. 즉, 데이터 블록이 할당 및 기록되고 inode가 업데이트되었지만(메모리에서) 반드시 디스크로 푸시될 필요는 없습니다.

[fsync()] 호출은 실제로 inode를 디스크에 강제로 저장합니다. ext4 파일 시스템의 경우 첫 번째 [fsync()]는 실제로 모든 inode를 디스크에 푸시합니다., 이후의 모든 [fsync()] 호출은 실제로 작동하지 않습니다(파일 'a', 'b' 및 'c'가 모두 동일한 파일 시스템에 있다고 가정). 그러나 이것이 의미하는 바는 (무거운) jbd2 커밋 수를 최소로 최소화한다는 것입니다.

이는 Linux 특정 시스템 호출 --- sync_file_range() ---을 사용하지만 결과적으로 모든 파일 시스템에 대해 전반적으로 더 빠른 성능이 제공됩니다. 따라서 나는 이것이 ext4 특정 해킹이라고 생각하지 않습니다. 하지만 아마도 다른 파일 시스템보다 ext4의 작업 속도가 더 빨라질 것입니다.

--테드 소

대신 AIO fsync() 작업을 사용하면 다른 파일 시스템이 도움이 될 수도 있습니다.

bcachefs(개발 중) ext4보다 훨씬 더 나은 서로 다른 파일 간의 IO를 격리한다고 주장합니다. 그래서 그것은 테스트하기에 특히 흥미로울 수 있습니다.

ext4가 순수 AIO fsync() 패턴에 대해 그렇게 잘 최적화되지 않은 것처럼 들립니다(다른 파일 시스템도 동일한 제약 조건을 가질 수 있다고 생각합니다). 그렇다면 동일한 sync_file_range() 호출을 모두 먼저 수행한 다음 모든 AIO fsync() 작업을 두 번째 라운드로 시작하고 모든 파일의 이름을 fsync()로 변경하여 마무리할 수 있다고 가정합니다. 작업이 완료되었습니다.


오래된:

그러한 조사의 첫 번째 단계는 측정이어야 합니다 :-).

를 사용하여 fsync() 부분을 비활성화할 수 있습니다 echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io.

지금까지 Debian 9 컨테이너에서 , apt-get install아래로 실행해 보았습니다. strace -f -wc예를 들어 "unsafe io"를 사용하여 패키지를 설치하면 aptitude동기식 fsync() 호출이 495개만 있습니다. 정상적으로 설치하는 동안 aptitude1011 fsync() 호출이 발생합니다. "unsafe io"는 또한 SYNC_FILE_RANGE_WAIT_BEFORE호출을 비활성화하여 sync_file_range() 호출 수를 1036에서 518로 줄였습니다.

그러나 이것이 평균 소요 시간을 줄였는지 여부는 훨씬 덜 명확했습니다. 그렇다면 실행 간의 무작위 변동 이상인 것 같지 않습니다. 지금까지 저는 이것을 기계식 HDD의 ext4와 XFS에서 테스트했습니다.


apt-get압축을 푼 518개 파일의 총 크기는 21.7MB라고 합니다(아래 출력 참조).

"unsafe io"를 요청하는 경우에도 계속 존재하는 495 fsync() 호출과 관련하여:

ext4에서 strace 출력은 나머지 fsync() 호출에 소요된 시간을 약 11초로 표시했습니다. XFS에서는 해당 수치가 약 7초였습니다. 모든 경우에 이는 설치에 소요되는 대부분의 시간이었습니다 aptitude.

따라서 "안전하지 않은 io"가 설치에 약간의 개선을 제공하더라도 차이가 실제로 눈에 띄기 전에 시스템의 나머지 부분보다 훨씬 빠른(낮은 대기 시간) 장치에 마운트해야 aptitude하는 것처럼 보입니다 . /var그러나 나는 틈새 시장을 최적화하는 데 관심이 없습니다.

아래에서 실행하면 strace -f -y -e trace=fsync,rename나머지 fsync() 호출의 경우 그 중 2개는 on /etc/ld.so.cache~이고 그 중 493개는 /var/lib/dpkg/패키지 데이터베이스 내부의 파일에 대한 것이었습니다.

fsync() 호출 중 318개는 /var/lib/dpkg/updates/. 이는 dpkg 데이터베이스에 대한 증분입니다 /var/lib/dpkg/status. dpkg 실행이 끝나면 증분은 기본 데이터베이스("체크포인트")로 롤업됩니다.


The following NEW packages will be installed:
  aptitude aptitude-common libboost-filesystem1.62.0 libboost-iostreams1.62.0 libboost-system1.62.0 libcgi-fast-perl libcgi-pm-perl
  libclass-accessor-perl libcwidget3v5 libencode-locale-perl libfcgi-perl libhtml-parser-perl libhtml-tagset-perl libhttp-date-perl
  libhttp-message-perl libio-html-perl libio-string-perl liblwp-mediatypes-perl libparse-debianchangelog-perl libsigc++-2.0-0v5 libsqlite3-0
  libsub-name-perl libtimedate-perl liburi-perl libxapian30
0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.
Need to get 0 B/6000 kB of archives.
After this operation, 21.7 MB of additional disk space will be used.

답변1

질문에 따르면 이것이 ext4 또는 XFS에서는 도움이 되지 않습니다.

또한 훨씬 더 큰 패키지( )를 설치하여 테스트했습니다 linux-image-4.9.0-9-amd64. 에 관계없이 여전히 같은 시간이 걸리는 것 같았습니다 --force-unsafe-io.

ext2

ext2에서는 --force-unsafe-io설치 시간이 linux-image50초에서 13초로 단축되었습니다.

내가 테스트를 실행한 커널 5.0.17-200.fc29.x86_64CONFIG_EXT4_USE_FOR_EXT2.

사용자 공간 aio_fsync() 구현을 사용하여 ext2를 테스트했습니다. 그러나 최고의 개선은 AIO fsync() 사용에 달려 있지 않았습니다.

나의 개선은 실제로 부작용 때문이었습니다. 모든 fsync() 작업을 먼저 수행한 다음 모든 rename() 작업을 수행하도록 dpkg를 변경했습니다. 패치되지 않은 dpkg는 각 fsync() 후에 rename()을 호출했습니다. 나는 최대 256의 AIO 대기열 깊이를 사용했습니다. 대기열 깊이가 1인 AIO fsync()는 동기식 fsync()보다 상당히 느렸습니다. 약간의 오버헤드가 있었던 것 같습니다. 최상의 개선을 위해서는 모든 원래 SYNC_FILE_RANGE_WRITE작업을 먼저 수행해야 했습니다. linux-image약 18초만에 개선된 버전이 설치되었습니다 .

이 작업 순서는 실제로 Ted T'so가 원래 제안한 것입니다 :-D. 에서 CONFIG_EXT4_USE_FOR_EXT2fsync()는 상위 디렉토리도 유용하게 동기화합니다. 모든 파일 이름 조작을 먼저 수행하여 각 디렉터리에 대해 여러 번의 디스크 업데이트를 피할 수 있습니다. 나는 이것이 이전 CONFIG_EXT2구현이나 일반 ext4파일 시스템에서는 발생하지 않는다고 생각합니다.

ext4: 이번에는 저널이 없는 상위 디렉토리를 동기화하기 위해 fsync를 만듭니다.

[...] 여기에는 분명히 ext2 기본 모드도 포함됩니다. [...]

https://elixir.bootlin.com/linux/v5.0.17/source/fs/ext4/fsync.c#L38

 * If we're not journaling and this is a just-created file, we have to
 * sync our parent directory (if it was freshly created) since
 * otherwise it will only be written by writeback, leaving a huge
 * window during which a crash may lose the file.  This may apply for
 * the parent directory's parent as well, and so on recursively, if
 * they are also freshly created.

이전과 마찬가지로 fsync() 단계를 sync()로 대체하면 --force-unsafe-io:-)와 일치하여 놀라울 정도로 좋은 성능을 제공하는 것으로 보입니다. sync() 또는 syncfs()를 사용하지 않고 벗어날 수 있다면 매우 좋은 것 같습니다.

btrfs

btrfs에서 aio_fsync() 테스트를 시작했을 때 최근 데이터 무결성 수정으로 인해 fsync() 작업으로 인해 파일 이름 변경()이 차단될 수 있다는 사실을 발견했습니다. 나는 btrfs에 관심이 없다고 결정했습니다.

fsync()가 먼저 호출될 때 rename()이 더 오래 걸리는 이유는 무엇입니까?

관련 정보