-
Notifications
You must be signed in to change notification settings - Fork 3
System Overview
파일 하나의 버전 이력을 관리하는 라이브러리다. 딱 그거 하나만 한다.
Git처럼 여러 파일을 트래킹하는 저장소도 아니고, 브랜치도 없고, 서버도
없다. SimpleRCS 클래스 하나를 파일 경로나 BytesIO, 혹은 파일과 비슷하게
동작하는 아무 객체(BinaryIO)에 붙이면, 그 스트림 위에 커밋 이력을
차곡차곡 쌓는다.
왜 이런 게 필요하냐면 — Git을 쓰기엔 오버헤드가 큰 상황이 있다. 위키
페이지 하나, 설정 파일 하나, DB 컬럼에 박아넣을 문서 하나의 히스토리를
관리하고 싶을 때, .git 디렉토리를 통째로 만들고 객체 그래프를 유지하는
건 낭비다. SimpleRCS는 그 자리에 파일 하나(혹은 DB의 BLOB 컬럼 하나)로
들어간다.
전통적인 RCS와 같은 발상이다. HEAD(최신 버전)는 항상 전체 텍스트로 저장하고, 그보다 오래된 버전들은 "한 단계 최신 버전에서 이 버전으로 되돌리는 방법"만 델타로 저장한다.
[V1 델타] [V2 델타] ... [Vn-1 델타] [Vn 전체 텍스트]
^^^^^^^^^^^^^^
HEAD
이렇게 하면 두 가지가 자연스럽게 따라온다.
- 최신 버전 읽기가 항상 빠르다. 실무에서 제일 자주 읽는 게 최신 버전이니까, 거기에 최적화하는 게 맞다. O(1).
- 과거 버전 읽기는 거리에 비례해서 느려진다. HEAD에서 5버전 전이면 델타 5개를 순서대로 적용해야 한다. O(k), k = HEAD와의 거리.
commit()을 호출하면:
- 지금 HEAD(전체 텍스트)를 읽는다
- "새 내용 → 지금 HEAD"로 되돌리는 델타를 계산한다
- 그 델타로 기존 HEAD 블록을 덮어쓴다 (전체 텍스트였던 게 델타로 바뀜)
- 새 내용을 전체 텍스트로 그 뒤에 추가한다
즉 매 커밋마다 손대는 부분은 "마지막 블록 하나 재작성 + 새 블록 추가"뿐이다. 그 앞의 모든 이력은 파일에서 원래 있던 자리 그대로 남아있다. 히스토리 전체를 다시 쓰는 일은 절대 없다. 자세한 코드 레벨 설명은 Storage Format에 있다.
commit()에 str을 넘기면 RCS 스타일 라인 델타(diff -n 계열)로,
bytes를 넘기면 BSDIFF40 호환 바이너리 패치로 저장한다. 호출하는
쪽에서는 그냥 같은 메서드를 쓰면 되고, 내부적으로 어떤 델타 알고리즘을
쓸지는 타입만 보고 결정한다. 이미지, PDF, 압축 파일 같은 것도 같은
.srcs 포맷 안에 히스토리를 넣을 수 있다는 뜻이다.
v2 포맷부터는 블록마다 "이 블록의 논리적 내용 해시 + 이전 블록 해시"를
같이 저장한다. Git의 커밋 체인이랑 비슷한 생각이다 — 과거 어느 시점의
내용을 조작하면 그 뒤로 이어지는 해시가 전부 안 맞게 되어 verify()로
잡아낼 수 있다. 여기에 더해 GPG 기반 다중 서명자 서명/검증도 붙일 수
있다(simple_rcs_gpg 모듈).
라이브러리 안에는 diff 알고리즘이 여러 개 있다 — 그리디 해시 매칭
(StreamSequenceMatcher), 순수 파이썬 Myers 구현, Cython으로 컴파일한
Myers SES/DMP까지. 그런데 실제로 commit()/checkout() 경로가 쓰는
건 StreamSequenceMatcher 하나뿐이다 — 최단 편집 거리를 보장하진
않지만 빠르다. 나머지는 벤치마크·비교용으로 남아있다. 왜 이렇게
됐는지, 뭐가 더 나은 선택인지는 Diff Engines에서 다룬다.
| 항목 | SimpleRCS | 전통 RCS | Git | 단순 DB 스냅샷 |
|---|---|---|---|---|
| 저장 단위 | 스트림(파일) 하나 | 파일(*,v) 하나 |
객체 DB + 작업 트리 | DB 행 |
| 델타 방향 | 역방향 (HEAD=전체) | 역방향 (HEAD=전체) | 스냅샷 (패킹 시 델타) | 델타 없음 |
| 콘텐츠 | 텍스트 + 바이너리 동일 포맷 | 텍스트 전용 | 뭐든 (blob) | 뭐든 |
| 무결성 | v2 해시체인 + GPG | 없음 | 콘텐츠 주소 기반 | DB 나름 |
| HEAD 조회 | O(1) | O(1) | O(1) | O(1) |
| 과거 조회 | O(k) 백워드 스캔 | O(N) 순방향 | O(log N) | O(1) |
더 자세한 비교는 저장소 루트의 README.md에 있다.
- 여러 파일을 관리하는 저장소가 아니다 (파일 하나당 인스턴스 하나)
- 브랜치/머지 개념이 없다 — 항상 선형 히스토리
- 동시편집 충돌 감지나 파일 잠금이 내장돼 있지 않다. 여러 사용자가 붙는 시스템(예: 위키) 위에 얹으려면 이 부분을 직접 구현해야 한다 — 자세한 내용은 Wiki Backend Design 참고.