트랜잭션 파일을 복사하는 방법?


9

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

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

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

나는 이것이 가까워 진다고 생각한다.

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

그러나 B.part가 존재하더라도 (-n 플래그로도) cp가 실패하지 않으면 3.이 위반됩니다. 결과적으로 다른 프로세스가 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). 그러한 파일 시스템을 감지하는 방법이 있습니까?

다른 관련이 있지만 완전히 같은 질문은 아닙니다.

파일 시스템을 통한 대략적인 원자 이동?

mv는 내 fs에 원자입니까?

eMMC에서 파일 및 디렉토리를 tempfs에서 ext4 파티션으로 원자 적으로 이동시키는 방법이 있습니까?

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


2
동일한 명령의 동시 실행 (즉, 도구 내에서 잠글 수 있음) 또는 파일과의 다른 외부 간섭에 대해서만 걱정하십니까?
Michael Homer

3
"거래"가 더 나을 수도 있습니다
muru

1
도구 내에서 @MichaelHomer는 충분하지만 외부는 일을 매우 어렵게 할 것이라고 생각합니다! 파일 잠금으로 가능하다면 ...
Evan Benn

1
@marcelm mv은 기존 파일 B를 덮어 씁니다 mv -n. 실패했음을 알리지 않습니다. B가 이미 존재하면 ln(1)( rename(2))가 실패합니다.
Evan Benn

1
@EvanBenn 좋은 지적! 귀하의 요구 사항을 더 잘 읽었을 것입니다. (기존 대상의 원자 업데이트가 필요한 경향이 있으며 그 점을 염두에두고 답했습니다)
marcelm

답변:


11

rsync이 일을한다. 임시 파일은 O_EXCL기본적으로 생성되고 (를 사용하는 경우에만 비활성화 됨 --inplace) renamed대상 파일 위에 생성됩니다. --ignore-existing존재하는 경우 B를 덮어 쓰지 않는 데 사용하십시오 .

실제로, 나는 ext4, zfs 또는 심지어 NFS 마운트에서도 이것에 대해 어떤 문제도 경험하지 않았습니다.


rsync는 아마도 이것을 훌륭하게 수행하지만 매우 복잡한 매뉴얼 페이지는 나를 놀라게합니다. 다른 옵션을
Evan Benn

내가 말할 수있는 한 Rsync는 요구 사항 # 3에 도움이되지 않습니다. 그럼에도 불구하고이 도구는 훌륭한 도구이므로 약간의 맨 페이지 읽기에서 벗어나지 말아야합니다. github.com/tldr-pages/tldr/blob/master/pages/common/rsync.md 또는 cheat.sh/rsync를 시도 할 수도 있습니다 . (tldr과 cheat는 "man page is TL; DR"이라는 문제를 해결하기위한 두 개의 서로 다른 프로젝트이며, 많은 공통 명령이 지원되며 가장 일반적인 사용법이 표시됩니다.
sitaram

@EvanBenn rsync는 놀라운 도구이며 배울 가치가 있습니다! 매뉴얼 페이지는 매우 다양하기 때문에 복잡합니다. 협박하지 마십시오 :)
Josh

@sitaram, # 3은 pid 파일로 해결할 수 있습니다. 여기답변 과 같은 작은 스크립트가 있습니다 .
Robert Riedl

2
이것이 가장 좋은 대답입니다. Rsync는 원자 파일 전송을위한 업계 표준으로, 다양한 구성에서 모든 요구 사항을 충족시킬 수 있습니다.
wKavey

4

걱정하지 마십시오 . noclobber표준 기능 입니다.


감사합니다. 간결한 답변을 받아 들였습니다. NFS와 같은 파일 시스템에 대한 의견이 있습니까?
Evan Benn

@EvanBenn, NFS가 어떤 식 으로든 당신을 엉망으로 만들지 확실하지 않지만 나는 잊어 버렸습니다.
ilkkachu

4

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또는 ln작업 전에 다시 확인하여 레이스의 크기를 줄일 수 있지만 여전히 작은 경합 상태가 있습니다. 그러나 경쟁 조건에 관계없이 두 프로세스가 모두 A (또는 유효한 파일에서 원본으로 사본)를 작성하려고하면 B의 내용은 일관성이 있어야합니다.

의 첫 번째 상황 mv에서 레이스가 존재할 때 rename (2) 이 기존 파일을 원자 적으로 대체 하므로 마지막 프로세스가이기는 프로세스입니다 .

경우 newpath를가 이미 존재 액세스를 시도하는 다른 프로세스에있는 아무 소용이 없도록, 그것은 원자, 대체됩니다 newpath를가 누락 찾을 수가. [...]

경우 newpath를가 존재하지만 작업이 어떤 이유로 실패, rename()보장의 인스턴스 떠날 하는 newpath을 장소에서.

따라서 당시 B를 소비하는 프로세스는이 프로세스 중에 다른 버전 (다른 inode)을 볼 수 있습니다. 작가가 모두 동일한 내용을 복사하려고 시도하고 독자가 단순히 파일의 내용을 소비하는 경우, 동일한 내용의 파일에 대해 다른 inode를 얻는다면 괜찮을 것입니다.

하드 링크를 사용하는 두 번째 접근 방식 더 좋아 보이지만 많은 동시 클라이언트의 NFS에서 빡빡한 루프로 하드 링크로 실험을하고 성공을 계산 한 것으로 기억합니다. 두 클라이언트가 하드 링크를 발급 한 경우 여전히 경쟁 조건이있는 것처럼 보입니다. 동일한 목적지에서 동시에 작업이 성공한 것 같습니다. (이 동작은 특정 NFS 서버 구현 인 YMMV와 관련이있을 수 있습니다.) 어쨌든 같은 종류의 경쟁 조건 일 수 있습니다.이 경우 동일한 파일에 대해 두 개의 개별 inode가 생길 수 있습니다. 이러한 경쟁 조건을 유발하기 위해 작가 간의 동시성 작가가 일관성 있고 (A를 B로 복사) 독자가 내용 만 소비한다면 충분할 수 있습니다.

마지막으로 잠금을 언급했습니다. 불행히도 잠금은 적어도 NFSv3에서 심각하게 부족합니다 (NFSv4에 대해서는 확실하지 않지만 좋지는 않을 것입니다.) 잠금을 고려하고 있다면 분산 잠금을 위해 다른 프로토콜을 살펴 봐야 할 것입니다. 실제 파일 사본이지만 교착 상태와 같은 문제가 발생하기 쉬우 며 복잡하고 피할 수 있으므로 피하는 것이 좋습니다.


NFS에서 원자 성의 주제에 대한 자세한 배경 정보를 보려면 Maildir 메일 박스 형식 을 읽으십시오.이 형식 은 잠금을 피하고 NFS에서도 안정적으로 작동합니다. 모든 곳에서 고유 한 파일 이름을 유지하여 그렇게합니다 (결국 최종 B도 얻지 못합니다).

아마도 좀 더 흥미로운 특정 케이스의 Maildir 형식 ++ 형식은 사서함 할당량에 대한 지원을 추가 할 Maildir 형식을 확장하고 원자 (즉 가깝게 B로 될 수 있도록) 나는 Maildir 형식 ++ 시도를 생각 사서함 내부에 고정 된 이름의 파일을 업데이트하여 그렇게 추가하는 것은 NFS에서는 실제로 안전하지 않지만 이와 비슷한 절차를 사용하는 재 계산 접근법이 있으며 원자 교체로 유효합니다.

잘만되면이 모든 포인터가 유용 할 것입니다!


2

이를위한 프로그램을 작성할 수 있습니다.

사용 open(O_CREAT|O_RDWD), 대상 파일을 열고 모든 바이트를 읽어 대상 파일이 완전한 하나의 경우 메타 데이터는 두 가지 가능성이있다,없는 경우, 확인하기

  1. 불완전한 쓰기

  2. 다른 프로세스가 동일한 프로그램을 실행 중입니다.

대상 파일에서 열린 파일 설명 잠금을 확보하십시오.

실패는 동시 프로세스가 있음을 의미하며 현재 프로세스가 존재해야합니다.

성공은 마지막 쓰기가 중단되었음을 의미합니다. 파일을 작성하여 다시 시작하거나 수정해야합니다.

또한 fsync()파일을 닫고 잠금을 해제하기 전에 대상 파일에 기록한 후 더 나은 방법 을 사용하십시오. 그렇지 않으면 다른 프로세스가 디스크에서 아직 읽지 않은 데이터를 읽을 수 있습니다.

https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html

이는 동시에 실행중인 프로그램과 마지막으로 충돌 한 작업을 구별하는 데 도움이됩니다.


정보 주셔서 감사합니다, 나는 이것을 직접 구현하고 그것을 갈 것입니다. 일부 coreutils / 비슷한 패키지의 일부로 아직 존재하지 않는다는 것에 놀랐습니다!
Evan Benn

이 접근 방식은 충돌 요구 사항 에 남아있는 부분적이거나 손상된 파일 B를 충족시킬 수 없습니다 . 파일을 임시 이름으로 복사 한 다음 제자리로 옮기는 표준 접근 방식을 사용하는 것이 가장 좋습니다.
reinierpost

@reinierpost 충돌이 발생하지만 데이터가 완전히 복사되지 않으면 부분적으로 복사 된 데이터가 무엇이든 관계없이 남겨집니다. 그러나 내 접근 방식은 이것을 감지하고 수정합니다. 파일 이동은 원자적일 수 없으며, 디스크 교차 물리 섹터에 기록 된 데이터는 원자 적이 지 않지만 소프트웨어 (예 : OS 파일 시스템 드라이버,이 접근 방식)는 파일을 수정하거나 (rw 인 경우) 일관된 상태를보고 할 수 있습니다 (ro 인 경우) 질문의 의견 섹션에 언급 된대로 또한 문제는 복사가 아니라 이동에 관한 것입니다.
炸鱼 薯条 德里克

또한 O_TMPFILE도 보았는데 아마도 도움이 될 것입니다. (FS에서 사용할 수없는 경우 오류가 발생해야 함)
Evan Benn

@Evan 문서를 읽었거나 O_TMPFILE이 파일 시스템 지원에 의존하는 이유를 생각해 보셨습니까?
炸鱼 薯条 德里克

0

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.


mv B.tmp B는 기존 B를 덮어 씁니다. cp A B.tmp는 기존 B.tmp를 모두 실패로 덮어 씁니다.
Evan Benn

mv B.tmp Bcp A B.tmp처음 실행하지 않으면 실행되지 않으며 성공 결과 코드를 반환합니다. 어떻게 실패? 또한, 나는 당신이하고 싶은 cp A B.tmp기존의 B.tmp것을 덮어 쓸 것이라고 동의합니다 . 첫 &&번째 명령이 정상적으로 완료된 경우에만 두 번째 명령이 실행됩니다.
kaan

문제에서 성공은 기존 파일 B를 덮어 쓰지 않는 것으로 정의됩니다. B.tmp를 사용하는 것이 하나의 메커니즘이지만 기존 파일을 덮어 쓰지 않아야합니다.
Evan Benn

답변을 업데이트했습니다. 궁극적으로 파일이 존재하거나 존재하지 않을 때 완전히 100 % 원 자성이 필요하고 여러 스레드가 필요한 경우, 모든 사람이 파일의 일부로 따라가는 어딘가에 하나의 독점 잠금 (특수 파일 작성 또는 데이터베이스 사용 등)이 필요합니다. 복사 / 이동 과정.
kaan

이 업데이트는 여전히 B.tmp를 덮어 쓰며 테스트와 mv 사이에 경쟁 조건이 있습니다. 그렇습니다. 요점은 제대로 희망을 갖지 못하는 일을 올바르게하는 것입니다. 다른 답변은 잠금 및 데이터베이스가 필요하지 않은 이유를 보여줍니다.
Evan Benn
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.