← 학습 카테고리

Learn

Spark

38개 모듈 · 현재 35번째

Spark 모듈 35/38 spark-learn-35

partitionBy와 파티션 프루닝 — 어떤 컬럼을 파티션 키로 고를 것인가

PySpark Tutorial: Build a Real Pipeline and Read the Spark UI — Darshil Parmar PySpark Tutorial: Build a Real Pipeline and Read the Spark UI — "Write it out, then read it back like a stranger would" 구간 (24~26페이지)

이 모듈을 다 읽으면

  • Parquet에 쓰고 다시 읽었을 때 city 컬럼이 스키마 끝으로 이동하는 이유를 파티션 디렉터리 구조로 설명할 수 있다
  • partitionBy("city")는 적절하고 partitionBy("order_date")는 부적절한 이유를 카디널리티 관점에서 설명할 수 있다
  • 직관적 예상(매출 1위 도시가 손실 1위일 것이다)이 실제 데이터로는 빗나갈 수 있다는 사례를 통해 집계 결과를 직접 확인해야 하는 이유를 설명할 수 있다

daily.write.partitionBy("city").parquet(...)로 저장한 뒤 다시 읽으면 order_date, revenue, orders, city 순서로 스키마가 바뀌어 있는데, city가 더 이상 파일 내부에 저장되지 않고 city=Ahmedabad/ 같은 폴더 이름 자체가 되었기 때문이다. 이 폴더 구조 덕분에 city == "Mumbai" 필터는 다른 7개 폴더를 아예 열지 않는 파티션 프루닝을 얻는다. city를 파티션 키로 고른 것은 적절한데, order_date로 나눴다면 35개 폴더에 각 8행씩 담기는 작은 파일 문제를 스스로 만들었을 것이기 때문이다 — 파티션 키는 쿼리가 실제로 필터하는 컬럼이면서 값의 개수(카디널리티)가 관리 가능한 컬럼이어야 한다. 상태를 취소/반품으로 바꿔 도시별 손실 매출을 다시 집계해 보면, 배송 완료 매출 1위인 Mumbai가 아니라 Bengaluru가 손실 매출 1위(116,869.84, 183건)로 나온다 — 주문 수는 더 많은데 금액은 더 적은, 일별 테이블이 있어야 발견할 수 있는 패턴이다.

Parquet에 쓰고 다시 읽으면 무엇이 바뀌는가

daily.write.mode("overwrite").partitionBy("city").parquet("out/daily_revenue")로 저장한 뒤 spark.read.parquet(...)로 다시 읽어 printSchema()를 호출하면 order_date, revenue, orders, city 순서로 나온다. city가 스키마 끝으로 이동한 이유는 이제 각 Parquet 파일 내부에 city 값이 저장되지 않기 때문이다 — city=Ahmedabad/part-00000-....snappy.parquet처럼 city 값 자체가 디렉터리 이름이 된다. 또한 CSV와 달리 Parquet은 타입을 그대로 보존하므로 order_date는 문자열이 아니라 date 타입으로 정확히 돌아온다.

핵심 포인트

  • partitionBy("city")로 저장하면 city 값이 파일 내부가 아니라 city=값/ 폴더 이름으로 저장된다
  • Parquet은 타입을 보존하므로 CSV로는 불가능했던 order_date의 date 타입이 다시 읽을 때도 유지된다

partitionBy(city)가 옳고 partitionBy(order_date)가 틀린 이유 — 카디널리티

이 폴더 구조가 바로 파티셔닝을 하는 이유다. spark.read.parquet(...).filter(col("city") == "Mumbai")를 실행하면 Mumbai 폴더 하나만 읽고 나머지 7개 폴더는 아예 열지 않는데, 이를 파티션 프루닝이라 한다. city를 파티션 키로 고른 것이 적절한 이유는 쿼리가 실제로 이 컬럼을 필터하면서 값의 종류(카디널리티)가 8개로 관리 가능한 수준이기 때문이다. 반대로 order_date로 파티셔닝했다면 35일치 데이터가 35개 폴더에 각각 8행씩 나뉘어 담기는데, 이는 앞서 확인한 small files problem을 스스로 만드는 선택이 된다. 파티션 키는 '쿼리가 필터하는 컬럼'과 '값의 개수가 관리 가능한 컬럼'이라는 두 조건을 동시에 만족해야 한다.

핵심 포인트

  • 쿼리가 필터하는 컬럼을 파티션 키로 잡으면 해당 값이 아닌 폴더는 아예 열리지 않는 파티션 프루닝을 얻는다
  • order_date처럼 카디널리티가 높은 컬럼으로 파티셔닝하면 폴더당 행 수가 적어져 small files problem을 스스로 만든다
  • 적절한 파티션 키는 '쿼리 필터 대상'이면서 '관리 가능한 카디널리티'라는 두 조건을 함께 충족해야 한다

예상이 빗나갈 수 있다 — 직접 집계해야 아는 것

필터를 취소·반품 상태로 바꿔 도시별 손실 매출을 집계해 보면, 배송 완료 매출 1위였던 Mumbai가 손실 매출에서도 1위일 것이라는 직관적 예상은 빗나간다. 실제로는 Bengaluru가 116,869.84(183건)로 1위이고 Mumbai는 102,865.69(186건)로 2위다. Mumbai가 주문 건수는 더 많은데도 손실 금액은 더 적은 셈인데, 이런 패턴은 원본 트랜잭션 로그를 하나하나 훑어서는 알아채기 어렵고, 이번 실습에서 만든 것과 같은 일별 집계 테이블이 있어야 비로소 드러난다.

핵심 포인트

  • 매출 1위 도시가 손실 1위일 것이라는 직관은 실제 집계 결과와 다를 수 있다
  • Bengaluru는 Mumbai보다 주문 건수는 적지만 손실 금액은 더 크며, 이런 패턴은 일별 집계 테이블이 있어야 드러난다