며칠 전, 실시간 로그 데이터를 정제하는 작업을 하던 중 이상한 결과를 마주했습니다.
status != 'deleted' 조건을 주고 필터링을 했는데, 결과 데이터가 너무 적었습니다.
“뭐지? deleted가 아닌 데이터만 남긴 건데, 왜 이렇게 많이 빠졌지?”
처음엔 단순히 삭제 상태가 많았겠거니 하고 넘어가려다, 궁금해서 groupBy("status").count()를 찍어봤습니다.
그 순간, 충격적인 결과가 나왔습니다. 생각보다 NULL이 굉장히 많았던 것이죠.
그리고 깨달았습니다. 아, 이건 단순히 조건식이 잘못된 문제가 아니라, Spark의 NULL 처리 방식에 대한 이해 부족이었다는 걸요.
PySpark의 삼진 논리(Tri-valued logic)
우리는 보통 이런 식으로 조건을 걸죠:
df.filter(col("status") != "deleted")
자연스럽게 “삭제 상태가 아닌 것”만 남겠지? 라고 생각합니다.
하지만 PySpark의 조건식은 SQL의 삼진 논리를 따릅니다.
즉, Boolean 평가 시 true, false, unknown(NULL) 이렇게 세 가지 결과가 존재합니다.
status 값 | status != "deleted" | 평가 필터 통과 여부 |
'active' | true | ✅ 포함됨 |
'deleted' | false | ❌ 제외됨 |
NULL | unknown | ❌ 제외됨 |
여기서 포인트는 NULL이 'false'가 아니라는 것입니다.
조건을 만족하지 않는 것이 아니라, 애초에 조건 평가가 안 되는 겁니다.
그래서 filter는 이 NULL 값들을 모두 제거해버립니다.
우리는 필터를 썼지만, 의도한 결과가 나오지 않은 거죠.
이 오류 하나로 인해 생각보다 큰 문제가 파생됐습니다.
1. 통계 수치 왜곡
분명히 전체 데이터는 1000만 건이 넘었는데, status != 'deleted' 조건 이후 남은 건 절반도 안 됐습니다.
나중에 확인해보니 status가 NULL인 데이터가 전체의 40% 이상이었습니다.
2. 모델 학습 데이터 편향
모델링 단계에서 "status != 'deleted'"로 데이터 필터 후 학습을 진행했는데,
status가 NULL인 데이터를 모두 제거하면서 특정 클래스가 과소 대표되는 문제가 발생했습니다.
3. 사용자 리포트 누락
운영 리포트 상에서 특정 상태가 통계에 아예 안 잡히는 문제가 생겼고,
원인은 status NULL인데 필터에서 빠졌기 때문이었습니다.
NULL을 배제하지 말고, 직접 다뤄야 한다
1. isNotNull()과 AND 조건 사용
from pyspark.sql.functions import col
filtered_df = df.filter(
(col("status").isNotNull()) & (col("status") != "deleted")
)
이렇게 명시적으로 NULL 값을 제외하는 조건을 추가하면, 우리가 원하는 정확한 행만 남길 수 있습니다.
2. 사전 분포 확인 (groupBy 필수)
df.groupBy("status").count().show()
조건 필터링 전에 값 분포를 확인하는 건 실무에서 정말 좋은 습관입니다.
NULL이 많은 컬럼일수록 더더욱 필요하죠.
3. NULL을 대체해서 정규화 처리 (분석/시각화 목적)
from pyspark.sql.functions import coalesce, lit
df = df.withColumn("status", coalesce(col("status"), lit("unknown")))
이렇게 하면 NULL이 아닌 "unknown"이라는 명확한 값으로 대체되기 때문에
분석이나 리포트에서 값 누락 없이 분포를 정확히 파악할 수 있습니다.
- 삼진 논리에서는 NULL은 != 조건에도 포함되지 않는다 → 명시적 예외 처리가 필요합니다.
- isNotNull()을 함께 쓰면 조건식이 true/false로 명확히 판단됩니다.
- NULL을 "unknown"으로 치환하면 필터 외에도 groupBy, 집계 등에서 의도치 않은 누락을 방지할 수 있습니다.
- groupBy로 분포를 미리 확인하는 습관은 실무에서 오류를 미연에 방지하는 강력한 방법입니다.
상황 | 잘못된 코드 | 실제 문제 |
기본 필터 | df.filter(col("status") != "deleted") | NULL 포함 데이터 누락 |
조인 키 전 필터 | status가 NULL인데 필터로 걸러져 조인 누락 | 일부 조인 실패 |
시각화 집계 | NULL 빠진 상태로 시각화 | 리포트 불완전 |
방지 패턴
if "status" in df.columns:
df = df.filter(
(col("status").isNotNull()) & (col("status") != "deleted")
)
else:
raise ValueError("status 컬럼이 존재하지 않습니다.")
요약
- PySpark의 filter에서 != 조건은 NULL을 고려하지 않으면 의도와 다르게 동작합니다.
- NULL은 false가 아니라 unknown → 명시적 .isNotNull() 또는 치환 필요
- 정제, 분석, 모델링 단계 모두에서 NULL은 사라지거나 무시되기 쉽지만,
그만큼 정확한 대응이 필요합니다. - “값이 없다”는 사실 자체를 정보로 바꿔주는 과정이 실력이라고 생각합니다.
'Data Engineer' 카테고리의 다른 글
'정규화(Normalization)'를 과도하게 적용하면 Spark 성능이 저하될까? (0) | 2025.04.24 |
---|---|
PySpark vs. Scala Spark 어떤 언어를 선택해야할까? (0) | 2025.04.02 |
Airflow XCom 태스크 간 데이터 전달 (0) | 2025.03.28 |
Airflow로 데이터 품질 테스트 (0) | 2025.03.28 |
Apache Spark vs Apace Flink (0) | 2025.03.26 |