콘텐츠로 이동

(KO) CUBRID Flashback — 트랜잭션 요약과 그 트랜잭션의 로그 단위 재생

목차

Flashback 이 답하는 질문은 단순하다. 과거 두 시점 사이에 내 데이터에 무슨 일이 있었고, 그것을 볼 수 있는가? rollback 과는 다르다. 데이터베이스 상태가 바뀌지 않는다. 운영자가 변경 리포트 (또는 다시 적용할 수 있는 스크립트) 를 받아 수동으로 적용한다. 이 일에 가장 가까운 고전 개념은 “log mining” 으로, WAL 을 질의 가능한 이력으로 바꾸는 작업이다.

Database Internals (Petrov) 에 flashback 전용 챕터는 없지만, 주제는 5장 (Recovery, WAL) 과 11장 (Logging) 의 교차점에 위치한다. 이 모델 위에서 모든 flashback 구현은 두 가지 결정을 내려야 하며, 그 결정이 본 문서의 골격을 만든다.

  1. Forward 워크인가, backward 워크인가. 사용자가 흔히 묻는 질문은 “시간 A와 B 사이” 다. 구현은 A 에서 forward 로 스캔하거나 B 에서 backward 로 스캔할 수 있고, 어느 방향이냐가 트랜잭션별 LSA 사슬을 따라가는 방향을 결정한다. CUBRID 은 forward 워크를 두 단계로 고른다. (1) 요약 단계는 forward 스캔으로 trid 별 카운트를 누적하고, (2) loginfo 단계는 선택된 trid 의 범위를 forward 스캔하면서 행 이미지를 구체화한다. 본문에서 역방향이라는 단어가 등장하는 것은 개념적 의미일 뿐이다. flashback 이 과거 상태를 복원하지만 구현 자체는 forward 로 걷는다는 뜻이다.
  2. 전체 로그 마이닝인가, 필터링인가. 긴 로그 범위에는 수백만 레코드가 들어 있을 수 있다. 사람이 보기 좋은 출력을 만들려면 class OID 와 사용자별 필터링이 필수다. CUBRID 은 두 필터를 모두 지원하고, 한 요청의 요약 크기를 FLASHBACK_MAX_NUM_TRAN_TO_SUMMARY 로 묶어, 필터 없는 질의가 메모리를 폭주시키는 일을 막는다.

이 두 답이 보이고 나면, 본 문서의 모든 CUBRID 구조는 그 답 중 하나를 구현하거나 그 답을 더 빠르게 만들기 위해 존재한다는 사실이 분명해진다.

flashback 기능을 제공하는 엔진들 (Oracle, CUBRID, SQL Server 의 “Temporal Tables”) 이 공유하는 패턴이 한 줌 있다.

두 단계 pull — 요약 다음에 상세

섹션 제목: “두 단계 pull — 요약 다음에 상세”

사용자는 넓은 시간 범위의 모든 이벤트를 한 번에 원하는 일이 드물다. 첫 단계는 트랜잭션을 열거해 작은 트랜잭션별 요약 (trid, user, 시간, INSERT/UPDATE/DELETE 카운트, 만진 클래스) 을 반환 한다. 두 번째 단계는 사용자가 트랜잭션 하나를 고르면 그 트랜잭션의 전체 레코드 stream 을 돌려준다. 요약 단계가 로그 walk 비용을 다수 트랜잭션에 걸쳐 분산시키고, 상세 단계가 레코드 디코딩 비용을 그 한 트랜잭션 안의 다수 DML 문에 걸쳐 분산시킨다.

flashback 과 CDC 모두 로그를 forward 로 걷는다. 현대 엔진은 walker 코드를 공유한다. 레코드별 디코더, LSA-시간 매핑, 간접 undo/redo chase 가 모두 같은 코드 경로다. CUBRID 도 그렇다. flashback 의 loginfo 경로는 결과를 CDC_LOGINFO_ENTRY (CDC 가 쓰는 같은 struct) 로 패킹하고, is_flashback=true 를 넘기는 cdc_get_recdes 로 데이터 레코드를 chase 한다.

flashback 요청 하나가 CDC 컨슈머에 필요한 것보다 오래된 archive 를 잡아 둘 수 있다. 그래서 archive 보존 워터마크는 두 입력의 최솟값 으로 정해진다. 한쪽은 CDC 가 유지해야 하는 가장 작은 pageid, 다른 한쪽은 flashback 이 유지해야 하는 가장 작은 pageid 다. CUBRID 은 이 둘을 cdc_min_log_pageid_to_keepflashback_min_log_pageid_to_keep 로 따로 노출하고, archive 제거 daemon 이 그 둘의 min 을 취한다.

사용자는 wall-clock 시간으로 요청을 표현하지만 엔진은 LSA 로 걷는다. flashback 은 time_t → LOG_LSA 해상이 필요하다. CUBRID 에서는 flashback_verify_time 이 그 경계 검사기 역할을 한다. commit timestamp 를 담은 로그 레코드를 따라 걸으면서 주어진 시간을 bracket 하는 LSA 를 찾아낸다.

이론적 개념CUBRID 명칭
Flashback 요약 entryFLASHBACK_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)
클라이언트 측 요약 entryFLASHBACK_SUMMARY_INFO (flashback_cl.h:49)
클라이언트 측 요약 mapFLASHBACK_SUMMARY_INFO_MAP (flashback_cl.h:58)
Wall-time → LSA 해상flashback_verify_time (flashback.h:117)
요약 builderflashback_make_summary_list (flashback.c:284)
Loginfo builderflashback_make_loginfo (flashback.c:767)
서버 측 initflashback_initialize (flashback.c:109)
Archive-keep 워터마크flashback_min_log_pageid_to_keep (flashback.h:128)
활성-flashback gateflashback_is_needed_to_keep_archive (flashback.h:129)
시간 budget gateflashback_check_time_exceed_threshold (flashback.h:130)
이벤트 entry 형식 (CDC와 공유)CDC_LOGINFO_ENTRY (log_impl.h)
트랜잭션별 요약 capFLASHBACK_MAX_NUM_TRAN_TO_SUMMARY 매크로 → PRM_ID_FLASHBACK_MAX_TRANSACTION

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 을 그대로 빌려 썼기 때문이다. 같은 인프라를 쓰는 별개 진입점이지 중복 구현이 아니다.

이 단계는 컨텍스트 객체로 구동된다.

// FLASHBACK_SUMMARY_CONTEXT — src/transaction/flashback.h:87
struct 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:63
struct 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 */

운영자가 요약에서 트랜잭션 하나를 골라 넘기면, 두 번째 단계가 그 트랜잭션의 전체 이벤트 stream 을 가져온다.

// FLASHBACK_LOGINFO_CONTEXT — src/transaction/flashback.h:100
struct 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 chase
int 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) 로 출력한다.

사용자가 wall-clock 시간 범위를 주면, 엔진이 LSA로 변환한다.

// flashback_verify_time — src/transaction/flashback.h:117
int 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.h
extern 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 ();

규율은 다음과 같다.

  1. 운영자가 flashback 요청을 시작하면 flashback_set_status_activeflashback_set_min_log_pageid_to_keep 가 그 요청의 start_lsa.pageid 로 워터마크를 설정한다.
  2. 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 만 삭제한다.
  3. 요청이 끝나거나 (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_activeflashback_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_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.h63
FLASHBACK_SUMMARY_CONTEXT (struct)flashback.h87
FLASHBACK_LOGINFO_CONTEXT (struct)flashback.h100
FLASHBACK_MAX_NUM_TRAN_TO_SUMMARYflashback.h45
OR_SUMMARY_ENTRY_SIZE_WITHOUT_CLASSflashback.h79
FLASHBACK_SUMMARY_INFO (struct)flashback_cl.h49
FLASHBACK_SUMMARY_INFO_MAP (typedef)flashback_cl.h58
flashback_initializeflashback.c109
flashback_make_summary_listflashback.c284
flashback_make_loginfoflashback.c767
  • flashback 은 요약 단계와 loginfo 단계, 두 단계로 구성된다. flashback.c:284flashback_make_summary_listflashback.c:767flashback_make_loginfo 라는 별도 진입점, 그리고 별도 컨텍스트 struct (FLASHBACK_SUMMARY_CONTEXTFLASHBACK_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_queuestd::queue<CDC_LOGINFO_ENTRY *> 인 것으로 확인된다. wire 형식이 컨슈머가 이미 이해하고 있는 것과 같으니, print 코드도 같이 쓸 수 있다.

  • undo/redo chase 는 cdc_get_recdes 를 공유한다. cdc_get_recdesis_flashback 파라미터 (cubrid-cdc.md, log_manager.h 의 시그니처) 로 확인된다. is_flashback=true 가 부분 사슬에 대한 에러 관용성을 풀어준다.

  • 요청별 트랜잭션 cap 은 설정 가능하다. flashback.h:45 에서 FLASHBACK_MAX_NUM_TRAN_TO_SUMMARYprm_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:128flashback_min_log_pageid_to_keep () 로 확인된다. archive 제거 daemon (cubrid-log-manager.md) 이 이 값과 cdc_min_log_pageid_to_keepmin 을 취한다.

  • flashback 요청이 timeout 으로 archive 를 풀어줄 수 있다. flashback.h:130flashback_check_time_exceed_threshold (int *threshold) 로 확인된다. 임계값을 int * 인자로 받아 출력하기 때문에 호출자가 N 초 초과 같은 메시지를 로그로 남길 수 있다.

  • 시간 → LSA 변환은 전용 함수로 존재한다. flashback.h:117flashback_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 출력에 직접 박혀 들어갈 뿐, 따로 유지되지 않는다.

  1. 동시 flashback 요청 처리 여부. flashback_set_status_active_inactive API 가 단일 글로벌 플래그를 쓴다. 운영자 두 명이 동시에 flashback 을 돌리면 race 가 일어날 수 있다. 추적 경로는 flashback.c:109flashback_initializeflashback_set_status_active 본문에서 mutex 나 refcount 가 있는지 확인하는 것이다.

  2. walk 도중 archive 가 제거됐을 때의 동작. 워터마크가 있는데도 daemon 이 어떤 경로로든 flashback 이 읽는 archive 를 삭제하면 어떻게 되는가? is_flashback=true 의 관용 처리는 부분적 graceful degradation 을 시사하지만, 운영자에게 돌아가는 정확한 에러 코드는 검증되지 않았다. 추적 경로는 cdc_get_recdes 의 flashback 분기를 따라가는 것이다.

  3. Backward-walking flashback 의 가능성. FLASHBACK_LOGINFO_CONTEXTforward 필드가 int 다. 코드 경로가 backward walking 을 지원하는지 (예: out-of-order replay 용으로), 아니면 그 필드가 미래용 예약 자리인지가 불분명하다. 추적 경로는 flashback_make_loginfo 본문을 읽는 것이다.

  4. trigger 이벤트 처리. LOG_SUPPLEMENT_TRIGGER_INSERT/UPDATE/DELETE 가 존재한다 (cubrid-cdc.md). 요약 단계가 이를 정상 DML 과 함께 세는지, 별도로 세는지, 아예 건너뛰는지가 미정이다. 추적 경로는 flashback_make_summary_list 본문의 SUPPLEMENT_REC_TYPE switch 를 보는 것이다.

  5. flashback 에서의 DDL 취급. 요약이 DDL 이벤트를 포함하는지가 불분명하다. 요약 entry 의 카운트는 num_insert, num_update, num_delete 뿐이고 DDL 카운터가 없다. 따라서 DDL 은 무시 되거나 loginfo 단계에서만 보일 가능성이 크다. 추적 경로는 flashback.c 안에서 LOG_SUPPLEMENT_DDL 처리를 검색하는 것이다.

  6. 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.