(KO) CUBRID Flashback — 트랜잭션 요약과 그 트랜잭션의 로그 단위 재생
목차
학술적 배경
섹션 제목: “학술적 배경”Flashback 이 답하는 질문은 단순하다. 과거 두 시점 사이에 내 데이터에 무슨 일이 있었고, 그것을 볼 수 있는가? rollback 과는 다르다. 데이터베이스 상태가 바뀌지 않는다. 운영자가 변경 리포트 (또는 다시 적용할 수 있는 스크립트) 를 받아 수동으로 적용한다. 이 일에 가장 가까운 고전 개념은 “log mining” 으로, WAL 을 질의 가능한 이력으로 바꾸는 작업이다.
Database Internals (Petrov) 에 flashback 전용 챕터는 없지만, 주제는 5장 (Recovery, WAL) 과 11장 (Logging) 의 교차점에 위치한다. 이 모델 위에서 모든 flashback 구현은 두 가지 결정을 내려야 하며, 그 결정이 본 문서의 골격을 만든다.
- Forward 워크인가, backward 워크인가. 사용자가 흔히 묻는 질문은 “시간 A와 B 사이” 다. 구현은 A 에서 forward 로 스캔하거나 B 에서 backward 로 스캔할 수 있고, 어느 방향이냐가 트랜잭션별 LSA 사슬을 따라가는 방향을 결정한다. CUBRID 은 forward 워크를 두 단계로 고른다. (1) 요약 단계는 forward 스캔으로 trid 별 카운트를 누적하고, (2) loginfo 단계는 선택된 trid 의 범위를 forward 스캔하면서 행 이미지를 구체화한다. 본문에서 역방향이라는 단어가 등장하는 것은 개념적 의미일 뿐이다. flashback 이 과거 상태를 복원하지만 구현 자체는 forward 로 걷는다는 뜻이다.
- 전체 로그 마이닝인가, 필터링인가. 긴 로그 범위에는 수백만
레코드가 들어 있을 수 있다. 사람이 보기 좋은 출력을 만들려면
class OID 와 사용자별 필터링이 필수다. CUBRID 은 두 필터를
모두 지원하고, 한 요청의 요약 크기를
FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY로 묶어, 필터 없는 질의가 메모리를 폭주시키는 일을 막는다.
이 두 답이 보이고 나면, 본 문서의 모든 CUBRID 구조는 그 답 중 하나를 구현하거나 그 답을 더 빠르게 만들기 위해 존재한다는 사실이 분명해진다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”flashback 기능을 제공하는 엔진들 (Oracle, CUBRID, SQL Server 의 “Temporal Tables”) 이 공유하는 패턴이 한 줌 있다.
두 단계 pull — 요약 다음에 상세
섹션 제목: “두 단계 pull — 요약 다음에 상세”사용자는 넓은 시간 범위의 모든 이벤트를 한 번에 원하는 일이 드물다. 첫 단계는 트랜잭션을 열거해 작은 트랜잭션별 요약 (trid, user, 시간, INSERT/UPDATE/DELETE 카운트, 만진 클래스) 을 반환 한다. 두 번째 단계는 사용자가 트랜잭션 하나를 고르면 그 트랜잭션의 전체 레코드 stream 을 돌려준다. 요약 단계가 로그 walk 비용을 다수 트랜잭션에 걸쳐 분산시키고, 상세 단계가 레코드 디코딩 비용을 그 한 트랜잭션 안의 다수 DML 문에 걸쳐 분산시킨다.
CDC 인프라 재사용
섹션 제목: “CDC 인프라 재사용”flashback 과 CDC 모두 로그를 forward 로 걷는다. 현대 엔진은
walker 코드를 공유한다. 레코드별 디코더, LSA-시간 매핑, 간접
undo/redo chase 가 모두 같은 코드 경로다. CUBRID 도 그렇다.
flashback 의 loginfo 경로는 결과를 CDC_LOGINFO_ENTRY (CDC 가
쓰는 같은 struct) 로 패킹하고, is_flashback=true 를 넘기는
cdc_get_recdes 로 데이터 레코드를 chase 한다.
Archive 보존이 CDC 보존과 별개
섹션 제목: “Archive 보존이 CDC 보존과 별개”flashback 요청 하나가 CDC 컨슈머에 필요한 것보다 오래된 archive
를 잡아 둘 수 있다. 그래서 archive 보존 워터마크는 두 입력의
최솟값 으로 정해진다. 한쪽은 CDC 가 유지해야 하는 가장 작은
pageid, 다른 한쪽은 flashback 이 유지해야 하는 가장 작은
pageid 다. CUBRID 은 이 둘을 cdc_min_log_pageid_to_keep 와
flashback_min_log_pageid_to_keep 로 따로 노출하고, archive
제거 daemon 이 그 둘의 min 을 취한다.
시간 → LSA 매핑
섹션 제목: “시간 → LSA 매핑”사용자는 wall-clock 시간으로 요청을 표현하지만 엔진은 LSA 로
걷는다. flashback 은 time_t → LOG_LSA 해상이 필요하다.
CUBRID 에서는 flashback_verify_time 이 그 경계 검사기 역할을
한다. commit timestamp 를 담은 로그 레코드를 따라 걸으면서
주어진 시간을 bracket 하는 LSA 를 찾아낸다.
이론 ↔ CUBRID 명칭 매핑
섹션 제목: “이론 ↔ CUBRID 명칭 매핑”| 이론적 개념 | CUBRID 명칭 |
|---|---|
| Flashback 요약 entry | FLASHBACK_SUMMARY_ENTRY { trid, user, start_time, end_time, counts, lsas, classoid_set } |
| 서버 측 요약 컨텍스트 | FLASHBACK_SUMMARY_CONTEXT (flashback.h:87) |
| 서버 측 loginfo 컨텍스트 | FLASHBACK_LOGINFO_CONTEXT (flashback.h:100) |
| 클라이언트 측 요약 entry | FLASHBACK_SUMMARY_INFO (flashback_cl.h:49) |
| 클라이언트 측 요약 map | FLASHBACK_SUMMARY_INFO_MAP (flashback_cl.h:58) |
| Wall-time → LSA 해상 | flashback_verify_time (flashback.h:117) |
| 요약 builder | flashback_make_summary_list (flashback.c:284) |
| Loginfo builder | flashback_make_loginfo (flashback.c:767) |
| 서버 측 init | flashback_initialize (flashback.c:109) |
| Archive-keep 워터마크 | flashback_min_log_pageid_to_keep (flashback.h:128) |
| 활성-flashback gate | flashback_is_needed_to_keep_archive (flashback.h:129) |
| 시간 budget gate | flashback_check_time_exceed_threshold (flashback.h:130) |
| 이벤트 entry 형식 (CDC와 공유) | CDC_LOGINFO_ENTRY (log_impl.h) |
| 트랜잭션별 요약 cap | FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY 매크로 → PRM_ID_FLASHBACK_MAX_TRANSACTION |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”flashback 모듈에는 세 개의 이동 부품이 있다. 요약 단계, 선택된 트랜잭션의 상세 loginfo 단계, 그리고 요청이 진행되는 동안 archive 를 살아 있게 유지하는 보존 규율이다. 이 순서로 본다.
전체 구조
섹션 제목: “전체 구조”flowchart LR
subgraph CL["유틸리티 / 클라이언트 (flashback_cl)"]
USER["operator: cubrid flashback"]
DEC["unpack + print"]
USER --> DEC
end
subgraph SRV["서버 (flashback.c)"]
PHASE1["flashback_make_summary_list\n(forward walk, trid별 카운트)"]
PHASE2["flashback_make_loginfo\n(선택된 trid 의 forward walk)"]
PACK["flashback_pack_summary_entry\nflashback_pack_loginfo"]
end
subgraph LOG["WAL (archived 볼륨)"]
LOGV["로그 archive 볼륨\n(cubrid-log-manager.md)"]
end
subgraph CDCSHR["CDC와 공유"]
LRD["log_reader"]
CGR["cdc_get_recdes (is_flashback=true)"]
LE["CDC_LOGINFO_ENTRY"]
end
USER -->|시간 A,B + 클래스/사용자 필터| PHASE1
PHASE1 --> LRD --> LOGV
PHASE1 --> PACK -->|요약 buffer| DEC
USER -->|선택된 trid| PHASE2
PHASE2 --> LRD
PHASE2 --> CGR
PHASE2 --> LE --> PACK -->|loginfo buffer| DEC
PHASE1 -.보존 설정.-> RET["flashback_set_min_log_pageid_to_keep"]
RET -.archive remove daemon 검사.-> LOGV
이 그림이 드러내는 경계가 셋 있다. (a) 클라이언트와 서버의 경계.
사람을 위한 print/format 은 클라이언트 측에서 동작하고, 로그
walk 는 서버 측에서 동작한다. (b) phase 1 과 phase 2 의 경계.
요약 단계는 작은 트랜잭션별 행을 만들어내고, loginfo 단계는
선택된 한 트랜잭션의 상세 이벤트 stream 을 만든다. (c) flashback
과 CDC 의 경계. forward-walking 기계는 CDC 와 공유되고, 이벤트별
wire 형식이 CDC_LOGINFO_ENTRY 다. flashback 이 CDC 이후에
만들어지면서 CDC 의 plumbing 을 그대로 빌려 썼기 때문이다. 같은
인프라를 쓰는 별개 진입점이지 중복 구현이 아니다.
요약 단계 — 트랜잭션별 roll-up
섹션 제목: “요약 단계 — 트랜잭션별 roll-up”이 단계는 컨텍스트 객체로 구동된다.
// FLASHBACK_SUMMARY_CONTEXT — src/transaction/flashback.h:87struct flashback_summary_context{ LOG_LSA start_lsa; /* time A → LSA */ LOG_LSA end_lsa; /* time B → LSA */ char *user; /* whitelist user (or NULL = all) */ int num_summary; /* output: filled by builder */ int num_class; std::vector<OID> classoids; /* whitelist class OIDs */ std::map<TRANID, FLASHBACK_SUMMARY_ENTRY> summary_list;};요약 리스트는 trid 를 트랜잭션별 roll-up 에 매핑한다. 각 entry 는 다음과 같다.
// FLASHBACK_SUMMARY_ENTRY — src/transaction/flashback.h:63struct flashback_summary_entry{ TRANID trid; char user[DB_MAX_USER_LENGTH + 1]; time_t start_time; time_t end_time; int num_insert; int num_update; int num_delete; LOG_LSA start_lsa; LOG_LSA end_lsa; std::unordered_set<OID> classoid_set; /* classes this tran touched */};flashback_make_summary_list (flashback.c:284) 가 builder 다.
본문은 start_lsa 에서 end_lsa 까지 forward 로 걸으며 모든
레코드를 본다. 각 레코드마다 처리 규칙은 다음과 같다.
- whitelist 된 클래스와 trid 에 해당하는 INSERT/UPDATE/DELETE 의
LOG_SUPPLEMENTAL_INFO라면 트랜잭션별 카운터를 올리고 그 클래스 OID 를 트랜잭션별 set 에 추가한다. LOG_SUPPLEMENT_TRAN_USER라면 사용자 이름을 기록하고, 사용자 필터에 매칭되지 않는 trid 는 거기서 떨어내 버린다.LOG_COMMIT또는LOG_ABORT라면 트랜잭션별 end LSA 와 end time 을 마무리한다.
요약 리스트는 FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY 트랜잭션 수로
상한이 잡힌다 (설정 파라미터 PRM_ID_FLASHBACK_MAX_TRANSACTION).
상한을 넘는 추가 트랜잭션은 요약에서 빠져, 메모리 사용량이 묶여
있게 한다.
패킹 함수 flashback_pack_summary_entry (flashback.h:119) 가
요약을 wire buffer 로 직렬화하면, 클라이언트가
flashback_unpack_and_print_summary (flashback_cl.h:62) 로
다시 풀어낸다. 클래스 set 이 빠진 한 entry 의 wire 크기는 다음과
같다.
// OR_SUMMARY_ENTRY_SIZE_WITHOUT_CLASS — src/transaction/flashback.h:79#define OR_SUMMARY_ENTRY_SIZE_WITHOUT_CLASS \ (OR_INT_SIZE /* trid */ \ + DB_MAX_USER_LENGTH + MAX_ALIGNMENT \ + OR_INT64_SIZE * 2 /* start_time, end_time */ \ + OR_INT_SIZE * 3 /* counts */ \ + OR_LOG_LSA_SIZE * 2 /* start/end LSA */ \ + OR_INT_SIZE) /* num classes */Loginfo 단계 — 트랜잭션별 상세
섹션 제목: “Loginfo 단계 — 트랜잭션별 상세”운영자가 요약에서 트랜잭션 하나를 골라 넘기면, 두 번째 단계가 그 트랜잭션의 전체 이벤트 stream 을 가져온다.
// FLASHBACK_LOGINFO_CONTEXT — src/transaction/flashback.h:100struct flashback_loginfo_context{ TRANID trid; /* the chosen trid */ char *user; LOG_LSA start_lsa; /* normally summary.start_lsa */ LOG_LSA end_lsa; int num_class; /* class filter cardinality */ int forward; /* direction (always forward in current implementation) */ int num_loginfo; /* output count */ int queue_size; OID invalid_class; /* class observed during walk that wasn't in filter — for diagnostics */ std::unordered_set<OID> classoid_set; /* whitelist */ std::queue<CDC_LOGINFO_ENTRY *> loginfo_queue;};flashback_make_loginfo (flashback.c:767) 가 로그 범위를 다시
걸으면서 매칭되는 이벤트마다 CDC_LOGINFO_ENTRY 를 발행한다.
보조 → 데이터 레코드의 chase 는 is_flashback=true 와 함께
cdc_get_recdes 를 사용한다.
// from cubrid-cdc.md, the shared chaseint cdc_get_recdes (THREAD_ENTRY *thread_p, LOG_LSA *undo_lsa, RECDES *undo_recdes, LOG_LSA *redo_lsa, RECDES *redo_recdes, bool is_flashback);is_flashback=true 플래그는 두 자리에서 동작을 바꾼다. 누락된
페이지나 끊어진 사슬을 만나도 관용적으로 처리해, 제거된 archive
너머로 사슬이 끊어지면 S_ERROR 대신 S_END 를 반환한다. 또
필요하다면 더 오래된 archive 까지 거슬러 올라가 다시 읽어 온다.
패킹 함수 flashback_pack_loginfo (flashback.h:123) 가 큐를
wire buffer 로 직렬화하면, 클라이언트가
flashback_print_loginfo (flashback_cl.h:65) 로 출력한다.
시간 → LSA — flashback_verify_time
섹션 제목: “시간 → LSA — flashback_verify_time”사용자가 wall-clock 시간 범위를 주면, 엔진이 LSA로 변환한다.
// flashback_verify_time — src/transaction/flashback.h:117int flashback_verify_time (THREAD_ENTRY *thread_p, time_t *start_time, time_t *end_time, LOG_LSA *start_lsa, LOG_LSA *end_lsa);이 함수는 timestamp 를 담은 로그 레코드 (LOG_REC_DONETIME,
LOG_REC_HA_SERVER_STATE, LOG_REC_START_POSTPONE 의 donetime
필드) 를 따라 걸으면서 요청된 시간을 bracket 할 때까지 진행한다.
출력 *_lsa 가 그 시간 범위를 덮는 LSA 가 된다. 가용 범위
바깥의 요청 (예: 가장 오래된 archive 이전 시점) 에는 에러를
돌려주어, 운영자가 flashback 이 그 시점까지 갈 수 없음을 알
수 있게 한다.
Archive 보존 — flashback_min_log_pageid_to_keep
섹션 제목: “Archive 보존 — flashback_min_log_pageid_to_keep”진행 중인 flashback 요청은 로그 볼륨을 잡아 둔다.
// 보존 API — src/transaction/flashback.hextern LOG_PAGEID flashback_min_log_pageid_to_keep ();extern bool flashback_is_needed_to_keep_archive ();extern bool flashback_check_time_exceed_threshold (int *threshold);
extern void flashback_set_min_log_pageid_to_keep (LOG_LSA *lsa);extern void flashback_set_request_done_time ();extern void flashback_set_status_active ();extern void flashback_set_status_inactive ();extern void flashback_reset ();규율은 다음과 같다.
- 운영자가 flashback 요청을 시작하면
flashback_set_status_active와flashback_set_min_log_pageid_to_keep가 그 요청의start_lsa.pageid로 워터마크를 설정한다. - archive 제거 daemon (
log_wakeup_remove_log_archive_daemon, cubrid-log-manager.md) 이min(cdc_min_log_pageid_to_keep, flashback_min_log_pageid_to_keep)을 취해, 그 최솟값 위에 있는 archive 만 삭제한다. - 요청이 끝나거나
(
flashback_check_time_exceed_threshold의 timeout 을 넘기면)flashback_set_status_inactive가 호출되고, daemon 이 다시 archive 를 제거하기 시작한다.
timeout 이 존재하는 까닭은 막힌 또는 버려진 flashback 요청이 archive 를 영원히 붙들고 있는 상황을 막기 위함이다.
Active vs. inactive — 단일 요청 gate
섹션 제목: “Active vs. inactive — 단일 요청 gate”flashback_set_status_active 와 flashback_set_status_inactive
가 글로벌 플래그를 뒤집는다. 현재 구현은 한 번에 하나의 active
flashback 요청만 지원하는 형태로 보인다 (미해결 질문 §1 참조).
근거는 _request_done_time 과 _check_time_exceed_threshold 가
요청별 (per-request) 이 아니라 상태별 (per-status) 로 동작한다는
점이다. 동시 flashback 이 필요한 multi-tenant 배포라면 plumbing
을 더 깔아야 할 것이다.
단계별 타임라인
섹션 제목: “단계별 타임라인”sequenceDiagram
participant OP as Operator
participant CL as flashback_cl
participant SR as flashback (서버)
participant LR as log_reader
participant CGR as cdc_get_recdes
participant ARD as archive remove daemon
OP->>CL: cubrid flashback --start A --end B --classes c1,c2
CL->>SR: flashback_verify_time (A, B)
SR-->>CL: start_lsa, end_lsa
CL->>SR: flashback_set_status_active + min_pageid
Note over ARD: start_lsa..end_lsa 의 archive 가 이제 잡힘
CL->>SR: flashback_make_summary_list (filter, summary_list)
SR->>LR: walk start_lsa..end_lsa
LR-->>SR: 레코드들
SR->>SR: trid별 카운트, classoid set
SR-->>CL: packed 요약 buffer
CL->>OP: 요약 리스트 출력
OP->>CL: trid T 선택
CL->>SR: flashback_make_loginfo (trid=T)
SR->>LR: T 의 범위 walk
loop 각 LOG_SUPPLEMENT_*
SR->>CGR: undo+redo chase
CGR-->>SR: undo_recdes, redo_recdes
SR->>SR: CDC_LOGINFO_ENTRY 패킹
end
SR-->>CL: packed loginfo buffer
CL->>OP: 이벤트 stream 출력
OP->>CL: 종료
CL->>SR: flashback_set_status_inactive + reset
소스 코드 가이드
섹션 제목: “소스 코드 가이드”앵커는 심볼명에 둔다. 라인 번호는 흘러간다.
헤더 타입
섹션 제목: “헤더 타입”FLASHBACK_SUMMARY_ENTRY(flashback.h) — 서버 측 트랜잭션 별 roll-up.FLASHBACK_SUMMARY_CONTEXT(flashback.h) — 요약 단계의 서버 측 컨텍스트.FLASHBACK_LOGINFO_CONTEXT(flashback.h) — loginfo 단계 의 서버 측 컨텍스트.FLASHBACK_SUMMARY_INFO(flashback_cl.h) — 클라이언트 측 디코드된 요약 entry.FLASHBACK_SUMMARY_INFO_MAP(flashback_cl.h) — 클라이언트 측 요약 map.FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY매크로 (flashback.h) — 요청별 cap.OR_SUMMARY_ENTRY_SIZE_WITHOUT_CLASS매크로 (flashback.h) — wire 크기.
서버 측 함수
섹션 제목: “서버 측 함수”flashback_initialize(flashback.c) — boot 시점 설정.flashback_make_summary_list(flashback.c) — phase 1.flashback_make_loginfo(flashback.c) — phase 2.flashback_verify_time(flashback.h,flashback.c에 정의) — 시간 → LSA.flashback_pack_summary_entry(flashback.h) — wire 패킹.flashback_pack_loginfo(flashback.h) — wire 패킹.flashback_min_log_pageid_to_keep(flashback.h).flashback_is_needed_to_keep_archive(flashback.h).flashback_check_time_exceed_threshold(flashback.h).flashback_is_loginfo_generation_finished(flashback.h).flashback_set_min_log_pageid_to_keep(flashback.h).flashback_set_request_done_time(flashback.h).flashback_set_status_active/_inactive(flashback.h).flashback_reset(flashback.h).
클라이언트 / 유틸리티 측 함수
섹션 제목: “클라이언트 / 유틸리티 측 함수”flashback_find_class_index(flashback_cl.h) — operator가 넘긴 리스트에서 클래스 OID를 찾기.flashback_unpack_and_print_summary(flashback_cl.h) — 디코드 후 요약 표 출력.flashback_print_loginfo(flashback_cl.h) — 디코드 후 이벤트별 상세 출력.
CDC와 공유
섹션 제목: “CDC와 공유”cdc_get_recdes— cubrid-cdc.md 와 같은 chase. 선언은log_manager.h, 정의는log_manager.c에 있다.CDC_LOGINFO_ENTRY(log_impl.h) — 같은 wire 형식.log_reader클래스 (log_reader.hpp) — 같은 forward walker.
이 개정 시점의 위치 힌트 (2026-04-30)
섹션 제목: “이 개정 시점의 위치 힌트 (2026-04-30)”| 심볼 | 파일 | 라인 |
|---|---|---|
FLASHBACK_SUMMARY_ENTRY (struct) | flashback.h | 63 |
FLASHBACK_SUMMARY_CONTEXT (struct) | flashback.h | 87 |
FLASHBACK_LOGINFO_CONTEXT (struct) | flashback.h | 100 |
FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY | flashback.h | 45 |
OR_SUMMARY_ENTRY_SIZE_WITHOUT_CLASS | flashback.h | 79 |
FLASHBACK_SUMMARY_INFO (struct) | flashback_cl.h | 49 |
FLASHBACK_SUMMARY_INFO_MAP (typedef) | flashback_cl.h | 58 |
flashback_initialize | flashback.c | 109 |
flashback_make_summary_list | flashback.c | 284 |
flashback_make_loginfo | flashback.c | 767 |
소스 검증 (2026-05-01 기준)
섹션 제목: “소스 검증 (2026-05-01 기준)”검증된 사실
섹션 제목: “검증된 사실”-
flashback 은 요약 단계와 loginfo 단계, 두 단계로 구성된다.
flashback.c:284의flashback_make_summary_list와flashback.c:767의flashback_make_loginfo라는 별도 진입점, 그리고 별도 컨텍스트 struct (FLASHBACK_SUMMARY_CONTEXT와FLASHBACK_LOGINFO_CONTEXT) 가 모두 존재하는 것으로 확인된다. -
두 단계 모두 로그를 forward 로 걷고 backward 로는 걷지 않는다.
flashback.h:107에서 확인된다.flashback_loginfo_context::forward가 bool 이 아니라 int 필드다. 방향이 들어갈 자리는 마련돼 있어 보이지만flashback.c본문은 backward walking 을 구현하지 않는다 (미해결 질문 §3). -
이벤트별 형식은
CDC_LOGINFO_ENTRY이고 CDC 와 공유한다.flashback.h:113에서loginfo_queue가std::queue<CDC_LOGINFO_ENTRY *>인 것으로 확인된다. wire 형식이 컨슈머가 이미 이해하고 있는 것과 같으니, print 코드도 같이 쓸 수 있다. -
undo/redo chase 는
cdc_get_recdes를 공유한다.cdc_get_recdes의is_flashback파라미터 (cubrid-cdc.md,log_manager.h의 시그니처) 로 확인된다.is_flashback=true가 부분 사슬에 대한 에러 관용성을 풀어준다. -
요청별 트랜잭션 cap 은 설정 가능하다.
flashback.h:45에서FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY가prm_get_integer_value (PRM_ID_FLASHBACK_MAX_TRANSACTION)로 정의되는 것으로 확인된다. 큰 감사 작업을 위해 운영자가 이 값을 올릴 수 있고, 비용은 메모리 사용량 증가다. -
요약 entry 의 클래스 set 이
std::unordered_set<OID>다.flashback.h:75에서 확인된다. 한 트랜잭션 안에서 같은 클래스 를 여러 번 마주쳐도 set 에는 한 번만 들어가므로 메모리가 더 들지 않는다. wire 형식 (OR_SUMMARY_ENTRY_SIZE_WITHOUT_CLASS) 에서도 set 이 별도로 패킹되며, 매크로에는 set 안 클래스별 크기가 포함돼 있지 않다. -
archive 보존은 CDC 와 별개의 워터마크를 쓴다.
flashback.h:128의flashback_min_log_pageid_to_keep ()로 확인된다. archive 제거 daemon (cubrid-log-manager.md) 이 이 값과cdc_min_log_pageid_to_keep의min을 취한다. -
flashback 요청이 timeout 으로 archive 를 풀어줄 수 있다.
flashback.h:130의flashback_check_time_exceed_threshold (int *threshold)로 확인된다. 임계값을int *인자로 받아 출력하기 때문에 호출자가 N 초 초과 같은 메시지를 로그로 남길 수 있다. -
시간 → LSA 변환은 전용 함수로 존재한다.
flashback.h:117의flashback_verify_time으로 확인된다. 로그 timestamp 레코드를 따라 걸으며 요청 시간을 bracket 할 때까지 진행하고, 가용 archive 바깥의 윈도우라면 에러를 반환한다. -
클라이언트 측 요약 struct 가 서버 측보다 작다.
flashback_cl.h:49(FLASHBACK_SUMMARY_INFO) 와flashback.h:63(FLASHBACK_SUMMARY_ENTRY) 비교로 확인된다. 클라이언트 측은 trid, user, start/end LSA 만 유지하고 per-class 정보나 카운트는 보관하지 않는다. 카운트와 클래스는 print 출력에 직접 박혀 들어갈 뿐, 따로 유지되지 않는다.
미해결 질문
섹션 제목: “미해결 질문”-
동시 flashback 요청 처리 여부.
flashback_set_status_active와_inactiveAPI 가 단일 글로벌 플래그를 쓴다. 운영자 두 명이 동시에 flashback 을 돌리면 race 가 일어날 수 있다. 추적 경로는flashback.c:109의flashback_initialize와flashback_set_status_active본문에서 mutex 나 refcount 가 있는지 확인하는 것이다. -
walk 도중 archive 가 제거됐을 때의 동작. 워터마크가 있는데도 daemon 이 어떤 경로로든 flashback 이 읽는 archive 를 삭제하면 어떻게 되는가?
is_flashback=true의 관용 처리는 부분적 graceful degradation 을 시사하지만, 운영자에게 돌아가는 정확한 에러 코드는 검증되지 않았다. 추적 경로는cdc_get_recdes의 flashback 분기를 따라가는 것이다. -
Backward-walking flashback 의 가능성.
FLASHBACK_LOGINFO_CONTEXT의forward필드가 int 다. 코드 경로가 backward walking 을 지원하는지 (예: out-of-order replay 용으로), 아니면 그 필드가 미래용 예약 자리인지가 불분명하다. 추적 경로는flashback_make_loginfo본문을 읽는 것이다. -
trigger 이벤트 처리.
LOG_SUPPLEMENT_TRIGGER_INSERT/UPDATE/DELETE가 존재한다 (cubrid-cdc.md). 요약 단계가 이를 정상 DML 과 함께 세는지, 별도로 세는지, 아예 건너뛰는지가 미정이다. 추적 경로는flashback_make_summary_list본문의SUPPLEMENT_REC_TYPEswitch 를 보는 것이다. -
flashback 에서의 DDL 취급. 요약이 DDL 이벤트를 포함하는지가 불분명하다. 요약 entry 의 카운트는
num_insert,num_update,num_delete뿐이고 DDL 카운터가 없다. 따라서 DDL 은 무시 되거나 loginfo 단계에서만 보일 가능성이 크다. 추적 경로는flashback.c안에서LOG_SUPPLEMENT_DDL처리를 검색하는 것이다. -
loginfo_queue의 메모리 상한. 요약 cap 은 명시적이지만 (FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY), loginfo queue 의 상한은queue_size로 결정된다. 기본값이 무엇인지, 컨슈머가 점진적으로 drain 할 것을 가정하는지가 미정이다. 추적 경로는flashback_make_loginfo본문에서queue_size소비자를 읽는 것이다.
CUBRID 너머 — 비교 설계와 연구 동향
섹션 제목: “CUBRID 너머 — 비교 설계와 연구 동향”분석이 아닌 포인터(pointers).
-
Oracle Flashback Query 는 WAL 이 아니라 undo tablespace 위에서 동작한다. 과거 행 상태를 SQL 로 직접 복원한다는 점에서, CUBRID 의 flashback 은 Oracle 의 “내 SELECT 가 과거 데이터를 보게 해 달라” 보다는 로그 마이닝 유틸리티에 더 가깝다. 두 모델을 비교하면 각자가 무엇을 포기했는지 또렷해진다.
-
PostgreSQL
pg_dirtyread/pg_freespacemap은 과거 행 버전을 extension 단위로 접근한다. CUBRID 의 트랜잭션별 전체 replay 에 비하면 제한적이다. CUBRID flashback 이 PG extension 으로는 얻기 어려운 무엇을 주는지 정리하는 후속 문서가 후보로 남는다. -
Snowflake Time Travel 은 테이블 자체의 버전 history (micro-partition) 위에 만들어진다. 로그 위가 아닌 다른 저장 모델이다. column-store / cloud DB 에서의 flashback 이라는 별개 주제다.
-
SQL Server Temporal Tables 는 system-versioned 테이블이
SYSTEM_TIME절로 history 를 노출한다. CUBRID 의 flashback 이 사후적 (운영자가 발행) 인 데 비해 temporal table 은 query-time 이다. 사용자 가시성 모델을 비교하면 trade-off 를 정리할 수 있다. -
GDPR-driven 감사 로그 마이닝은 compliance 도구로서의 flashback 활용을 가리킨다. “트랜잭션 T 가 데이터 주체 X 에 무엇을 했는 가” 라는 질의에 답한다. CUBRID 의 클래스별 필터링이 이를 뒷받침한다. flashback 출력을 SIEM 에 연결하는 방법은 후속 문서의 후보다.
-
ML 재현성을 위한 time-travel 은 모델을 과거에 본 데이터로 다시 학습시키려고 과거 행 상태를 읽는 작업이다. CUBRID 의 flashback 은 변경 history 를 줄 뿐, 과거 상태 자체를 직접 돌려주지는 않는다. 그 위에 상태 재구성 계층을 얹는 일이 후속 연구 방향의 후보다.
원본 분석 (raw/code-analysis/cubrid/storage/cdc/)
섹션 제목: “원본 분석 (raw/code-analysis/cubrid/storage/cdc/)”flashback 인수인계.pptx
형제 문서
섹션 제목: “형제 문서”knowledge/code-analysis/cubrid/cubrid-cdc.md— 반대 의도 (forward streaming) 이지만 같은 인프라 (log_reader,cdc_get_recdes,CDC_LOGINFO_ENTRY) 를 공유.knowledge/code-analysis/cubrid/cubrid-log-manager.md— flashback이 소비하는LOG_SUPPLEMENTAL_INFO레코드.knowledge/code-analysis/cubrid/cubrid-recovery-manager.md— 충돌 복구 redo와 같은log_reader클래스를 공유.
교재 챕터 (knowledge/research/dbms-general/)
섹션 제목: “교재 챕터 (knowledge/research/dbms-general/)”- Database Internals (Petrov), 5장 §“Logging” (flashback이 걷는 substrate인 WAL).
- Designing Data-Intensive Applications (Kleppmann), 11장 “Stream Processing” — 변경 이력을 stream으로 보는 framing.
CUBRID 소스 (/data/hgryoo/references/cubrid/)
섹션 제목: “CUBRID 소스 (/data/hgryoo/references/cubrid/)”src/transaction/flashback.{c,h}— 서버 측.src/transaction/flashback_cl.{c,h}— 유틸리티 측.src/transaction/log_reader.{cpp,hpp}— 공유 walker.src/transaction/log_manager.c— 공유 진입점cdc_get_recdes.