"원천 시스템에선 분명 삭제했는데, 데이터 분석 쪽에서는 그 데이터가 그대로 보인다." 데이터 엔지니어가 자주 마주치는 문제 중 하나입니다. 오늘은 이 현상이 왜 일어나는지, 어떻게 해결하는지 정리하고, 끝에 면접 단골 질문도 같이 넣었습니다.
1. 데이터는 보통 두 곳에 저장됩니다
회사에서 데이터는 크게 두 종류의 시스템에 나뉘어 저장됩니다.
- 원천(Source) DB: 실제 업무 처리용. 주문 등록, 회원 가입, 결제 같은 작업이 실시간으로 일어나는 시스템입니다. Oracle, MySQL, PostgreSQL 같은 RDBMS가 흔히 쓰입니다.
- 데이터 웨어하우스 / 데이터 레이크: 분석·통계·리포트용. 원천 시스템에서 데이터를 주기적으로 가져와 쌓아두고, 마케팅·추천·BI 같은 작업에 씁니다. Hadoop 생태계가 대표적입니다.
업무 처리와 분석을 분리하는 이유는 단순합니다. 분석 쿼리는 무겁고 오래 걸리는데, 그걸 업무 시스템에서 돌리면 실제 사용자가 느려지거나 장애로 이어질 수 있기 때문입니다. 그래서 분석은 별도 환경에서 합니다.
문제는 두 시스템의 데이터를 어떻게 일치시킬 것인가입니다.
2. 데이터 동기화 배치는 보통 INSERT/UPDATE만 합니다
원천 DB의 데이터를 Hadoop 쪽으로 옮기는 작업을 배치(Batch)라고 부릅니다. 보통 새벽에 돌면서 "어제 새로 생긴 데이터", "어제 바뀐 데이터"를 가져와 쌓습니다.
여기서 처리하는 작업은 대부분 두 가지입니다.
- INSERT: 새로 생긴 행 추가
- UPDATE: 변경된 행 갱신
빠진 게 있죠. DELETE(삭제)입니다.
원천에서 누가 데이터를 지웠을 때, 배치가 그걸 인지하지 못하면 타겟 쪽엔 그 행이 그대로 남게 됩니다.
3. 실제 사례로 보기
다음과 같은 테이블이 있다고 해봅시다. PK는 고객ID + 주소순번입니다.
- 원천 DB 조회 결과
| 고객ID | 주소순번 | 주소 |
| 1234 | 9 | 서울시 강남구 |
- Hadoop 조회 결과 (같은 SQL)
| 고객ID | 주소순번 | 주소 |
| 1234 | 4 | 서울시 마포구 ← 원천엔 없음 |
| 1234 | 9 | 서울시 강남구 |
원천에서는 누가 옛날 주소(순번 4번)를 깔끔하게 삭제했는데, Hadoop엔 여전히 남아있습니다. 이 상태를 데이터 정합성이 깨졌다고 표현합니다.
이대로 분석을 돌리면 이 고객이 두 군데 사는 사람으로 잡힙니다. 마케팅 메일이 두 번 발송되거나, 통계가 부풀려지거나, 리포트 신뢰도가 떨어지는 등 후속 문제가 줄줄이 생깁니다.
4. Hadoop은 원래 DELETE 못 하는 거 아니었어?
여기서 중요한 개념이 등장합니다. Hadoop은 단일 시스템이 아니라 여러 저장 방식이 공존하는 생태계입니다.
HDFS (전통적인 Hadoop 저장 방식)
HDFS는 append-only(추가만 가능) 구조입니다. 한번 쓴 파일의 일부만 수정하거나 한 행만 지우는 작업이 매우 비효율적입니다. 보통 그런 작업이 필요하면 파일을 통째로 다시 쓰거나, 파티션 단위로 교체합니다.
이런 설계 철학은 "쓰기는 한 번, 읽기는 여러 번(write-once, read-many)"이라는 원칙에서 나왔습니다. 로그처럼 시간순으로 쌓이기만 하는 데이터에는 굉장히 효율적이지만, 행 단위 수정·삭제가 잦은 데이터에는 맞지 않습니다.
Kudu
Kudu는 Hadoop 생태계 안에 있지만 설계 목적이 다릅니다. INSERT, UPDATE, UPSERT, DELETE 모두 row-level로 지원합니다. LSM-tree 계열 구조라 행 단위 변경이 효율적이고, 그래서 자주 바뀌는 마스터성·트랜잭션성 데이터를 다룰 때 많이 씁니다.
💡 핵심 정리 "Hadoop은 DELETE 안 된다"는 말은 정확히는 HDFS에 한정된 이야기입니다. Kudu는 DELETE가 됩니다. 단, WHERE 절에 PK가 들어가야 효율적으로 동작합니다.
5. 그래서 DELETE 문제는 왜 생긴 걸까?
이제 사례를 다시 보면 정리가 됩니다.
- 데이터는 Kudu에 저장됨 → DELETE는 기술적으로 가능
- 그런데 배치 로직이 DELETE를 처리하지 않음 → 설계상의 누락
배치가 INSERT/UPDATE만 보고, "원천에서 사라진 행"은 따로 챙기지 않은 거죠. CDC(Change Data Capture, 변경 데이터 캡처) 같은 도구를 쓰지 않고 단순 증분 적재만 하면 흔히 발생하는 패턴입니다.
6. 잠깐, 그런데 데이터를 직접 지우는 경우가 있긴 한가?
여기서 의문이 들 수 있습니다. "실무에서 데이터를 진짜로 지우긴 하나? 보통 안 지우지 않나?" 정확한 지적입니다. 그래서 이 부분을 짚고 넘어가겠습니다.
일반론: 물리 DELETE는 실제로 드뭅니다
대부분의 운영 DB에서는 데이터를 물리적으로 삭제하지 않는 게 정석입니다. 이유는 다음과 같습니다.
- 감사·추적(audit) 요구사항: 금융·통신·공공 분야는 데이터를 함부로 못 지웁니다. 모든 변경 이력이 추적 가능해야 합니다.
- 법적 보존 의무: 전자상거래법, 개인정보보호법, 통신비밀보호법 등 일정 기간 보관해야 하는 데이터가 있습니다.
- 데이터 분석 가치: 탈퇴 회원, 취소된 주문, 폐기된 상품도 모두 분석 자산입니다. 지우면 통계가 왜곡됩니다.
그래서 대부분 soft delete 패턴으로 처리합니다.
-- 물리 삭제 대신
DELETE FROM members WHERE member_id = 1234;
-- 플래그만 변경
UPDATE members SET use_yn = 'N', del_dtm = SYSDATE WHERE member_id = 1234;
USE_YN, DEL_YN, STATUS 같은 컬럼이 그래서 거의 모든 테이블에 들어가 있는 거죠.
그럼에도 물리 DELETE가 일어나는 케이스
그런데도 다음과 같은 상황에서는 실제로 물리 삭제가 발생합니다.
- 이력성 테이블의 끝일자 관리: SCD Type 2(천천히 변하는 차원, Slowly Changing Dimension) 패턴을 적용한 테이블에서 이력 행을 잘못 끊거나 재처리할 때 물리 삭제가 들어가는 경우가 있습니다.
- 임시·작업성 테이블: 매일 truncate 후 재적재되는 staging 테이블, 일별 스냅샷 테이블 등.
- 개인정보 파기: GDPR이나 개인정보보호법상의 파기 요청(right to be forgotten) 처리 시.
- 데이터 정정: 잘못 들어간 데이터를 운영자가 수동으로 정리할 때.
- 마스터 데이터 정비: 중복 코드 통합, 잘못 발급된 ID 회수 등.
- 시스템 마이그레이션·정합성 보정 작업: 일회성으로 발생.
우리 사례를 다시 보면
처음에 봤던 테이블 이름이 ORD_RGLR_ORD_CTR_ADDR_H였는데, 끝의 _H는 보통 **History(이력)**를 의미하는 명명 규칙입니다. VLD_BGN_DTM(유효 시작일시), VLD_END_DTM(유효 종료일시) 컬럼이 있는 것도 SCD Type 2 이력 관리 패턴이고요.
이런 테이블은 운영 중 정정 작업이나 이력 재처리에서 물리 삭제가 종종 발생합니다. 일반적인 "DELETE는 드물다"는 원칙에서 벗어나는 대표적인 케이스죠.
그래서 모든 테이블에 일률 적용할 필요는 없습니다
PK 차집합 비교 방식은 비용이 들기 때문에, 모든 테이블에 적용하면 부담이 큽니다. 다음 우선순위로 적용하는 것이 합리적입니다.
| 1순위 | 이력 테이블(_H), SCD Type 2 적용 테이블 | ✅ 우선 적용 |
| 2순위 | 마스터 테이블 중 정정·통합이 잦은 것 | ⚠️ 적용 검토 |
| 3순위 | 트랜잭션 테이블 중 물리 삭제 정책이 있는 것 | ⚠️ 정책 확인 후 적용 |
| 4순위 | 순수 append-only 로그성 테이블 | ❌ 적용 불필요 |
💡 정리 일반론으로는 DELETE 동기화가 불필요해 보이지만, 이력 테이블이나 정정·파기가 일어나는 테이블에서는 반드시 필요합니다. 적용 대상을 잘 가려서 선택적으로 도입하는 것이 핵심입니다.
7. 해결 방법: PK 차집합 비교
해결 흐름은 다음과 같습니다.
- 원천에서 PK(기본키)만 추출합니다. PK는 행을 유일하게 식별하는 값이라 가볍습니다. 전체 컬럼을 다 가져오는 것보다 IO 부담이 훨씬 적습니다.
- Kudu에 있는 PK 목록과 비교합니다. "Kudu에는 있는데 원천엔 없는 PK"가 곧 삭제됐어야 할 행입니다.
- 그 PK들을 Kudu에서 DELETE 합니다.
- 그 다음 평소처럼 INSERT/UPDATE 배치를 수행합니다.
효율을 위한 SQL 힌트
원천에서 PK만 빠르게 떠오기 위해 보통 이런 힌트를 씁니다.
SELECT /*+ INDEX_FFS(t PK_TABLE_NAME) PARALLEL(t 4) */
PK_COL1, PK_COL2
FROM SOURCE_TABLE t
WHERE [조건];
- INDEX_FFS (Index Fast Full Scan): 테이블 전체 블록을 읽지 않고 PK 인덱스만 빠르게 스캔합니다. PK만 필요한 작업이므로 인덱스에 모든 정보가 있어 테이블 본체를 읽지 않아도 됩니다.
- PARALLEL(t 4): 병렬도 4로 실행해 추출 속도를 높입니다.
처리 순서가 중요합니다
DELETE는 반드시 INSERT/UPDATE보다 먼저 실행해야 합니다. 그렇지 않으면 같은 배치 사이클 안에서 새로 들어온 행이 PK 비교 시점에 잠깐 존재하다 사라지는 race condition이 발생할 수 있습니다.
반드시 챙겨야 할 안전장치
원천에서 PK 추출이 실패해서 결과가 0건으로 나오면 어떻게 될까요?
차집합 기준으로 계산하면 "Kudu에 있는 모든 행을 삭제해야 한다"가 됩니다. 그대로 실행하면 테이블이 통째로 비워집니다. 운영 환경에서 실제로 발생하는 사고 유형입니다.
그래서 다음과 같은 안전장치를 반드시 둡니다.
- 추출 건수 0건이면 작업 중단(Abort) 및 알람
- 삭제 비율(전체 대비 %)이 임계치를 넘으면 일시 정지 후 수동 검토
- 전일 대비 삭제 건수 변동이 비정상이면 Hold
질문들
데이터 엔지니어, 백엔드 개발자, DBA 면접에서 자주 묻는 패턴으로 정리했습니다.
Q1. HDFS와 Kudu의 차이를 설명해보세요.
핵심 답변 포인트
HDFS는 append-only, write-once-read-many 철학을 따르는 분산 파일 시스템입니다.
대용량 순차 쓰기·읽기에 강하지만 row-level 수정·삭제가 비효율적입니다. Kudu는 row-level INSERT/UPDATE/UPSERT/DELETE를 모두 지원하며 LSM-tree 계열 구조로 자주 변경되는 데이터에 적합합니다. 실무에서는 로그/이벤트성 대용량 데이터는 HDFS(Parquet)로, 자주 바뀌는 마스터성 데이터는 Kudu로 나눠 운영하는 경우가 많습니다.
Q2. Kudu에서 DELETE 시 주의할 점은?
핵심 답변 포인트
DELETE는 가능하지만 WHERE 절에 PK가 포함되어야 효율적입니다. PK 없이 임의 컬럼으로 DELETE 하면 풀스캔이 발생할 수 있습니다. 운영 정책상 DELETE 권한이 막혀 있는 환경도 있는데, 이런 경우 soft delete 컬럼(USE_YN='N' UPSERT)이나 tombstone 패턴으로 우회합니다.
Q3. 원천과 데이터 웨어하우스 간 정합성이 깨지는 대표 원인은?
핵심 답변 포인트
- INSERT/UPDATE만 동기화하고 DELETE를 미반영하는 경우 (오늘 다룬 케이스)
- 배치 실행 중 트랜잭션 타이밍 차이로 인한 dirty read
- CDC 누락 또는 처리 지연
- 원천 스키마 변경을 타겟이 따라가지 못하는 경우
Q4. 원천에서 DELETE가 발생했는지 인지하는 방법은?
핵심 답변 포인트
- CDC (Change Data Capture): Oracle GoldenGate, Debezium 등. 가장 정석적인 방법.
- PK 차집합 비교: 원천 PK를 추출해 타겟과 비교 (오늘 다룬 방법). CDC 도입이 어려울 때 현실적인 대안.
- 소프트 딜리트 정책: 원천에서 물리 삭제 대신 삭제 플래그만 변경하도록 정책을 설계.
Q5. PK 차집합 비교 방식의 장단점은?
장점
- CDC 인프라 없이 구현 가능, 도입 비용 낮음
- 로직이 단순하고 검증이 쉬움
단점
- 원천 전체 PK를 매번 추출해야 해서 테이블이 클수록 부담
- 실시간성이 떨어짐 (배치 주기에 의존)
- 추출 실패 시 전체 삭제 위험 → 반드시 안전장치 필요
Q6. INSERT, UPDATE, UPSERT, DELETE의 차이는?
명령 설명
| INSERT | 새 행 추가. 같은 PK가 이미 있으면 에러. |
| UPDATE | 기존 행 수정. 해당 PK가 없으면 아무 일도 일어나지 않음. |
| UPSERT | PK가 있으면 UPDATE, 없으면 INSERT. (Update + Insert) |
| DELETE | 행 삭제. |
Kudu에서 UPSERT가 자주 쓰이는 이유는, 적재 배치에서 "이 PK가 신규인지 기존인지"를 미리 확인할 필요 없이 한 번에 처리할 수 있기 때문입니다.
Q7. PK 추출 시 INDEX_FFS, PARALLEL 힌트를 쓰는 이유는?
핵심 답변 포인트
- INDEX_FFS: 테이블 본체 블록을 읽지 않고 PK 인덱스만 빠르게 스캔합니다. PK만 필요하므로 인덱스에 모든 정보가 있어 테이블 접근이 불필요합니다.
- PARALLEL: 병렬 실행으로 추출 시간 단축.
- 결과적으로 원천 DB의 부하를 최소화하면서 빠르게 PK 목록을 확보할 수 있습니다.
Q8. 원천이 Oracle이 아닌 MySQL/PostgreSQL이라면 같은 방식이 적용 가능한가?
핵심 답변 포인트
개념은 동일하지만 힌트 문법이 다릅니다. MySQL은 USE INDEX / FORCE INDEX, PostgreSQL은 옵티마이저가 자동으로 index-only scan을 선택하는 경우가 많아 별도 힌트 없이도 효율적인 경우가 많습니다. DBMS별로 통계 정보 수집 방식과 인덱스 구조가 다르므로 실행 계획을 반드시 확인하는 것이 중요합니다.
Q9. 만약 Kudu에서 DELETE 권한이 막혀 있다면 어떻게 우회할 수 있나요?
핵심 답변 포인트
- Soft delete: 삭제여부 컬럼을 두고 UPSERT로 'N' → 'Y' 변경. 단, 모든 조회 SQL이 이 필터를 포함해야 함.
- Tombstone UPSERT: 삭제 표시만 해두고 별도 컴팩션 잡에서 정리.
- 단점은 데이터가 실제로 줄지 않아 스토리지 사용량이 계속 증가한다는 점. 주기적인 정리 잡이 필요.
Q10. 모든 테이블에 DELETE 동기화 로직을 적용해야 하나요?
핵심 답변 포인트
아니요, 일률 적용은 비효율적입니다. PK 추출과 비교에는 비용이 들기 때문에 다음 우선순위로 적용 대상을 선별해야 합니다.
- 우선 적용: 이력 테이블(_H 명명, SCD Type 2 적용 테이블) — 정정·재처리에서 물리 삭제가 자주 발생.
- 적용 검토: 마스터 테이블 중 정정·통합이 잦은 것 (예: 코드 통합, 중복 ID 정리).
- 정책 확인 후 적용: 트랜잭션 테이블 중 물리 삭제 정책이 있는 것 (예: 개인정보 파기 대상).
- 적용 불필요: 순수 append-only 로그성 테이블, soft delete만 사용하는 테이블.
실무에서는 물리 DELETE보다 soft delete가 일반적이므로, 무조건 모든 테이블에 적용하기보다 삭제 패턴을 먼저 분석한 후 선택적으로 도입하는 것이 합리적입니다.
8. 마무리
전체 데이터를 매번 통째로 복사하면 정합성은 보장되지만, 대용량 환경에서는 시간·네트워크·시스템 부하 측면에서 현실적이지 않습니다. 그래서 데이터 엔지니어링은 늘 "바뀐 부분만 효율적으로 옮기는 방법"을 고민하는 분야가 됩니다.
오늘 본 PK 차집합 비교는 그중 하나의 답입니다. CDC가 가장 정석적인 방법이지만, 도입 환경이 갖춰지지 않았거나 비용·복잡도 문제가 있을 때 PK 차집합 방식이 좋은 대안이 됩니다.
결국 데이터 엔지니어링은 정확성, 속도, 비용 사이에서 균형을 잡는 일입니다. 이번 글이 그 의사결정의 한 장면을 이해하는 데 도움이 됐길 바랍니다.
더 공부해볼 만한 주제
- Apache Kudu 공식 문서: 구조, DML 제약, 파티셔닝 전략
- Debezium: 오픈소스 CDC 도구, 이벤트 기반 동기화의 표준
- Apache Iceberg / Delta Lake / Hudi: 최근 주목받는 "DELETE 가능한 데이터 레이크" 포맷들. HDFS의 한계를 극복하는 방향성
다음 글에서는 Iceberg가 어떻게 데이터 레이크 환경에서 row-level DELETE를 가능하게 만들었는지 다뤄볼 예정입니다.
'데이터엔지니어링 > 개념' 카테고리의 다른 글
| Impala가 Hive보다 빠른 이유 (2) | 2025.01.24 |
|---|---|
| Iceberg 에 대해 알아보자 (2) | 2024.12.30 |
| [하둡, 하이브로 시작하기] 빅데이터 기본 개념/처리 단계/빅데이터 에코시스템 (0) | 2024.12.19 |
| Oozie, HUE, Airflow의 차이점 (2) | 2024.12.11 |