[EXT-014][실무] 파일을 중간 상태 없이 안전하게 바꾸기 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[EXT-014][실무] 파일을 중간 상태 없이 안전하게 바꾸기

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 10회 작성일 26-09-08 04:37

본문

[이번 수업]
파일을 덮어쓰다 멈추면 절반만 기록될 수 있습니다. 같은 폴더의 임시 파일에 완성본을 쓴 뒤 원자적 교체(중간 상태 없이 한 번에 이름을 바꾸는 작업)로 저장합니다.

[선수지식]
파일 경로, JSON, 예외 처리와 `with` 문을 알고 Python 파일을 실행할 수 있으면 됩니다. 원자적은 작업이 전부 되거나 전혀 되지 않는 것처럼 보인다는 뜻입니다.

[학습목표]
1. 직접 덮어쓰기와 임시 파일 교체의 차이를 설명한다.
2. 버퍼를 비우고 디스크 동기화 뒤 대상 파일을 교체한다.
3. 원자성·내구성·동시 실행 제어를 구분한다.

[핵심개념]
대상과 같은 파일시스템에 임시 파일을 만들고 끝까지 기록한 뒤 `os.replace()`로 교체합니다. 같은 폴더를 쓰면 파일시스템 사이 이동 실패를 피할 수 있습니다. 성공한 이름 변경은 POSIX에서 원자적이며 대상이 있으면 교체하지만, 실패 예외는 처리해야 합니다.

`flush()`는 Python 버퍼를 운영체제로 보내고 `os.fsync()`는 저장장치 동기화를 요청합니다. 임시 파일을 닫고 교체해야 Windows의 열린 핸들 문제를 줄일 수 있습니다. 전원 장애까지 고려하면 디렉터리 동기화가 필요할 수 있으므로 플랫폼 문서를 확인하며, 교체는 백업을 대신하지 않습니다.

원자성은 반쪽 파일을 막지만 작성자 충돌은 막지 않습니다. 두 프로세스가 교체하면 마지막 작업이 앞선 변경을 덮을 수 있습니다. 단일 작성자, 잠금, 버전 비교, 데이터베이스 트랜잭션 중 맞는 방법을 추가합니다.

`tempfile.mkstemp()`는 경쟁 조건 없이 임시 파일을 만듭니다. 예외 뒤 임시 경로만 정리합니다.

[따라하기]
아래를 atomic_save.py로 저장해 빈 연습 폴더에서 실행합니다.

```python
import json
import os
import tempfile
from pathlib import Path

target = Path("demo-settings.json")
payload = {"version": 2, "enabled": True}
fd, temp_name = tempfile.mkstemp(
    dir=target.parent, prefix=f".{target.name}.", suffix=".tmp"
)
temp_path = Path(temp_name)

try:
    with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as file:
        json.dump(payload, file, ensure_ascii=False, sort_keys=True)
        file.write("\n")
        file.flush()
        os.fsync(file.fileno())
    os.replace(temp_path, target)
finally:
    if temp_path.exists():
        temp_path.unlink()

loaded = json.loads(target.read_text(encoding="utf-8"))
print("저장됨:", target.exists())
print("버전:", loaded["version"])
print("활성:", loaded["enabled"])
```

macOS/Linux는 `python3 atomic_save.py`, Windows는 `py atomic_save.py`로 실행합니다. 예상 결과입니다.

```text
저장됨: True
버전: 2
활성: True
```

확인 뒤 `demo-settings.json`은 삭제해도 됩니다. Windows에서는 대상 파일 핸들을 닫고 교체 오류를 제한적으로 재시도합니다.

[흔한 실수]
대상 파일을 먼저 지우면 교체 사이에 파일이 없는 순간이 생깁니다. 임시 파일을 다른 디스크에 만들거나, `flush()`만 호출하고 `fsync()`와 교체 오류를 무시하는 것도 문제입니다. 원자적 교체가 여러 작성자의 변경 병합까지 해결한다고 오해하지 마세요.

[보안 주의]
본인 소유의 로컬·격리 환경에서만 실습하세요. 대상은 신뢰한 기준 폴더 안인지 확인합니다. 비밀값이 든 임시 파일은 공유 임시 폴더 대신 권한이 제한된 대상 폴더에 만들고, 오류 로그에 내용을 남기지 않습니다. 심볼릭 링크와 권한 변경 가능성이 있는 신뢰하지 않는 폴더에서는 이 예제를 사용하지 마세요.

[직접 해볼 과제]
payload와 target을 받는 저장 함수를 만드세요. 기존 파일이 있을 때 `version`을 1씩 올려 세 번 저장하고, 폴더에 `.tmp` 파일이 남지 않았는지 확인하세요. 동시에 실행할 경우 마지막 변경이 사라질 수 있는 이유도 두 문장으로 적으세요.

[확인문제]
1. 임시 파일을 대상과 같은 폴더에 만드는 이유는 무엇입니까?
2. 원자적 교체와 여러 작성자의 충돌 방지는 어떻게 다릅니까?
3. `flush()` 뒤에도 `fsync()`를 호출하는 이유는 무엇입니까?

[다음 학습]
다음에는 같은 설정을 여러 프로세스가 고칠 때 버전 확인으로 잃어버린 업데이트를 막는 방법을 배웁니다.

[공식 참고 자료]
- Python os.replace 공식 문서: https://docs.python.org/3/library/os.html#os.replace
- Python os.fsync 공식 문서: https://docs.python.org/3/library/os.html#os.fsync
- Python tempfile.mkstemp 공식 문서: https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp
- POSIX rename 명세: https://pubs.opengroup.org/onlinepubs/9799919799/functions/rename.html
- Windows ReplaceFile 공식 문서: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-replacefilew

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
3,899
어제
6,862
최대
16,772
전체
769,850
Copyright © 소유하신 도메인. All rights reserved.