요구사항
애플리케이션에서 어떤 파일을 쓰고, 읽을 수 있어야한다.
파일은 반드시 완결된 내용을 갖춰야한다.
즉, "쓰는 중" 과 같은 상태는 허용하지 않는다.
구현 방법
/data/
|--- data.snapshot
|--- data.snapshot.tmp
data.snapshot.tmp파일에 데이터를 쓴다.data.snapshot.tmp파일 쓰기를 완료한다.data.snapshot.tmp파일을data.snapshot파일로 이름을 변경한다.data.snapshot파일이 있는 경우, 덮어쓰기한다.
이런 순서로 구현할 수 있다.
.tmp 는 왜 필요할까?.tmp 파일 이름은 temporary 임시파일로부터 왔다.
그럼 '임시' 파일이란게 왜 필요한지를 생각해보자.
파일의 완결성과 가시성
파일에 데이터를 '쓰는 중'이라면 어떻게될까?
문자열 "a b c d e" 를 3차례 걸쳐서 써보자
a b
a b c
a b c d e
문자열 "a b c d e" 를 모두 쓰고 나서는 반드시 파일을 저장해야한다.
저장된 파일을 열면 다음과 같이 써있을 것이다.
a b c d e
근데, a b c 까지만 쓴 상황에서 누군가 파일을 열었다고 가정해보자
a b c
이런 상태를 허용해선 안된다.
요구사항에서 파일은 완결된 상태를 가져야 한다고 했다.
여기서 완결이란, "모든 데이터가 파일에 쓰고 저장한 상태" 를 의미한다.
.tmp 파일이 필요한 이유
파일을 읽는 프로세스는 파일명 data.snapshot 를 읽는다.
파일을 읽는 프로세스에서는 이 파일이 쓰다만 파일인지, 완결된 파일인지 모른다. 그저 읽을 뿐이다.
읽기 프로세스는 .tmp 파일에 대한 visibility (가시성) 이 없는 상태다.
data.snapshot -> 완결된 상태만 허용data.snapshot.tmp -> 쓰는 '중' 인 상태 허용
읽기 프로세스는 data.snapshot.tmp 파일에 접근하지 않는다.
대신 쓰기 프로세스가 data.snapshot.tmp 파일에 다음과 같이 쓸 수 있다.
a b
a b c
a b c d e
data.snapshot.tmp 파일에 a b c d e 를 모두 입력 완료하여 파일을 저장했다.
이제 간단하다.data.snapshot.tmp 파일을 data.snapshot 파일로 덮어쓰면 끝이다.
기존 data.snapshot.tmp 파일은 제거하면 된다.
이런 식으로, 일부만 써진 상태 (Write In Process) 를 없애 완결된 파일만 쓰고, 읽을 수 있도록 제한할 수 있다.


실전 코드
위에서 설명한 방식을 Kotlin 코드로 작성해보자.
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
class FileManager {
fun saveSnapshot() {
Files.createDirectories(baseDir)
val tmp = baseDir.resolve(SNAPSHOT_TMP)
val target = baseDir.resolve(SNAPSHOT)
DataOutputStream(BufferedOutputStream(
Files.newOutputStream(tmp),
BUFFER_SIZE)
).use { out ->
out.write() // ..
}
}
// Atomic rename within the same dir: a reader sees either the old snapshot or the complete new one.
// No fsync — the snapshot is a startup optimization, rebuildable from the Kafka dedup-store topic.
Files.move(
tmp,
target,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING
)
}
companion object {
const val SNAPSHOT = "data.snapshot"
const val SNAPSHOT_TMP = "data.snapshot.tmp"
}
}
위 함수는 data.snapshot.tmp 파일에서 data.snapshot 파일로 bytes 를 복사하지 않는다.
파일명 변경 명령어 (rename)는 메타데이터 명령어로, 기존에 data.snapshot 파일의 inode/data 를 원자적으로 새로운 data.snapshot 파일을 가리키도록 변경한다.
Windows, Linux 등의 운영체제는 rename 명령어를 Atomic 하게 설계해놓았다.
이렇게하면 아래 2가지 상황만 가능하다.
- 덮어쓰기 전에 파일 읽기 -> 이전 버전의
data.snapshot파일 읽기 - 덮어쓰기 후 파일 읽기 -> 새로운 버전의
data.snapshot파일 읽기
어느쪽이든 완결된 파일을 읽는다.
Atomic 한 연산으로 써지기 때문에 "일부만 써진" 파일이 존재할 수 있는 시간이 아예 없다.
'LINUX > Linux' 카테고리의 다른 글
| [Linux] 중복 파일 제거 솔루션 'fdupes' (0) | 2024.04.28 |
|---|---|
| [Linux] ufw 방화벽 설정 (0) | 2021.12.20 |
| [Linux] AppImage 파일 (0) | 2021.12.02 |
| [Linux] node.js 최신버전 설치 (0) | 2021.05.20 |
| rpm package with command (0) | 2020.11.10 |