Pager 라이프사이클 함수: 페이지 고정, 트랜잭션 실행, 그리고 SQLite에서 이를 고정하기
Source: Dev.to
Hello, I’m Maneshwar. I’m working on FreeDevTools online currently building “one place for all dev tools, cheat codes, and TLDRs” — a free, open‑source hub where developers can quickly find and use tools without any hassle of searching all over the internet.
지난 글에서는 페이저의 진입점을 살펴보았습니다: 데이터베이스 열기, 페이지 가져오기, 쓰기 의도 선언, 저널링 준비.
오늘은 제어 함수로 들어갑니다. 이 함수들은 페이지 수명, 트랜잭션 경계, 그리고 커밋 vs 롤백을 관리합니다. 여기서 페이저는 캐시 관리자를 벗어나 SQLite의 트랜잭션 엔진으로 완전히 모습을 드러냅니다.
Source:
페이지 고정하기: sqlite3PagerRef 와 sqlite3PagerUnref
페이지가 트리 모듈에 전달되면, 페이지가 그 아래에서 사라지지 않도록 페이지 관리자가 보장해야 합니다. 여기서 레퍼런스 카운팅이 등장합니다.
sqlite3PagerRef: 페이지 고정
이 함수는 페이지의 레퍼런스 카운트를 증가시키고 페이지를 고정(pinned) 상태로 표시합니다.
고정된 페이지는 건드릴 수 없습니다:
- 페이지가 내보내지지 않음
- 페이지가 재활용되지 않음
- 페이지가 덮어쓰기되지 않음

레퍼런스 카운트가 0이 아닌 한, 페이지 관리자는 해당 페이지의 메모리가 유효하게 유지된다고 보장합니다.
sqlite3PagerUnref: 고정 해제
이 함수는 레퍼런스 카운트를 감소시킵니다. 카운트가 0이 되면 페이지는 고정 해제(unpinned) 상태가 되어 재사용이 가능해지고, 캐시 프리리스트에 배치될 수 있습니다.
여기서 미묘하지만 중요한 동작이 발생합니다: 모든 페이지가 고정 해제될 때
- 페이지 관리자는 데이터베이스 파일에 대한 **공유 잠금(shared lock)**을 해제합니다
- Pager 객체는 중립적이고 유휴(idle) 상태로 돌아갑니다

이러한 방식으로 SQLite는 상위 레이어에서 명시적인 잠금 호출 없이도 필요 이상으로 잠금을 보유하는 일을 방지합니다.
쓰기 트랜잭션 시작: sqlite3PagerBegin
이 함수는 명시적인 쓰기 트랜잭션의 시작을 표시합니다.
동작 내용
- 데이터베이스 파일에 예약 잠금을 획득합니다.
- 롤백 저널을 엽니다(데이터베이스가 임시인 경우 제외).
- 페이저를 쓰기 모드로 전환합니다.
데이터베이스가 이미 쓰기용으로 예약되어 있는 경우, 이 함수는 no‑op이 됩니다—이는 sqlite3PagerWrite가 이미 암시적 쓰기 트랜잭션을 시작했을 수 있고, SQLite는 불필요하게 작업을 중복하지 않기 때문에 중요합니다.
페이저는 실제 쓰기가 시작될 때까지 기다리는 대신 배타 잠금을 즉시 획득할 수도 있습니다.

이 선택은 동시성 및 지연 시간에 영향을 미치며, 전적으로 페이저에 의해 관리됩니다.
Source:
커밋 단계 1: 데이터 내구성 확보
SQLite는 두 개의 뚜렷한 단계로 커밋을 수행합니다. 첫 번째 단계는 다음 함수가 담당합니다:
sqlite3PagerCommitPhaseOne
이 단계는 내구성 단계입니다. 페이지 관리자는:
- 데이터베이스 헤더의 file‑change‑counter를 증가시킵니다.
- 롤백 저널을 디스크에 동기화합니다.
- 캐시의 모든 더러운 페이지를 데이터베이스 파일에 기록합니다.
- 데이터베이스 파일 자체를 동기화합니다.
이 단계가 끝나면 데이터베이스 파일에 새로운 데이터가 들어가지만 저널은 여전히 존재하므로 복구가 가능합니다. 이 시점에서 트랜잭션은 충돌 안전(crash‑safe) 상태이지만 아직 최종 확정된 것은 아닙니다.

커밋 단계 2: 승리 선언
두 번째 단계에서 커밋이 완료됩니다:
sqlite3PagerCommitPhaseTwo
이 함수는 저널 파일을 최종 처리합니다—삭제, 잘라내기, 혹은 무효화합니다. 이 작업이 수행되면:
- 복구가 더 이상 필요하지 않음
- 트랜잭션이 공식적으로 완료됨
- 잠금을 안전하게 낮출 수 있음
이와 같이 두 단계로 나뉜 설계 덕분에 SQLite는 커밋 순서의 어느 시점에서든 충돌을 견딜 수 있습니다.

Source: …
롤백: 실패 없이 되돌리기
무언가 잘못되었거나 애플리케이션이 요청할 경우, 페이저는 다른 경로를 택합니다.
sqlite3PagerRollback
이 함수는:
- 롤백 저널에서 원래 페이지 내용을 복원합니다.
- 모든 메모리 내 페이지를 되돌립니다.
- 저널을 종료합니다.
- 배타적 잠금을 공유 잠금으로 낮춥니다.
두 가지 중요한 보장
- 롤백은 실패할 수 없습니다
- 데이터베이스는 일관된 상태로 남습니다
실행 중에 상황이 얼마나 복잡해지든, 롤백은 항상 성공합니다. 이는 SQLite가 제공하는 가장 강력한 정확성 약속 중 하나입니다.
Source: …
Savepoints: Nested Safety Nets
SQLite는 트랜잭션을 평면 구조로 취급하지 않습니다. 모든 SQL 문은 저장점(savepoint) 안에서 실행되며, 애플리케이션도 자체 저장점을 정의할 수 있습니다. 페이지 관리자는 이를 위해 두 가지 함수를 제공합니다.
sqlite3PagerOpenSavepoint
이 함수는:
- 새로운 저장점 핸들러를 생성합니다
- 현재 롤백 저널 위치를 기록합니다
- 그 순간의 데이터베이스 상태를 캡처합니다
저장점은 스택 형태로 동작합니다 – 여러 저장점이 동시에 존재할 수 있습니다.

sqlite3PagerSavepoint – Release 또는 Rollback
같은 함수가 두 가지 매우 다른 동작을 수행합니다. 요청에 따라 달라집니다.
Savepoint Release
- 저장점 핸들러를 파괴합니다
- 저장점 이후에 이루어진 모든 변경 사항을 유지합니다
Savepoint Rollback
- 데이터베이스 상태를 저장점으로 복원합니다
- 저장점 이후에 이루어진 모든 변경을 취소합니다
- 롤백된 저장점과 그 이후에 만든 모든 저장점을 삭제합니다
이 메커니즘을 통해 SQLite는 전체 트랜잭션을 중단하지 않고도 단일 문장을 롤백할 수 있으며, 파일을 다시 열거나 잠금을 재설정할 필요가 없습니다. 모든 작업은 페이지 관리자가 처리합니다.

더 큰 그림
지금 확대해 보면, 패턴이 명확히 보일 것입니다. 페이지 관리자는:
- 페이지 수명 주기를 담당한다
- 트랜잭션 경계를 담당한다
- 저널링을 담당한다
- 내구성을 담당한다
- 복구를 담당한다
- 롤백을 담당한다
상위 레이어는 단순히 요청만 합니다. 올바름을 강제하지 않으며, 페이지 관리자가 이를 수행하도록 의존합니다.
Pager 아크 마무리
지난 몇 개의 게시물에서 우리는 다음과 같이 진행했습니다:
- 저널
- 트랜잭션
- 락
- 세이브포인트
- 모든 것을 연결하는 페이저 함수
SQLite와 관련된 나의 실험 및 직접 실행 예제는 여기에서 확인할 수 있습니다:
lovestaco/sqlite – sqlite‑examples
References
- SQLite Database System: Design and Implementation – Sibsankar Haldar.
- FreeDevTools – A collection of free developer tools.

👉 확인해 보세요:
피드백이나 기여를 환영합니다!
온라인이며 오픈‑소스이고, 누구든지 사용할 수 있습니다.
⭐ GitHub에서 별표를 달아 주세요: