데이터 엔지니어로 일하다 보면 크고 작은 ETL 파이프라인을 많이 만들게 된다. 그때마다 비정상 데이터 처리나 정규화 등 필수적으로 거치는 작업들이 있다. 이번 글에서 현업에서 내가 반복적으로 부딪혔던 문제와 처리했던 방식을 한번 정리해보았다.
시간 단위 정규화
데이터 소스에서 시간이 문자열/타임스탬프/epoch 등이 섞여 들어온다. 때문에 스키마를 일관되게 유지하기 위해서 정규화가 필수이다.
문제 패턴
•
스키마는 string인데 값은 yyyy-mm-dd, yyyy-mm-dd HH:MM:SS, yyyy-mm-dd HH:MM:SS.sssZ 이 뒤섞임
•
epoch가 초/밀리초/마이크로초 단위로 섞여서 들어옴
•
MySQL 등에서 0000-00-00 같은 제로 데이트가 기본값으로 존재
처리 방식
•
raw 데이터를 그대로 저장하는게 아닌, 가공하여 저장하는 경우에는 데이터팀 자체적으로 시간 표현방식을 통일하는것을 추천. 그래야 분석가들이 일관된 방식으로 시간 컬럼들을 파싱 할 수 있다.
•
호환성이 제일 좋은 iso-8601 형태 yyyy-mm-ddTHH:MM:SS.sssZ 추천
•
정규화 유틸을 프로젝트 공용 time_utils.py 에 모듈화 해두면 프로젝트 전체에서 재사용할 수 있음
•
0000-00-00 같은 비정상 값은 유의미한 값이 아니므로 결측처리 하거나, 시간으로 집계하는 경우라면 아예 제외하는 로직을 넣어도 무방
타임존
ETL 파이프라인을 만들다 보면 다양한 소스에서 데이터를 수집하게 된다.
이때 시간대를 통일하지 않으면 분석 과정에서 큰 혼란이 생긴다. 실제로 “데이터 값이 이상해요”라는 제보가 들어올 때 보면, 원인은 대부분 시간 조건이나 타임존 때문인 경우가 많다.
따라서 ETL 개발 시점부터 타임존을 항상 인지하고 일관되게 처리하는 것이 중요하다.
문제 패턴
•
타임존 누락: 2025-08-27 12:00:00 처럼 tz 정보가 아예 없으면 UTC인지, KST인지 알 수 없음
•
잘못된 tz 태깅: 실제로 KST 인데 UTC가 붙어 들어오는 경우 ETL 과정에서 한번더 +9H 보정해 최종적으로 +18 밀린 값이 되는 경우도 있음
•
여러 소스 혼합: 광고 데이터는 UTC, 주문 데이터는 KST → 조인이나 집계 시 시각이 맞지 않아 집계 결과가 어긋남
권장 처리 방식
•
이상적인 처리: 저장/전송 시점은 UTC로 맞추고, 리포팅/분석 시점에만 현지시간 변환
•
혹은 입력 데이터만 UTC로 통일하고 파이프라인 내부에서는 KST로 통일해 저장
•
타임존 관련 내용을 컬럼 코멘트에 적어두거나 문서로 꼭 남겨둬야 헷갈리지 않을 수 있음
Pandas 타입
ETL 작업에서 pandas를 사용하다 보면 스키마 불일치 문제를 자주 겪는다.
특히 DB → pandas → Parquet 같은 흐름에서 의도치 않게 타입이 변해버려 Athena/BigQuery 같은 쿼리 레이어에서 오류가 나는 경우가 많다.
문제 패턴
•
Null → NaN 변환
DB의 NULL 값이 pandas로 들어오면 NaN(Not a Number)으로 표현되는데, NaN은 float 타입에서만 표현 가능
◦
예: int 컬럼이 하나라도 NaN이 있으면 전체가 float64로 바뀌어 스키마 어긋남
•
PyArrow 추론 오류
pandas DataFrame을 Parquet으로 저장할 때 PyArrow가 자동 추론한 타입이 DB 스키마와 다르게 잡히는 경우
처리 방식
•
저장 직전 최종 단계에서 명시적 캐스팅을 해줘야함
•
스키마는 별도 정의 파일로 관리하면서 저장 단계에서 맞춰주는게 안전
문자열
문자열은 생각보다 훨씬 많은 문제를 일으킨다. 단순히 텍스트처럼 보이지만, 실제로는 인코딩·공백·포맷 차이 때문에 조인이나 집계가 깨지는 경우가 많다.
워낙 다양한 문제가 있어 중요하게 사용될 값이라면 EDA를 통해 데이터를 확인하고 도메인 규칙에 맞춰 로직을 정의해야한다. 여기서는 대표적인 문제만 적었다.
문제 패턴
•
인코딩 혼재: CP949 / Shift-JIS / UTF-8 혼합, BOM(Byte Order Mark) 포함 CSV
•
보이지 않는 문자: 보이지 않는 공백과 특수문자, 전각/반각 문자
•
포맷 불일치: 010-1234-5678, 10-1234-5678, 01012345678
◦
사실상 같은 값인데도 문자열 비교·집계 시 다르게 처리됨
◦
(심지어 000-0000-0000 같은 값도 있음)
•
대소문자 혼합: ABC, abc, Abc
처리 방식
•
입력 인코딩 명시: 가능하면 UTF-8 고정
•
strip + normalize: 문자열 좌우 공백 제거, 유니코드 정규화(NFC) 적용
•
일관된 포맷팅: 대소문자 통일(lower()), 숫자·하이픈 제거 등 도메인 규칙에 맞게 정규화
숫자 파싱 / Boolean
숫자와 Boolean 값은 DB나 외부 소스마다 표현 방식이 제각각이라 변환 과정에서 문제가 자주 발생한다.
문제 패턴
•
숫자: 1,234 나 “₩10,000” 같은 경우 int() 변환 실패
•
boolean: 시스템마다 달라서 tinyint(0/1), 문자열 "True"/"False", "Y"/"N", "YES"/"NO", 혹은 대소문자 섞여 들어옴
처리 방식
•
숫자: 기호/콤마 제거 후 파싱
◦
소수점/지수 표기법(예: 1.2E3)도 있을 수 있으므로 사전 데이터 샘플링 후 로직 설계 필요
•
boolean: 매핑표를 유틸 함수로 만들어두고 재사용
◦
예: "1","true","y","yes" → True "0","false","n","no" → False
대용량 처리
ETL 파이프라인을 만들 때 흔히 하는 실수 중 하나는, 무심코 모든 데이터를 한번에 조회해서 처리하고 저장하는 방식이다. 하지만 하루치 데이터를 한번에 메모리에 올리게 되면 OOM이 발생하기 쉽다.
문제
•
하루치 데이터가 매우 클 경우 OOM 발생 가능성 높아짐
•
OOM이 나지 않더라도 단일 프로세스에서 처리 시간이 급격히 늘어나 효율성이 좋지 않음
처리 방식
•
읽기/쓰기 모두 배치 단위로 나눠서 처리
•
파이썬에서는 generator + yield 사용해 데이터를 조금씩 불러오고 처리한 뒤 바로 내보내는 방식으로 메모리 사용량을 최적화
•
데이터 저장도 대량 단일 파일보다 일정 크기로 잘라 저장
def read_csv_in_chunks(path, chunksize=100_000):
for chunk in pd.read_csv(path, chunksize=chunksize):
yield chunk
for chunk in read_csv_in_chunks("s3://bucket/large.csv"):
chunk = transform(chunk)
write_parquet(chunk)
Python
복사
업스트림 데이터는 항상 정제되지 않은 상태라고 생각하고, 원칙을 세워 일관되게 지켜야 ETL 파이프라인을 안정적으로 운영할 수 있다.