콘텐츠로 이동

(KO) CUBRID 트랜잭션과 복구 — 섹션 개요

목차

이 섹션은 CUBRID code-analysis 트리 안에서, 관계형 엔진이 클라이언트에게 약속하는 ACID 계약을 실제로 짜 두는 부분을 다룬다. txn-recovery/ 안 모든 문서는 그 계약의 한 조각이다.

원자성 (Atomicity). 트랜잭션은 끝까지 커밋되거나, 흔적을 하나도 남기지 않는다. 이 약속은 로그 안의 undo 레코드와 복구 매니저의 undo 패스가 지킨다. 영속성 (Durability). COMMIT이 돌아온 뒤에는 변경이 크래시를 견딘다. 이 약속은 WAL, prior list 커밋 파이프, 주기 체크포인트, 그리고 더블 라이트 버퍼(스토리지 엔진 섹션에 있지만 영속성을 말하면서 빼고 갈 수는 없다)가 함께 지킨다. 격리 (Isolation). 같이 도는 트랜잭션들이 서로의 효과를 정해진 순서로만 본다. 읽기에는 MVCC 스냅샷, 쓰기-쓰기 충돌에는 락 매니저, MVCC가 남기고 간 죽은 버전을 거두는 일에는 vacuum 서브시스템이 붙는다. 일관성 (Consistency). 스키마가 선언한 모든 제약을 보존한다 — 이 책임은 이 레이어 (DDL, 스키마 매니저, 무결성 규칙)에서 진다. 그러나 DML 도중 걸리는 모든 제약 검사가 결국 같은 로그 매니저와 prior list를 거쳐 로그 레코드를 적는다. 그래서 규칙은 다른 데서 갖고 있더라도, 장부 정리는 txn-recovery 섹션이 떠맡는다.

세 개 문서는 같은 도구를 로컬 ACID 너머로 늘려 쓴다. 2PC는 원자성을 여러 서버에 걸쳐 잡는다. 같은 TDES, 같은 로그 레코드 위에 코디네이터/참여자 FSM이 얹히고, 크래시를 견디는 prepared 상태가 더해진다. Flashback은 WAL을 조회 가능한 역사로 바꾼다 — 로그를 복구에 쓰는 것이 아니라, 어느 시간 구간에 무슨 일이 있었는지 보고하는 데 쓰는 길이다. backup-restore는 데이터 볼륨과 LSA로 잘라 둔 로그 구간을 한 봉투에 담아 자기 완결적인 스냅샷으로 만들고, 복구 매니저의 redo 패스로 그 구간 안의 어떤 커밋 경계로든 맞춰 가는 시점 복구(PITR)를 제공한다.

이 개요는 길잡이일 뿐이다. 계약의 어느 조각을 어느 문서가 들고 있는지 호명하고, 런타임에 그것들이 어떻게 맞물리는지 윤곽을 그려 두며, 섹션을 가로지르는 의존을 짚어 둔다. 페이지 버퍼와 DWB는 스토리지 엔진에, 부팅 길은 서버 아키텍처에, 로케이터는 스토리지 엔진에 있지만 로그 매니저를 부르는 쪽이다. 코드 차원의 분석은 11개 세부 문서가 들고 있다. 이 문서는 그 분석을 다시 적지 않는다.

바로 앞 스토리지 엔진 섹션은 모듈을 볼륨 -> 버퍼 -> 레코드 -> 키로 한 칸씩 올라가는 층 구조로 엄격하게 펼쳐 둔다. 트랜잭션과 복구 섹션은 그렇게 깔끔하게 세우기는 어렵다. 이 섹션의 모듈들은 수직 계층이라기보다, 한가운데 놓인 상태 허브(TDES)와 모두가 공유하는 기반(WAL) 주위에 모여 있는 모양에 가깝다. 그래도 ACID 속성으로 잘라 보기 전에 구조 그림 한 장을 먼저 펴 두는 편이 좋다. 이 섹션의 경계가 어디까지인지, 그리고 그 안의 11개 세부 문서가 서로 또 스토리지 엔진과 어떻게 이어지는지를 한눈에 보여 주기 때문이다.

flowchart TB
  subgraph CALLERS["이 섹션을 부르는 쪽 (위 계층)"]
    LOC["locator / DML 진입<br/>(cubrid-locator.md)"]
    DDL["DDL / 스키마<br/>(DDL & 스키마 섹션)"]
    BOOT["부팅 / 기동<br/>(cubrid-boot.md)"]
    XA["XA 클라이언트 / 코디네이터"]
    UTIL["백업 / 복원 CLI<br/>(util_sa.c, util_cs.c)"]
  end

  subgraph HUB["트랜잭션 상태 허브"]
    TX["cubrid-transaction.md<br/>(TDES, trantable, savepoint,<br/>격리 수준 디스패치)"]
  end

  subgraph ISO["격리 평면"]
    MVCC["cubrid-mvcc.md<br/>(MVCCID, 스냅샷,<br/>활성-MVCCID 표)"]
    LM["cubrid-lock-manager.md<br/>(락 테이블, 의향 모드,<br/>waits-for 그래프)"]
    VAC["cubrid-vacuum.md<br/>(WAL 기반 죽은 버전 GC)"]
  end

  subgraph WAL["WAL 파이프라인 (원자성 + 영속성)"]
    PRIOR["cubrid-prior-list.md<br/>(생산자 큐,<br/>group commit)"]
    LOGM["cubrid-log-manager.md<br/>(WAL append, LSA,<br/>로그 리더)"]
    CHKPT["cubrid-checkpoint.md<br/>(fuzzy chkpt,<br/>redo-LSA 힌트)"]
    REC["cubrid-recovery-manager.md<br/>(ARIES 분석 /<br/>redo / undo)"]
  end

  subgraph EXT["확장"]
    P2C["cubrid-2pc.md<br/>(코디네이터/참여자 FSM)"]
    FLASH["cubrid-flashback.md<br/>(시간 여행을 위한 로그 마이닝)"]
    BR["cubrid-backup-restore.md<br/>(온라인 백업 + PITR)"]
  end

  subgraph SE["스토리지 엔진 (별도 섹션, 아래)"]
    PB["page-buffer + DWB<br/>(cubrid-page-buffer-manager.md,<br/>cubrid-double-write-buffer.md)"]
    HEAP["heap / btree / ehash<br/>(레코드 + 키 on-page)"]
    DM["disk-manager / 볼륨"]
  end

  LOC --> TX
  LOC --> HEAP
  DDL --> TX
  DDL --> LM
  BOOT --> REC
  XA --> P2C
  UTIL --> BR

  TX --> MVCC
  TX --> LM
  TX -- "commit / abort 로그" --> PRIOR

  HEAP -- "페이지 변경 로그" --> PRIOR
  PRIOR --> LOGM
  CHKPT --> LOGM
  LOGM --> PB

  MVCC -. MVCC 헤더 읽음 .-> HEAP
  VAC -. WAL을 앞으로 따라감 .-> LOGM
  VAC -. 죽은 버전 회수 .-> HEAP

  REC -. WAL 읽음 .-> LOGM
  REC -. chkpt_lsa에 묶임 .-> CHKPT
  REC -. trantable 재구성 .-> TX
  REC -- "redo 재생" --> PB

  P2C -. 확장 .-> TX
  P2C --> LOGM
  FLASH -. 아카이브 WAL 읽음 .-> LOGM
  BR -. 구동 .-> REC
  BR -. 양 끝 표시 .-> CHKPT

  PB --> DM

그림을 위에서 아래로 따라가 본다.

  • 위 계층의 호출자. txn-recovery/ 안의 모든 일은 결국 다섯 진입점에서 흘러든다. 로케이터(cubrid-locator.md, 스토리지 엔진 소속)는 DML이 가장 자주 지나는 길이다. 트랜잭션 상태가 필요할 때는 TDES를 거치고, 페이지를 직접 손볼 때는 heap이나 B+Tree로 곧장 내려간다. DDL & 스키마도 같은 허브를 부르되, 스키마 안정성 락 때문에 락 매니저까지 함께 끌어들인다. 부팅(cubrid-boot.md)은 그 반대편의 차분한 길이다. 서버가 클라이언트를 받기 전, 기동 도중 단 한 번 복구 매니저를 돌린다. XA 클라이언트는 바깥에서 2PC를 직접 끌고 가고, 백업 / 복원 유틸리티는 standalone 모드에서 같은 복구 매니저를 그대로 다시 쓴다.
  • 트랜잭션 상태 허브 (cubrid-transaction.md). 이 섹션의 다른 모든 문서는 결국 TDES의 어느 자리를 읽거나 쓴다 — trid, MVCCID, 격리 수준, savepoint 사슬, undo-next / postpone-next LSA, 트랜잭션별 락 테이블 커서까지 모두 거기에 모인다. cubrid-transaction.md가 읽는 순서에서 맨 앞자리를 차지하는 이유는 여기 있다. 디스크립터의 모양이 머리에 그려져 있어야 뒤따르는 모듈들이 제자리를 찾는다.
  • 격리 평면. 읽기에는 MVCC, 쓰기-쓰기 충돌과 스키마 안정성에는 락 매니저, 그 둘이 남기고 간 죽은 버전을 거두는 일에는 vacuum이 붙는다. 이 셋은 두 가닥으로 묶여 있다. 한 가닥은 가장 오래된 가시 MVCCID 워터마크다 — vacuum의 멈춤 표지이고, 그 값은 MVCC의 활성 표에서 흘러나온다. 다른 한 가닥은 TDES 자체다. 스냅샷 정체성과 락 테이블 커서를 같은 디스크립터가 함께 들고 다닌다.
  • WAL 파이프라인 (원자성 + 영속성). prior list는 세 갈래 로그를 한 줄로 받는다 — heap이 적는 페이지 변경 레코드, TDES가 적는 commit / abort 레코드, 필요할 때 락 매니저가 적는 레코드. 로그 매니저는 그 줄을 비워서 fsync를 친 뒤, group commit waiter들이 함께 잠들어 있는 영속 LSN 워터마크를 앞으로 옮긴다. 체크포인트는 복구가 거슬러 가야 할 거리를 미리 잘라 둔다. 복구 매니저는 그 모든 도구의 결산이다 — ARIES 세 단계 재시작이 같은 WAL을 앞으로(분석 + redo) 한 번, 뒤로(undo) 한 번, 두 차례 훑어 간다.
  • 확장. 2PC, flashback, 백업 / 복원은 새로운 기반을 짓는 것이 아니다. 같은 trantable, 같은 로그 레코드, 같은 복구 패스를 그대로 두고 목적만 달리한다.
  • 스토리지 엔진 (아래). 섹션 경계를 넘어가는 자리는 세 곳이다. heap 페이지는 로 prior list에 로그 레코드를 올린다. 로그 매니저(그리고 복구 매니저의 redo 패스)는 아래로 page buffer를 거쳐 로그 / 데이터 페이지를 내린다 — 거기서 DWB를 한 번 통과한 뒤 disk manager에 닿는다. MVCC는 페이지 버퍼의 flush 길을 우회해 heap 헤더로 곧장 내려간다.

이어지는 “ACID 렌즈” 절은 같은 11개 모듈을 다른 각도 — 어느 ACID 속성을 주로 떠받치는지 — 에서 다시 들여다본다.

11개 문서를 머리에 넣는 데 쓸 수 있는 한 가지 틀은, 각 문서가 주로 어떤 ACID 속성을 떠받치는지를 묻는 것이다. 거의 모든 문서가 둘 이상에 손을 대지만, 아래 매핑은 가장 큰 몫을 가리킨다.

원자성은 복구 매니저와 로그 매니저가 같이 들고 있다. undo 가능한 변경은 모두 자기를 되돌리기에 충분한 정보를 담아 로그 레코드 한 줄을 적는다(데이터 쪽은 물리-논리 혼합인 physiological, B+Tree 연산은 논리적 logical). 롤백은 TDES 끝에서 시작해 트랜잭션 LSA 사슬을 거슬러 가며 각 레코드의 undo 함수를 적용하고, undo 자체도 다시 시작 가능하도록 보상 로그 레코드(CLR)를 적는다. 재시작 시 원자성도 같은 도구를 쓴다 — 분석 패스가 패자(loser)를 가리고, undo 패스가 그것을 되감는다. 다만 trantable이 앞에서 그대로 따라온 게 아니라 로그에서 다시 짠 상태일 뿐이다. 자세한 내용은 cubrid-log-manager.md(레코드 모양, LSA 명명, 추가 규율)와 cubrid-recovery-manager.md(세 단계 재시작, undo 패스가 복구 시점에 원자성을 굴리는 길)에 있다. cubrid-transaction.md는 롤백과 복구 모두가 따라가는 트랜잭션별 상태 — head/tail/undo-next/postpone-next 사슬을 단 TDES — 가 사는 자리다.

일관성은 대부분 이 섹션 위에서 잡는다. DDL 제약, 외래 키, 유니크 인덱스, 스키마 매니저의 무결성 규칙은 스토리지 엔진과 DDL & 스키마의 영토다. 그러나 DML 도중 걸리는 제약 검사 모두가 cubrid-log-manager.mdcubrid-prior-list.md를 거쳐 로그 레코드를 만들어 내고, 위반으로 끊긴 트랜잭션은 같은 원자성 도구를 거쳐 롤백된다. 그래서 일관성은 이 섹션이 적어 두는 일이라고 보면 된다 — 규칙은 다른 데서 정하지만, 장부는 여기서 도는 셈이다.

격리는 MVCC와 락 매니저의 합작이고, 그 뒤를 vacuum이 따라 정리한다. CUBRID의 기본 격리는 단조 증가 MVCCID와 활성-MVCCID 표 위에 얹은 스냅샷 격리다. 읽는 쪽은 스냅샷을 보고 가며, 쓰기를 막지도 쓰기에 막히지도 않는다 — 이게 MVCC의 핵심 약속이다. 다만 쓰는 쪽은 쓰기-쓰기 충돌을 줄 세우고, lost update를 막고, 스키마 안정성을 지키기 위해 여전히 배타 락을 잡아야 한다. 락 매니저가 그 역할을 한다 — 의향 모드(IS, IX, SIX, 그리고 BU, SCH-S, SCH-M, NON_2PL), 정사각 변환 표, waits-for 그래프 기반 데드락 탐지가 여기 들어 있다. vacuum은 정원사다 — UPDATE마다 옛 버전이 남고, DELETE마다 무덤이 남는데, 살아 있는 어떤 스냅샷도 그것들을 못 보게 될 때까지는 그 자리를 못 다시 쓴다. CUBRID의 vacuum은 가장 오래된 가시(MVCCID) 워터마크 아래에서 WAL을 고정 크기 블록으로 앞쪽으로 따라 가며, 블록마다 일을 워커 풀에 던진다. cubrid-mvcc.md, cubrid-lock-manager.md, cubrid-vacuum.md로 간다.

영속성은 가장 긴 파이프라인이다. 변경은 전역 로그 래치 바깥에서 LOG_PRIOR_NODE를 만든 뒤 prior list에 넘기고, 커밋 시점에는 영속 LSN 워터마크가 자기 꼬리(tail)를 지나칠 때까지 condition variable 위에서 잠을 잔다. 로그 flush 데몬은 로그 임계 구역 안에서 prior list를 비워다가, 정식 로그 페이지에 바이트를 옮겨 적고 fsync를 친다. group commit은 여러 커미터가 같은 CV에서 잠들어 있다가 함께 깨면서 자연스럽게 떨어진다. 페이지 쪽은 체크포인트가 양 끝을 잡아 둔다 — 주기 데몬이 활성 트랜잭션 스냅샷과 redo-LSA 힌트를 담은 LOG_START_CHKPT / LOG_END_CHKPT를 적어 두고, 이로써 log_Gl.hdr.chkpt_lsa가 앞으로 나간다. 다음 분석 패스는 그 아래는 건너뛰면 된다. 데이터 페이지의 영속성은 한 단계 더 — 더블 라이트 버퍼(스토리지 엔진의 cubrid-double-write-buffer.md)가 더해진다. 모든 더티 페이지가 자기 홈에 가기 전에 DWB에 한 번 쌓이므로, 홈에서 찢긴 쓰기가 나도 DWB 사본으로 다시 살릴 수 있다. cubrid-log-manager.md, cubrid-prior-list.md, cubrid-checkpoint.md, cubrid-recovery-manager.md로 들어간다.

서버 사이의 원자성은 cubrid-2pc.md다. 같은 TDES와 로그 도구 위에, 역할(코디네이터 vs 참여자)에 따라 갈리는 LOG_2PC_EXECUTE 열거값이 얹히고, 크래시를 견디는 prepared-state 로그 레코드가 더해진다. ARIES 분석 패스가 in-doubt 트랜잭션을 집어 올려 gtrid → tid 매핑을 다시 짜고, XA 클라이언트가 FSM을 바깥에서 굴린다.

시간 여행 질의는 cubrid-flashback.md다. 두 단계로 로그를 앞쪽으로 따라간다. 1단계는 트랜잭션마다의 요약(trid, 사용자, 시간, INSERT/UPDATE/DELETE 건수, 건드린 클래스)을 만들고, 2단계는 고른 트랜잭션의 행 이미지를 실체화한다. 와이어 포맷은 CDC와 같지만, flashback은 살아 있는 tail이 아니라 아카이브된 로그 볼륨을 읽는다.

백업은 cubrid-backup-restore.md다. 온라인 물리 백업은 데이터 볼륨을 떠 가는 동안 그 양 끝을 start_lsa(현재 체크포인트)와 복사 직후의 LOG_END_CHKPT로 잘라 둔다. 복원은 페이지 이미지를 마운트하고, 복구 매니저의 redo 패스를 사용자가 정한 멈춤 시점까지 앞쪽으로 굴린다. PITR는 벽시계 시각을 받지만, redo 도중에 LSA 단위 멈춤 지점으로 풀린다. 증분 백업은 세 단계까지 늘릴 수 있는데, 부모 백업의 start LSA보다 prv.lsa가 더 오래된 페이지를 건너뜀으로써 사슬을 만든다.

flowchart TB
  subgraph A["원자성 (Atomicity)"]
    A1["cubrid-transaction.md<br/>(TDES, savepoint, LSA 사슬)"]
    A2["cubrid-log-manager.md<br/>(undo 레코드, LSA)"]
    A3["cubrid-recovery-manager.md<br/>(undo 패스, CLR)"]
  end

  subgraph I["격리 (Isolation)"]
    I1["cubrid-mvcc.md<br/>(MVCCID, 스냅샷,<br/>활성-MVCCID 표)"]
    I2["cubrid-lock-manager.md<br/>(의향 모드,<br/>waits-for 그래프)"]
    I3["cubrid-vacuum.md<br/>(WAL 기반 죽은 버전 GC)"]
  end

  subgraph D["영속성 (Durability)"]
    D1["cubrid-log-manager.md<br/>(WAL, 추가 규율)"]
    D2["cubrid-prior-list.md<br/>(커밋 파이프, group commit)"]
    D3["cubrid-checkpoint.md<br/>(fuzzy chkpt, redo-LSA 힌트)"]
    D4["cubrid-recovery-manager.md<br/>(redo 패스)"]
    DWB["cubrid-double-write-buffer.md<br/>(찢긴 쓰기 보호)<br/><i>(스토리지 엔진 소속)</i>"]
  end

  subgraph C["일관성 (Consistency)"]
    CC["위 레이어가 강제<br/>(DDL & 스키마, 무결성 규칙)<br/>다만 장부는 D1+D2를 통과"]
  end

  subgraph X["확장 (Extensions)"]
    X1["cubrid-2pc.md<br/>(서버 사이 원자성)"]
    X2["cubrid-flashback.md<br/>(시간 여행 질의)"]
    X3["cubrid-backup-restore.md<br/>(redo 재생을 통한 PITR)"]
  end

  TDES["TDES<br/>(트랜잭션별 상태 허브)"]

  TDES --> A1
  TDES --> I1
  TDES --> X1

  A2 -. 적어 둔다 .-> D1
  I3 -. WAL 따라간다 .-> D1
  D2 -. 비워 넣는다 .-> D1
  D3 -. 닻이 된다 .-> D4
  D4 -. 사용한다 .-> A3
  DWB -. 페이지 쓰기 양 끝 .-> D1

  X1 -. 확장한다 .-> A1
  X2 -. 아카이브로 읽는다 .-> D1
  X3 -. 사용한다 .-> D4

CUBRID 트랜잭션 스택을 처음 보는 독자라면, 11개 가운데 가장 화려한 복구 매니저부터 펴면 곤란해진다. 복구는 무엇이 복구되는지 — 트랜잭션이 무엇이고, 그 상태가 어떻게 놓여 있고, MVCC 스냅샷이 어떤 모양이고, 복구가 따라가는 로그 레코드가 무엇인지 — 가 머리에 들어와 있을 때 비로소 풀린다. 권하는 순서는 다음과 같다.

  1. cubrid-transaction.md 먼저. TDES, trantable, savepoint와 시스템-op 중첩, 그리고 격리 레벨에서 스냅샷 기반 또는 락 기반 읽기로 갈리는 디스패치가 여기 정의된다. 다음 모든 문서가 TDES 위 필드를 읽거나 쓴다. 디스크립터를 머리에 먼저 넣어 둬야 한다.

  2. cubrid-mvcc.md 다음. MVCCID 발급 방식, 활성-MVCCID 표 유지, 문장(또는 트랜잭션) 시작 시점에 스냅샷이 어떻게 만들어지는지, 행 버전마다의 가시성을 어떻게 정하는지가 여기 들어 있다. CUBRID 격리 이야기에서 읽기 쪽 스냅샷 절반이 이 문서다.

  3. cubrid-log-manager.md 그다음. WAL은 아래 모든 것이 의지하는 바닥이다 — 영속성, 복구, 복제, vacuum, flashback, 백업까지 다 WAL을 쓴다. 레코드 모양(LOG_RECORD_HEADER, 종류별 본문의 union), (page_id, offset)으로 갈리는 LSA, 그리고 prior list에서 로그 페이지 버퍼로 바이트를 넘기는 추가 규율을 잡으러 간다.

  4. cubrid-prior-list.md가 바로 옆에 짝으로 붙는다. 생산자 쪽이 어떻게 로그 임계 구역 바깥에 머무는지를 푼다 — LOG_PRIOR_NODE는 래치 바깥에서 만들어 두고, 짧게 잡는 뮤텍스 아래에서 큐에 매달고, 로그 flush 데몬이 그것을 비운다. group commit과 영속 LSN 워터마크가 여기 산다.

  5. cubrid-checkpoint.md 다음. 뒤이어 오는 두 문서가 전제로 깔고 가는 복구 경계이기 때문이다. fuzzy ARIES 프로토콜, 활성 트랜잭션 스냅샷, 페이지 버퍼 더티 목록에서 뽑아 낸 redo-LSA 힌트, 그리고 log_Gl.hdr.chkpt_lsa가 앞으로 가는 길까지가 여기 들어 있다.

  6. cubrid-recovery-manager.md가 보상이다. ARIES 세 단계 재시작 — chkpt_lsa에서 로그 끝까지 가는 분석, min-redo-LSA에서 앞쪽으로 가는 redo(RV_fun[]을 거쳐 페이지 단위 워커 풀로 병렬화), 패자별 거꾸로 가는 undo. 1~5번이 머리에 들어 있으면, 모든 단계 아래에 익숙한 자료 구조가 깔려 있는 게 보인다.

  7. cubrid-lock-manager.md — MVCC만으로는 부족할 때 문서다. 쓰기-쓰기 충돌, 스키마 안정성 락, 의향 모드, waits-for 그래프 데드락 탐지가 여기 있다. CUBRID의 모드 집합은 교과서적 집합에 BU, SCH-S, SCH-M, NON_2PL을 더한다.

  8. cubrid-vacuum.md가 MVCC 고리를 닫는다. 가장 오래된 가시(MVCCID) 워터마크 아래에서 WAL을 앞쪽으로 다시 굴리고, 고정 크기 vacuum 블록을 마스터/워커가 나눠 받고, 지워진 파일을 따로 추적한다. cubrid-mvcc.md(워터마크 개념이 있어야 한다)와 cubrid-log-manager.md(vacuum이 복구가 읽는 그 WAL을 같이 읽는다) 두 문서를 본 다음에 들어간다.

  9. cubrid-2pc.md — distributed commit이 필요할 때. LOG_2PC_EXECUTE로 굴리는 코디네이터/참여자 FSM, prepared-state 로그 레코드, ARIES 분석 패스에 같이 묶이는 in-doubt 복구가 여기 있다.

  10. cubrid-flashback.md — 로그 마이닝이 필요할 때. 두 단계 앞쪽 워크, 요약 → 상세, CDC가 쓰는 같은 로그 리더로 아카이브 로그 볼륨을 읽는 길이 들어 있다.

  11. cubrid-backup-restore.md — 운영 차원에서 PITR이 필요할 때. 디스크 위 백업 포맷, LSA로 자르는 양 끝, 세 단계 증분, 그리고 복원 시점에 타임스탬프를 LSA로 풀어 가는 길이 정리되어 있다.

9, 10, 11번은 1~8번을 먼저 깔아 두면 서로 독립적으로 읽어도 된다. 셋 다 핵심 도구의 확장이지, 서로의 선행은 아니다.

세 가지 결합이 11개 문서 전체에 걸쳐 반복된다. 매번 새로 발견하는 것보다 이름표를 붙여 외워 두는 편이 빠르다.

MVCC + 락 매니저 — 읽기는 스냅샷, 쓰기는 X-락

섹션 제목: “MVCC + 락 매니저 — 읽기는 스냅샷, 쓰기는 X-락”

CUBRID의 격리는 MVCC만도 락만도 아니다. 정확한 분담이 있다. 읽는 쪽은 MVCC 가시성으로 간다(쓰기에 막히지 않고, 쓰기와 데드락도 안 진다). 쓰는 쪽은 쓰기-쓰기 충돌, lost update 방지, 스키마 안정성을 위해 배타 락을 잡는다. TDES는 스냅샷 정체성에 쓰는 MVCCID와 락 표 커서를 모두 들고 있고, cubrid-transaction.md의 격리 레벨 디스패치가 어느 길로 갈지를 정한다. 그래서 CUBRID는 읽기 경로를 둘로 나누지 않고도 read-committed와 스냅샷 격리 모드를 함께 지원한다 — 같은 코드가 가시성에는 mvcc_snapshot을 보고, 쓰기에는 락 매니저에 들른다. 격리 레벨은 그저 새 스냅샷을 언제 떠야 하는지를 바꿀 뿐이다. cubrid-transaction.md(디스패치), cubrid-mvcc.md(스냅샷), cubrid-lock-manager.md(쓰기 락)로 간다.

로그 + prior list + 체크포인트 + DWB — 영속성 파이프라인

섹션 제목: “로그 + prior list + 체크포인트 + DWB — 영속성 파이프라인”

영속성은 컴포넌트 한 개가 아니다. 4단계 파이프라인이고, 각 단계가 자기만의 약속을 들고 있다. 어느 한 단계만 빼도 계약이 깨진다.

  1. prior list (cubrid-prior-list.md). 생산자는 로그 임계 구역 바깥에서 LOG_PRIOR_NODE를 빚어 두고, 짧게 잡는 뮤텍스로 큐에 매단다. 생산자의 일이 전역 핫패스에서 빠진다.
  2. 로그 매니저 (cubrid-log-manager.md). 로그 flush 데몬이 로그 임계 구역 안에서 prior list를 비우고, 정식 로그 페이지 버퍼에 바이트를 옮겨 적고, fsync를 친다. group commit은 여러 커미터가 영속 LSN CV에서 같이 잠들어 있다가 같이 깨는 그 자리에서 자연스럽게 떨어진다.
  3. 체크포인트 (cubrid-checkpoint.md). 주기적인 LOG_START_CHKPT / LOG_END_CHKPT 한 쌍이 활성 트랜잭션 스냅샷과 redo-LSA 힌트를 담는다. 다음 분석 패스가 어디서부터 시작해도 되는지를 이 한 쌍이 잡아 준다.
  4. 더블 라이트 버퍼 (cubrid-double-write-buffer.md, 스토리지 엔진 소속). 더티 데이터 페이지가 자기 홈 자리에 가기 전에 DWB에 먼저 떨어진다. 홈에서 찢긴 쓰기가 났어도, 재시작 시 DWB 사본으로 되살린다.

계약 내용은 이렇다 — 커밋된 트랜잭션의 로그 레코드는 커밋이 돌아오기 전에 영속화되어 있다(1~2단계). 해당 데이터 페이지가 아직 디스크에 안 가 있어도 된다 — 복구의 redo 패스가 따라잡는다. 3단계가 그 따라잡을 양에 한도를 매긴다. 그리고 어떤 반쪽 쓰기 페이지도 홈이나 로그를 망가뜨리지 못한다(4단계). DWB를 빼면 찢긴 쓰기가 조용히 손상을 일으킨다. 체크포인트를 빼면 복구가 로그 전체를 따라가야 한다. prior list를 빼면 모든 커밋이 로그 래치로 한 줄로 직렬화된다.

vacuum의 입력은 힙이 아니라 WAL이다. 모든 LOG_MVCC_* 레코드가 MVCC 연산 한 건을 적어 둔 셈인데, vacuum은 가장 오래된 가시(MVCCID) 워터마크 아래에서 그 레코드들을 고정 크기 블록으로 앞쪽으로 따라가며 대상 페이지에 적절한 정리를 가한다 — 힙 행 회수, B+Tree 리프 항목 제거, OID 목록 압축 등이 그 정리다. 워터마크는 cubrid-mvcc.md에서 온다(살아 있는 모든 트랜잭션 가운데 가장 작은 스냅샷 하한선이고, 트랜잭션이 들거나 날 때 다시 계산된다). 로그 레코드는 cubrid-log-manager.md에서 온다(복구가 쓰는 그 WAL을 redo가 아니라 앞쪽으로 읽는다). 디스패치는 cubrid-vacuum.md에서 온다(마스터 데몬이 로그를 블록으로 쪼개고 워커 풀이 받아 굴린다). 트레이드오프는 PostgreSQL의 힙 스캔 기반 autovacuum이나 InnoDB의 undo-log purge와 갈린다 — 한 번도 갱신되지 않은 튜플이 많이 남는 워크로드는 스캔 비용을 아끼는데, 거꾸로 수정 없이 긴 스캔만 도는 워크로드는 아무 이득이 없다.

문서맡는 영역한 줄 요약
cubrid-transaction.mdTDES, savepoint, 격리 디스패치트랜잭션별 상태 허브 — trid, 생명주기, 격리 레벨, LSA 사슬, 부수 상태 레지스트리. 시스템 op와 savepoint로 부분 롤백을 중첩한다.
cubrid-mvcc.mdMVCCID, 스냅샷, 활성-MVCCID 표단조 MVCCID와 인메모리 활성-MVCCID 표 위에 스냅샷을 짠다. 가장 오래된 가시 워터마크로 vacuum과 손을 잡는다.
cubrid-lock-manager.md락, 의향 모드, 데드락 탐지OID 단위의 다중 단위 락. 의향 모드(IS, IX, SIX, BU, SCH-S, SCH-M)와 변환 표, 그리고 waits-for 그래프 데드락 탐지기.
cubrid-vacuum.md죽은 버전 회수가장 오래된 가시(MVCCID) 워터마크 아래에서 WAL을 고정 크기 블록으로 앞쪽으로 따라간다. 마스터/워커 디스패치, 지워진 파일은 따로 추적.
cubrid-log-manager.mdWAL, LSA, 추가 규율WAL 레코드의 모양, (page_id, offset)으로 매기는 LSA, prior list → 추가 페이지로 이어지는 추가 규율을 잡는다.
cubrid-prior-list.mdlock-free 생산자 큐짧게 잡는 뮤텍스 한 개로 굴러가는 단방향 LOG_PRIOR_NODE 큐. 로그 임계 구역 안에서 로그 flush 데몬이 비운다. group commit이 큐 자체에서 떨어진다.
cubrid-checkpoint.mdfuzzy ARIES 체크포인트활성 트랜잭션 스냅샷과 redo-LSA 힌트를 담은 주기적 LOG_START_CHKPT / LOG_END_CHKPT 한 쌍. chkpt_lsa를 앞으로 옮겨 재시작 작업량을 잘라 둔다.
cubrid-recovery-manager.mdARIES 세 단계 재시작chkpt_lsa에서 앞쪽으로 분석, min-redo-LSA에서 앞쪽으로 redo(RV_fun[]을 거쳐 페이지 단위 병렬화), 패자별로 거꾸로 undo. CLR이 undo를 다시 시작 가능하게 만든다.
cubrid-2pc.md2-phase commit, in-doubt 복구LOG_2PC_EXECUTE로 굴리는 코디네이터/참여자 FSM. prepared-state 로그 레코드가 크래시를 견딘다. in-doubt 복구는 ARIES 분석 패스에 같이 묶인다.
cubrid-flashback.md시간 여행을 위한 로그 마이닝두 단계 앞쪽 로그 워크 — 트랜잭션마다의 요약(건수, 클래스)을 먼저, 그다음 고른 트랜잭션의 상세 로그 정보. CDC 로그 리더로 아카이브 로그 볼륨을 읽는다.
cubrid-backup-restore.md온라인 백업, PITR데이터 볼륨 스냅샷의 양 끝을 start_lsa와 체크포인트로 자른다. 복원은 페이지를 마운트하고, 사용자 시각까지 로그를 앞쪽으로 굴린다. 증분 세 단계는 prv.lsa <= 부모.start_lsa인 페이지를 건너뛰며 사슬을 만든다.

다른 세 묶음이 이 섹션과 단단히 묶여 있고, 참조는 양방향으로 흐른다.

스토리지 엔진. 페이지 버퍼가 로그 매니저와 디스크 사이에 끼는 층이다. 모든 더티 페이지가 페이지 버퍼의 3-zone LRU와 더블 라이트 버퍼를 거쳐 자기 홈 볼륨에 닿는다. 홈에서 찢긴 쓰기가 나도 DWB 사본으로 살릴 수 있는 까닭이 그것이다. 로그 매니저의 더티 페이지는 자기 로그 레코드보다 먼저 디스크에 갈 수 없다는 약속은 페이지 버퍼에서 강제된다 — 페이지 버퍼가 BCB마다의 oldest_unflush_lsa를 영속 LSN과 비교한 뒤에야 flush를 건다. 이 섹션의 영속성 파이프라인을 본 다음에 cubrid-page-buffer-manager.mdcubrid-double-write-buffer.md로 가면 된다. 로케이터(cubrid-locator.md)는 로그 매니저를 부르는 쪽이다 — 모든 DML이 locator_*_force를 거쳐 가면서, prior list와 복구 매니저가 나중에 따라가게 될 로그 레코드를 만든다.

서버 아키텍처. 부팅 길(cubrid-boot.md)이 시작 시 복구 매니저를 굴려 주는 디스패처다. 로그 헤더를 올리고, log_Gl.hdr.chkpt_lsa를 찾고, 클라이언트를 받기 전에 log_recovery_analysislog_recovery_redolog_recovery_undo로 제어를 넘긴다. 백업과 복원은 SA 모드 유틸 진입점(util_cs.c, util_sa.c)을 거쳐 boot_sr.c로 들어가, 같은 도구를 standalone 모드에서 다시 쓴다. 복구가 어떻게 굴러가고, SA 모드 유틸이 엔진을 어떻게 인프로세스로 다시 들고 가는지 보려면 cubrid-boot.mdcubrid-sa-cs-runtime.md로 간다.

HA와 복제. 복제는 복구 매니저가 다시 굴리는 그 로그를 똑같이 받아 쓴다. copylogdb가 로그 레코드를 slave에 보내고, applylogdb(그리고 더 새로운 페이지 서버 복제 길)가 그것을 굴린다. flashback(cubrid-flashback.md)과 CDC가 같은 로그 리더 인프라를 함께 쓴다. 2PC FSM(cubrid-2pc.md)은 로컬 커밋과 외부 코디네이터 사이에 놓이는 다리다 — XA 클라이언트는 같은 로그 레코드로 FSM을 굴리고, HA 승격은 코디네이터 장애 뒤를 정리할 때 그 in-doubt 복구를 그대로 다시 쓴다. WAL이 어떻게 네트워크 산출물이 되는지 보려면 cubrid-ha-replication.md, cubrid-cdc.md, cubrid-heartbeat.md로 간다.

DDL과 스키마. 이 섹션의 ACID 매핑에서 비어 있던 일관성 자리를 DDL과 스키마가 채운다. 제약 정의, 외래 키 강제, 스키마 변경 락(락 매니저의 SCH-M 모드를 쓴다), 그리고 로케이터에서 발화하는 무결성 검사 규칙이 그쪽 영토다. 발화하는 모든 제약 검사가 결국 이 섹션의 도구를 거쳐 로그 레코드를 적지만, 규칙 — 무엇이 위반인가 — 은 바깥에서 정해 둔다. 규칙은 cubrid-ddl-execution.mdcubrid-class-object.md에서, 그 효과가 어떻게 적히고 어떻게 살아 돌아오는지는 이 섹션에서 읽는다.