서브트랜잭션

서브트랜잭션 (Subtransactions)

트랜잭션 안에서 또 다른 트랜잭션을 시작할 수 있다면? 실패한 부분만 되돌리고 나머지는 계속 진행하고 싶을 때가 있잖아요. PostgreSQL의 서브트랜잭션(subtransaction)이 바로 그걸 가능하게 해줘요. 큰 트랜잭션을 더 작은 단위로 쪼개서 오류 처리(에러 핸들링)를 훨씬 수월하게 만드는 개념이에요.

출처: PostgreSQL 공식 문서 — subxacts

서브트랜잭션이란 (What is a subtransaction)

서브트랜잭션은 트랜잭션 안에서 시작(started inside transactions)돼요. 덕분에 큰 트랜잭션을 더 작은 단위로 쪼갤 수 있죠. 서브트랜잭션은 부모 트랜잭션(parent transaction)에 영향을 주지 않고 커밋(commit)하거나 어보트(abort)할 수 있어요. 즉, 서브트랜잭션 하나가 실패해서 어보트돼도 부모 트랜잭션은 계속 진행할 수 있는 거예요.

이 성질 덕분에 오류 처리를 훨씬 쉽게 할 수 있어요. 실제 애플리케이션 개발에서 아주 자주 쓰이는 패턴이죠. "subtransaction"이라는 단어는 흔히 subxact로 줄여 부르는데, 문서에서도 이 약어를 그대로 써요.

어떻게 시작하나요 (How subtransactions start)

서브트랜잭션은 SAVEPOINT 명령으로 명시적으로 시작할 수 있고, 그 외에도 다른 방식으로 시작되기도 해요. 예를 들어 PL/pgSQL의 EXCEPTION 절이 대표적이에요. PL/Python과 PL/Tcl도 명시적 서브트랜잭션을 지원해요.

게다가 서브트랜잭션 안에서 또 다른 서브트랜잭션을 시작할 수도 있어요. 그러면 최상위 트랜잭션(top-level transaction)과 그 자식 서브트랜잭션들이 하나의 계층(hierarchy) 내지 트리(tree)를 이루게 되죠. 그래서 메인 트랜잭션을 **최상위 트랜잭션(top-level transaction)**이라고 부르는 거예요.

서브트랜잭션 ID (subxid)

서브트랜잭션에 **가상이 아닌 트랜잭션 ID(non-virtual transaction ID)**가 부여되면, 그 ID를 subxid라고 불러요.

여기서 재미있는 규칙들이 몇 가지 있어요.

  • 읽기 전용(read-only) 서브트랜잭션은 subxid를 부여받지 않아요.
  • 그런데 한번 쓰기(write)를 시도하면 그때 subxid가 부여돼요.
  • subxid가 부여되면, 그 subxid의 모든 조상(parent), 즉 최상위 트랜잭션까지 전부 가상이 아닌 트랜잭션 ID를 부여받게 돼요.
  • 그리고 PostgreSQL은 부모 xid가 항상 자식 subxid보다 작은 값을 갖도록 보장해요.

subxid의 기록 (Recording subxids)

각 subxid의 바로 위 부모(parent) xidpg_subtrans 디렉터리에 기록돼요. 다만 두 경우는 예외예요.

  • 최상위 트랜잭션 xid — 부모가 없으므로 기록하지 않아요.
  • 읽기 전용 서브트랜잭션 — 기록하지 않아요.

커밋과 어보트의 전파 (Commit and abort propagation)

서브트랜잭션의 커밋·어보트는 자식 관계에 따라 함께 전파돼요.

  • 서브트랜잭션이 커밋되면, 그 안에 있던 subxid를 가진 커밋된 자식 서브트랜잭션들도 해당 트랜잭션 안에서 "subcommitted"(부분 커밋)된 것으로 간주돼요.
  • 서브트랜잭션이 어보트되면, 그 모든 자식 서브트랜잭션들도 어보트된 것으로 간주돼요.

최상위 레벨로 올라가 보면요.

  • xid를 가진 최상위 트랜잭션이 커밋되면, subcommitted된 모든 자식 서브트랜잭션들도 pg_xact 하위 디렉터리에 영구적으로 커밋으로 기록돼요.
  • 최상위 트랜잭션이 어보트되면, 아무리 subcommitted 상태였더라도 그 아래 모든 서브트랜잭션도 함께 어보트돼요.

오버헤드에 대한 참고 (Note on overhead)

서브트랜잭션은 편리하지만 공짜가 아니에요. 각 트랜잭션이 롤백하거나 릴리스하지 않고 열어둔(open) 서브트랜잭션이 많을수록 트랜잭션 관리 오버헤드가 커져요.

구체적으로, 백엔드(backend)마다 열린 subxid를 최대 64개까지 공유 메모리(shared memory)에 캐시해요. 그 한도를 넘어가면, pg_subtrans에서 subxid 항목을 추가로 조회해야 하므로 저장소 I/O 오버헤드가 크게 증가해요. 오래 열어두는 서브트랜잭션이 너무 많아지지 않도록 신경 쓰는 게 좋겠죠.

더 알아보기 (Learn more)