콘텐츠로 이동

(KO) CUBRID checksumdb — chunked, replication-replayed 비교를 통한 HA replica vs master 행 checksum 검증기

Replica 무결성 검증기 는 “내 replica 가 정말 master 와 동일 한가?” 라는 질문에 답하는 도구이다 — 긴 복제 chain 후, 데이터 부패 의심 후, 또는 계획된 failover 전 정기 점검으로 떠오르는 질문이다. 사소한 구현 (양쪽을 dump 하고 diff)은 규모에서 비실용적이다 — dump 동안 쓰기를 중단하거나 비싼 스냅샷-추적 비용을 내야 하고, 대역폭 비용이 replica 당 한 번의 full table-scan 이 든다.

Production 엔진은 따라서 sampling 또는 chunked-checksum 검증기 를 ship 한다 — exhaustiveness 를 online 동작과 trade 한다는 점이다:

  1. Per-row checksum. 행별 hash 비교; exhaustive 하지만 데이터 크기에 비례하고 모든 행을 양측 동기화가 필요하다.
  2. Chunked checksum. 표가 chunk 로 split (PK 범위, 행 수, 또는 시간 window 별); chunk 별 checksum 을 양측에서 계산해 비교. chunk 수준 mismatch 가 divergence 를 표시; 운영자가 표시된 chunk 에서만 per-row 로 drill down 할 수 있다는 점이다.
  3. Replication-replayed checksum. master 가 chunk 별 checksum 을 복제되는 표 로 계산하고, 같은 복제 stream 이 slave 로 운반하고, slave 가 같은 chunk 를 로컬에서 재계산해 자기 checksum 으로 표를 update. 비교가 결과 표에 대한 WHERE master_checksum != slave_checksum 쿼리가 된다는 뜻이다. 대역폭 비용이 chunk 당 summary 행 한 개이지 데이터 행당 한 개가 아니다.

CUBRID 의 checksumdb 가 옵션 3 을 고른다 — primary-key 범위 (각 chunk 가 표의 논리적 순서에서 contiguous), 행으로 측정되는 configurable chunk size, 그리고 행 데이터가 멀쩡할 때조차 DDL drift 를 감지하는 별도 schema-checksum 측면.

엔진도구접근Replica 왕복
MySQLpt-table-checksum (Percona Toolkit)PK 범위별 chunked CRC32; checksum SQL 을 binlog 통해 복제해 slave 가 자기 데이터로 계산chunk 당 결과 행 하나 복제
PostgreSQLpg_compare, pglogical_compare, bucardo_ctl validateper-table 스냅샷 diff; 일부 도구는 chunked hash 사용도구마다 다름
OracleDBMS_COMPARISON 패키지chunked PK 범위 hash; 차이 reconcile 도 가능scan 컬럼당 summary 행 하나
MongoDBdb.collection.dataSize() summary 검사; first-class row-level 무결성 검증기 없음application 수준n/a
CUBRID checksumdbPK 범위별 chunked CRC, 데이터를 운반하는 같은 WAL stream 으로 복제, chunk 동안 lock; 명시적 schema-checksum 측면chunk 당 결과 행 하나 복제; 스키마 행 별도 복제

CUBRID 디자인은 pt-table-checksum 에 가장 가깝다 — chunked PK 범위, replication-replayed, lock-during-checksum. 차별점은 명시적 schema-checksum 표 이다 — master 와 replica 사이 DDL drift 가 행-데이터 divergence 보고와 충돌하지 않는 별도 row 클래스를 생성한다는 점이다.

checksumdb 가 자기 사용을 위해서만 존재하는 두 전용 표를 유지한다는 점이다:

내용목적
db_ha_apply_info_chksum_* (결과 표)(target 표, chunk_id) 쌍당 한 행: PK lower bound, chunk 크기, master checksum, master 복제 정보, slave checksum (replay 시점에 계산)chunk 별 검증
db_ha_apply_info_chksum_*_schema (schema 표)검사된 target 표마다 한 행: 직렬화된 schema 정의 + repid + master schema checksumDDL-drift 감지

정확한 이름 suffix 는 configurable 하다. 상수 chksum_result_Table_namechksum_schema_Table_name 가 startup 동안 데이터베이스 이름 에서 채워진다.

두 표는 일반 CUBRID 표 다 — 다른 카탈로그 행처럼 WAL 에 참여한다. 즉 master 의 그 표에 대한 쓰기가 기존 HA 복제 경로 (cubrid-ha-replication.md 참조)로 자동으로 slave 로 전파된다는 뜻이다. Slave 측에서는 apply-log 데몬이 master 의 행 insert 를 결과 표로 replay; slave 측 checksumdb 가 그 다음 각 행을 다시 읽고 chunk 의 checksum 을 로컬에서 재계산하고 slave 의 자기 checksum 을 비교를 위해 같은 행에 다시 쓴다.

각 user 표를 checksumdb 가 PK 순서에 따라 데이터를 walk 하면서 --chunk-size 행 (기본 10,000)으로 chunk 로 자른다는 점이다. 각 chunk 가 다음을 가진다:

  • lower_bound: 적절한 quoting 과 escaping 과 함께 “(pk_col1, pk_col2, …) >= (lb1, lb2, …) 인 행” 을 select 하는 WHERE 절로 쓰여진, chunk 의 첫 행과 동등한 출력 가능 표현식.
  • upper_bound: 암묵적 — 다음 chunk 의 lower bound 또는 표의 끝.
  • chunk_id: (표, chunk) 쌍당 순차적 정수.
  • checksum: PK 순서로 chunk 의 행에 대한 CRC, WHERE-bound 가 적용되고 LIMIT chunk_size 가 있는 표 자체에서 read 되는 SELECT BIT_XOR(MD5(...)) 쿼리로 계산.
// chksum_calculate_checksum — checksumdb.c:1686
chksum_calculate_checksum (PARSER_CONTEXT *parser, const OID *class_oidp,
const char *table_name, DB_ATTRIBUTE *attributes,
PARSER_VARCHAR *lower_bound, int chunk_id,
int chunk_size)
{
/* 이 chunk 에 대한 실제 checksum SELECT 빌드 */
checksum_query = chksum_print_checksum_query (parser, table_name, attributes,
lower_bound, chunk_id, chunk_size);
/* master 의 result-table-INSERT replay 가 slave 로 올바른 컨텍스트
* 를 운반하게 복제 record 정보 설정;
* SELECT 동안 chunk 가 일관되게 user 표에 SHARED lock 획득 */
chksum_set_repl_info_and_demote_table_lock (table_name, checksum_query, ...);
/* checksum SELECT 실행; 결과 표에 결과 행 INSERT;
* chksum_get_next_lower_bound 통해 다음 chunk 로 진행 */
}

Lock 패턴이 중요하다 — 배타 LOCK TABLE WRITE 는 reader 를 block 한다. LOCK TABLE READ (SHARED)가 user 표에 획득되어 다른 reader 는 허용하고 chunk 의 SELECT 동안만 writer 를 block 한 다음, downgrade / release 된다. 이는 일관성과 online 동작 사이의 trade-off 다 — lock 동안 복제는 잠시 queue 되었다가 잠시 멈췄다가 재개된다는 점이다.

chksum_get_next_lower_bound (chunk 사이에 호출됨)가 다음 chunk 의 첫 행이 행의 PK 를 찾는 짧은 SELECT 를 발사한다는 점이다:

SELECT pk_col1, pk_col2, ...
FROM <table>
WHERE (pk_col1, pk_col2, ...) >= (lb1, lb2, ...)
ORDER BY pk_col1 ASC, pk_col2 ASC, ...
LIMIT <chunk_size>;
-- 결과의 마지막 행이 boundary

limited 결과의 마지막 행 이 다음 chunk 의 lower bound 가 된다. 결과가 chunk_size 보다 적으면 표가 끝에 있는 것이고 루프가 종료된다.

가장 첫 chunk 를 chksum_get_initial_lower_bound 가 합성 최소 PK 표현식 — 보통 PK 컬럼 type 에 따라 NULL 또는 MIN(pk) 파생물 — 을 돌려준다. 모든 행을 select 한다.

struct chksum_arg {
int chunk_size; // chunk 당 행; 기본 10000
int sleep_msecs; // throttle 위해 chunk 사이 pause
int timeout_msecs; // chunk 당 lock-wait timeout
bool resume; // 이전 실행에서 이어받기
bool cont_on_err; // 실패한 표 건너뛰고 계속
bool schema_only; // 데이터 chunk 건너뛰고 schema 검사만
dynamic_array *include_list; // 명시적 표 allowlist
dynamic_array *exclude_list; // 표 blocklist (기본: 카탈로그 표)
};

다섯 운영 노브:

  • --chunk-size. 더 작은 chunk = 더 많은 lock-acquire 오버헤드
    • 더 많은 결과 행, 하지만 더 짧은 chunk 당 lock window. 기본 10,000 은 OLTP 표에 production sweet spot 이다 — 상당히 넓은 행이나 상당히 바쁜 표는 더 작게 원할 수 있다는 점이다.
  • --sleep-msecs. 검증기가 양측을 saturate 하지 않게 chunk 사이에 N ms sleep — 특히 SELECT 가 live 워크로드와 경쟁하는 master 에 관련됨.
  • --timeout-msecs. chunk 당 SHARED lock 을 얼마나 기다릴 지; expiration 이 chunk 를 abort (그리고 --cont-on-err 에 따라 표 또는 전체 실행).
  • --resume. 이전 결과 표를 읽어 표당 가장 높은 저장된 chunk_id 부터 시작해 이미 checksum 된 chunk 를 건너뛴다. chksum_get_prev_checksum_resultschksum_Prev_results 를 채우고, chksum_set_initial_chunk_id_and_lower_bound 가 소비 한다는 뜻이다.
  • --schema-only. 데이터를 건너뛰고 schema 표만 다시 검사. full data 실행 전 빠른 pre-flight 로, 또는 per-row 비용 없이 지속적인 DDL-drift 모니터링에 유용.

Include 와 exclude list 가 chksum_need_skip_table 에 의해 표별 로 처리된다는 점이다:

  • include_list 가 비어 있지 않으면 list 된 표만 checksum 된다.
  • exclude_list 는 항상 include list 를 이긴다.
  • 기본 exclude_list 는 카탈로그 표 (db_class, db_attribute 등)를 포함하며 — 그 외에는 거대한 schema-noise 를 생산할 것이다.

두 list 는 --include-class-file / --exclude-class-file 로 명시된 파일 (한 줄에 한 표 이름)에서 읽거나, 반복된 --include-class / --exclude-class 플래그로 직접 설정한다.

데이터 실행이 시작하기 전에 checksumdb 가 모든 target 표를 schema checksum 을 계산한다는 점이다:

// 각 target 표에 대해:
chksum_update_current_schema_definition (table_name, repid);
// 클래스 정의 (type 을 가진 컬럼 list, 제약, owner)를 텍스트
// canonical 형식으로 직렬화;
// MD5 계산; 저장된 schema 행과 비교;
// 없으면 새 행 insert;
// mismatch 면 chksum_report_schema_diff 에 divergence 표시.

Schema checksum 은 현재 revision 시점 (repid 는 카탈로그의 schema revision id) 으로 계산된다. Slave 에서 같은 canonical 형식 직렬화가 로컬에서 돌고 재계산된 checksum 이 비교된다는 뜻이다.

표의 schema 가 diverge 하면, 데이터 chunk checksum 이 여전히 시도되지만 (또한 diverge 하거나 우연히 매치할 수 있다 — 둘 다 기록), 실행은 schema mismatch 를 primary 에러로 보고한다.

실행이 완료 (또는 부분 완료)된 후, chksum_report 가 출력 파일 을 열고 세 절을 쓴다는 점이다:

  1. Header — chksum_report_header: 데이터베이스 이름, 실행 시작 시간, 출력 표 이름, --chunk-size, include/exclude list.
  2. Summary — chksum_report_summary: 표별 합계 (검사된 chunk, 매치된 chunk, 미스매치 chunk, schema 상태).
  3. Diff — chksum_report_diff (데이터)와 chksum_report_schema_diff (schema): PK lower bound, master vs slave checksum, 복제 정보를 가진 미스매치별 행.

Report 는 사람이 읽을 수 있는 텍스트 다. Downstream 도구는 보통 모니터링 통합을 위해 summary 표를 파싱한다.

--resume 는 중단될 수 있는 long-running 검증을 위한 운영 모드 이다 (운영자가 실행을 중지, slave 가 apply 동안 재시작 등). Resume 시:

  1. chksum_get_prev_checksum_results 가 결과 표의 모든 기존 행을 in-memory linked list chksum_Prev_results 로 읽는다 (이미 완료된 가장 높은 chunk_id 를 가진 표당 한 entry).
  2. chunk-start 시점에 각 target 표를 chksum_set_initial_chunk_id_and_lower_bound 가 list 를 참조; 표에 이전 결과가 있으면 chunk_id = prev_chunk_id + 1 와 이전 행의 last_lower_bound 에서 lower_bound 를 설정. 그 외에는 chunk 1 과 합성 최소-PK bound 부터 시작한다는 뜻이다.

결과 표는 따라서 출력이자 checkpoint 다 — 결과 행을 slave 로 전파하는 같은 WAL 복제가 master 의 부분 실행에서의 재시작도 지원한다.

심볼역할
checksumdb (진입, cubrid checksumdb 가 호출)UTIL_FUNCTION_ARG-style 진입; 옵션 파싱; chksum_start 호출
chksum_start메인 루프; 각 target 표를 schema 검사 그 다음 데이터 chunk
chksum_init_checksum_tables결과 표 drop + 재생성, 또는 resume 시 이전 결과 read
chksum_drop_and_create_checksum_tableresult/schema 표의 카탈로그 수준 drop + create
chksum_get_prev_checksum_results / _get_checksum_result / _free_resultsResume-mode 이전-실행 consumer
chksum_need_skip_tableInclude/exclude 술어
심볼역할
chksum_calculate_checksumper-chunk: SELECT 빌드, repl info 설정, lock, 실행, 결과 insert, 진행
chksum_print_checksum_querychunk 위에 SELECT BIT_XOR(MD5(...)) 빌드
chksum_print_pk_listPK-컬럼 list 렌더
chksum_print_attribute_list전체 attribute list 렌더 (checksum query 에 사용)
chksum_print_select_last_chunkBoundary-finding SELECT
chksum_print_lower_bound_string / chksum_get_quote_escaped_lower_boundPK tuple 을 quoted SQL literal 로 렌더
chksum_get_initial_lower_bound / chksum_get_next_lower_boundper-chunk boundary 진행
chksum_set_initial_chunk_id_and_lower_boundResume-aware 첫-chunk setup
심볼역할
chksum_set_repl_info_and_demote_table_lockuser 표에 SHARED lock 획득, master 의 result-table-INSERT 가 올바른 metadata 로 복제되도록 복제-record 컨텍스트 설정
심볼역할
chksum_update_current_schema_definition현재 schema 직렬화, MD5 계산, 기존 행 update
chksum_insert_schema_definition이전 행이 없을 때 insert
심볼역할
chksum_print_select_master_checksum결과 표에서 master checksum 을 read 하는 SELECT 빌드
chksum_print_update_master_checksummaster checksum 을 write 하는 UPDATE 빌드
chksum_update_master_checksumupdate 실행
심볼역할
chksum_reporttop-level report driver
chksum_report_open_filereport 출력 파일 열기
chksum_report_headerheader
chksum_report_summary표별 합계
chksum_report_diffper-mismatch 데이터 행
chksum_report_schema_diffper-mismatch schema 행
심볼경로
CHKSUM_RESULT (struct)src/executables/checksumdb.c:84
CHKSUM_ARG (struct)src/executables/checksumdb.c:94
chksum_calculate_checksumsrc/executables/checksumdb.c:1686
chksum_print_checksum_querysrc/executables/checksumdb.c:1082
chksum_get_next_lower_boundsrc/executables/checksumdb.c:1319
chksum_startsrc/executables/checksumdb.c:1798
chksum_report_open_filesrc/executables/checksumdb.c:160
chksum_report_headersrc/executables/checksumdb.c:175

심볼 이름이 정규 anchor 이고, 라인 번호는 updated: 날짜에 스코프된 힌트이다.

  • 결과 표 자체가 복제된다. master 의 checksum 행을 복제하는 요점은 slave 의 로컬 재계산이 master 에 별도 연결 없이 비교할 무언가를 가지게 하는 것이다. non-HA 데이터베이스는 schema-only 모드 이상으로는 checksumdb 의 사용처가 없다는 뜻이다.
  • 복제 지연이 중요하다. master 가 chunk 를 끝낸 순간까지 slave 가 따라잡지 못했다면, slave 의 재계산된 checksum 이 표의 이전 상태에 대한 것이 된다. 운영자는 (cubrid heartbeat status 또는 db_ha_apply_info_* 지연 지표로) 복제가 catch-up 되었다고 확인된 후에만 checksumdb 를 돌린다는 점이다.
  • SHARED lock 의미. chunk 당 lock 은 LOCK TABLE READ, SELECT 후 downgrade. 같은 표에 대한 동시 INSERT/UPDATE 가 chunk 당 잠시 queue 되며, 표의 chunk 수만큼 곱해진다. 매우 큰 표를 누적 영향이 의미 있다 — --sleep-msecs 가 정확히 그 영향을 분산시키기 위해 존재한다.
  • PK 가 필수다. primary key 가 없는 표는 안정된 순서를 따라 chunk 할 수 없다. checksumdb 가 경고와 함께 건너뛰고 report 에 skip 을 기록한다는 뜻이다.
  • 카탈로그-표 제외가 기본에 hard-coded. exclude list 기본값에 db_class, db_attribute, db_attr_setdomain_elm 등이 포함 된다. 카탈로그 자체를 검증하고 싶은 운영자는 exclude list 를 override 해야 하지만, 카탈로그가 또한 가장 가능성 있는 복제 drift 의 희생자다 — 그래서 이는 정확성 호출이라기보다는 noise- suppression 기본값에 가깝다.
  • Schema 직렬화는 canonical 이어야 한다. 양측이 같은 schema 에 서 byte-identical 텍스트를 생산해야 한다. 그렇지 않으면 모든 checksum 비교가 거짓으로 drift 를 표시할 것이다. 직렬화기가 명시적으로 whitespace, attribute 순서 (storage-order 가 아닌 attribute-order 사용), quoting 을 normalise 한다.
  • chunk 당 lock backoff. timeout 된 chunk 가 실패로 기록된다 — loop 안 backoff 와 함께 retry 가 없다는 뜻이다. 오래 걸리는 application 트랜잭션이 따라서 실행 안에서 다시 시도되지 않는 chunk grid 의 gap 을 남길 수 있다.
  • MVCC 스냅샷 bracketing. checksumdb 가 chunk 너머로 스냅샷 을 pin 하지 않는다 — 각 chunk 가 자기 MVCC 스냅샷에서 read 한다. 실행 동안 chunk 사이를 옮기는 행이 빠지거나 (둘 어느 chunk 에도 안 카운트) 이중 카운트 될 수 있다는 뜻이다. 실제 divergence 감지가 따라서 동시 쓰기로부터의 작은 false-positive 비율을 견딘다 — 운영자는 보통 checksumdb 를 quiesced 또는 read-mostly window 에 돌린다.
  • Cross-version 호환성. 직렬화된 schema 형식은 schema 의 repid 에 묶여 있다 — schema 변경이 진행 중인 cross-version 복제는 의미 없는 diff 를 생산할 수 있다. 해결책은 master 와 slave 가 같은 repid 일 때만 checksumdb 를 돌리는 것이다.
  • ECC-style 자동 수리 없음. checksumdb 가 차이를 보고하지만 reconcile 하지는 않는다. 수리는 수동 개입 또는 새 master backup 에서 slave 를 re-baseline 해야 한다는 점이다.
  • src/executables/checksumdb.c — 전체 utility (단일 파일, ~2400 줄)
  • src/executables/AGENTS.md — agent 가이드
  • 인접 문서: cubrid-ha-replication.md (checksumdb 가 의존하는 복제 채널), cubrid-cdc.md (변경 capture 를 위한 CDC 대안; checksumdb 가 HA 를 검증, CDC 가 변경을 publish), cubrid-cub-admin.md §데이터베이스-admin verb (checksumdb 는 통합 cubrid admin 진입의 CS_ONLY verb), cubrid-master-process.md (cub_master 는 연결 broker 로만 관여; checksumdb 자체는 libcubridcs 에서 돈다)