파일을 트랜잭션 방식으로 복사하는 방법은 무엇입니까?

파일을 트랜잭션 방식으로 복사하는 방법은 무엇입니까?

다른 파일 시스템에 있을 수 있는 A에서 B로 파일을 복사하고 싶습니다.

몇 가지 추가 요구 사항이 있습니다.

  1. 복사본은 전부이거나 전무하며, 충돌 시 파일 B의 일부 또는 손상된 부분을 남기지 않습니다.
  2. 기존 파일 B를 덮어쓰지 마십시오.
  3. 동일한 명령의 동시 실행과 경쟁하지 마십시오. 최대 하나만 성공할 수 있습니다.

나는 이것이 가깝다고 생각한다:

cp A B.part && \
ln B B.part && \
rm B.part

그러나 3. B.part가 존재하면(-n 플래그가 있어도) 위반되며 cp는 실패하지 않습니다. 이후 1. 다른 프로세스가 cp를 "승리"하고 제자리에 링크된 파일이 불완전한 경우 실패할 수 있습니다. B.part가 관련 없는 파일일 수도 있지만 다른 숨겨진 이름을 시도하지 않고 이 경우 실패하게 되어 기쁩니다.

나는 bash noclobber가 도움이 된다고 생각합니다. 이것이 전혀 작동합니까? bash 버전이 필요하지 않은 방법이 있습니까?

#!/usr/bin/env bash
set -o noclobber
cat A > B.part && \
ln B.part B && \
rm B.part

후속 조치를 취하면 일부 파일 시스템이 어쨌든 실패할 것이라는 것을 알고 있습니다(NFS). 이러한 파일 시스템을 감지할 수 있는 방법이 있습니까?

관련되어 있지만 동일하지 않은 다른 질문:

파일 시스템 간 대략적인 원자 이동?

내 fs에 mv 원자가 있습니까?

eMMC의 tempfs에서 ext4 파티션으로 파일과 디렉터리를 원자적으로 이동하는 방법이 있습니까?

https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html

답변1

rsync작업을 수행합니다. 기본적으로 임시 파일이 생성되고 O_EXCL(사용하는 경우에만 비활성화됨 --inplace) renamed대상 파일을 덮어씁니다. --ignore-existingB가 있는 경우 덮어쓰지 않는 데 사용됩니다 .

사실 저는 ext4, zfs, 심지어 NFS 마운트에서도 문제가 발생한 적이 없습니다.

답변2

답변3

NFS에 대해 질문하셨습니다. 이러한 코드는 검사에 noclobber두 개의 별도 NFS 작업(파일 존재 여부 확인, 새 파일 생성)이 포함되고 두 개의 별도 NFS 클라이언트에서 두 프로세스가 경합 상태에 빠질 수 있으므로 NFS에서 중단될 가능성이 높습니다. 성공(둘 다 이미 존재하지 않는지 확인 B.part하고 성공적으로 생성을 진행하여 결과적으로 서로를 덮어씁니다.)

작성 중인 파일 시스템이 원자성과 같은 기능을 지원하는지 확인하는 보편적인 검사는 실제로 없습니다 noclobber. NFS인 경우 파일 시스템 유형을 확인할 수 있지만 이는 단지 경험적 방법일 뿐 반드시 보장되는 것은 아닙니다. SMB/CIFS(Samba)와 같은 파일 시스템도 동일한 문제를 겪을 수 있습니다. FUSE를 통해 노출된 파일 시스템은 제대로 작동할 수도 있고 작동하지 않을 수도 있지만 이는 구현에 따라 크게 달라집니다.


B.part아마도 더 나은 접근 방식 은 에 의존할 필요가 없도록 고유한 파일 이름을 사용하여(다른 에이전트와 작업하여) 단계적으로 충돌을 피하는 것입니다 noclobber. 예를 들어 호스트 이름, PID 및 타임스탬프(+는 임의의 숫자일 수 있음)를 포함할 수 있습니다. )을 파일 이름의 일부로 포함합니다. 호스트에는 언제든지 특정 PID로 실행되는 프로세스가 있어야 하므로 고유성이 보장되어야 합니다.

따라서 다음 중 하나:

test -f B && continue  # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
# Maybe check for existance of B again, remove
# the temporary file and bail out in that case.
mv B.part."$unique" B
# mv (rename) should always succeed, overwrite a
# previously copied B if one exists.

또는:

test -f B && continue  # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
if ln B.part."$unique" B ; then
    echo "Success creating B"
else
    echo "Failed creating B, already existed"
fi
# Both cases require cleanup.
rm B.part."$unique"

따라서 두 에이전트 사이에 경쟁 조건이 있는 경우 둘 다 작업을 계속 수행하지만 마지막 작업은 원자성이므로 B가 A의 전체 복사본과 함께 존재하거나 B가 존재하지 않게 됩니다.

mv복사 후 or 연산 전에 다시 확인하여 경합 크기를 줄일 수 있지만 ln여전히 약간의 경합 상태가 있습니다. 그러나 경쟁 조건에 관계없이 두 프로세스가 A에서(또는 소스의 복사본인 유효한 파일에서) B를 생성하려고 한다고 가정하면 B의 내용은 일관되어야 합니다.

첫 번째 경우에는 mv경주가 있을 때 마지막 프로세스가 승자가 됩니다. 왜냐하면이름 바꾸기(2)기존 파일은 자동으로 교체됩니다.

만약에새로운 길이미 존재하는 경우 다른 프로세스가 액세스를 시도할 가능성이 없도록 자동으로 교체됩니다.새로운 길누락된 것을 발견하게 될 것입니다. [...]

만약에새로운 길존재하지만 어떤 이유로 작업이 실패하고 rename()인스턴스를 떠나는 것이 보장됩니다.새로운 길제자리에.

따라서 당시 B를 사용하는 프로세스는 프로세스 중에 다른 버전(다른 inode)을 볼 가능성이 높습니다. 작성자가 동일한 콘텐츠를 복사하려고 하고 리더가 파일의 콘텐츠를 사용하는 경우에는 아마도 괜찮을 것이며 동일한 콘텐츠가 있는 파일에 대해 다른 inode를 얻으면 기뻐할 것입니다.

두 번째 방법은 하드 링크를 사용합니다.것 같다더 좋지만, 많은 동시 클라이언트에서 NFS의 긴밀한 루프로 하드링크를 실험하고 성공을 세었던 것을 기억합니다. 두 클라이언트가 동일한 목적으로 동시에 하드링크 작업을 실행하는 것처럼 보이는 일부 경쟁 조건이 있는 것 같았습니다. 글쎄, 둘 다 성공한 것 같았습니다. (이 동작은 YMMV의 특정 NFS 서버 구현과 관련이 있을 수 있습니다.) 어쨌든 이는 동일한 종류의 경쟁 조건일 수 있으며, 대용량 데이터의 경우 동일한 작업에 대해 두 개의 별도 파일이 생성될 수 있습니다. 파일.인덱스 노드. 작성자 간의 동시성으로 인해 이러한 경쟁 조건이 발생합니다. 작성자가 일관되고(모두 A에서 B로 복사) 독자가 콘텐츠만 소비한다면 충분할 수 있습니다.

마지막으로 폐쇄에 대해 언급하셨습니다. 불행하게도 NFSv3에서는 잠금 기능이 심각하게 부족합니다. (NFSv4에 대해서는 확실하지 않지만 그다지 좋지는 않을 것 같습니다.) 잠금을 고려하고 있다면 실제 파일과 대역 외 다른 분산 잠금 프로토콜을 조사해야 합니다. 하지만 이는 파괴적이고 복잡하며 교착 상태와 같은 문제가 발생하기 쉽기 때문에 피하는 것이 가장 좋다고 생각합니다.


NFS 원자성 주제에 대한 자세한 배경 정보를 보려면 다음을 읽어보세요.Maildir 이메일 형식NFS에서도 잠금을 방지하고 안정적으로 작동하도록 만들어졌습니다. 모든 곳에서 고유한 파일 이름을 유지하여 이를 수행합니다(따라서 끝에 B가 표시되지도 않습니다).

특정 사례에 더 흥미로울 수도 있습니다.Maildir++ 형식Maildir을 확장하여 메일함 할당량에 대한 지원을 추가하고 메일함 내에서 고정된 이름을 가진 파일을 자동으로 업데이트하여 이를 수행합니다(이것은 B에 더 가까울 수 있습니다.) 내 생각에 Maildir++는 NFS에서 실제로 안전하지 않은 추가를 시도하는 것 같습니다. 이와 유사한 프로세스를 사용하는 재계산 방법으로 원자 대체 역할을 합니다.

이 모든 지침이 도움이 되기를 바랍니다!

답변4

cp와 함께 실행하면 올바른 결과가 나옵니다 mv. 이렇게 하면 "B"가 "A"의 새 ​​복사본으로 바뀌거나 "B"가 이전 상태로 유지됩니다.

cp A B.tmp && mv B.tmp B

기존에 맞게 업데이트되었습니다 B.

cp A B.tmp && if [ ! -e B ]; then mv B.tmp B; else rm B.tmp; fi

100% 원자는 아니지만 거의 비슷합니다. 두 가지가 실행되는 경쟁 조건이 있습니다. 둘 다 if동시에 테스트에 들어가고 둘 다 B존재하지 않는 것을 확인한 다음 둘 다 실행합니다 mv.

관련 정보