이번 주 공부 발표

필터는 어디까지
내려가는가

Spark 기본구조 · Spark SQL · predicate pushdown 실측 · Dremel
2026-08-22 · Rami
무대

우리 파이프라인 위에서 따라간다

MongoDB 소스 Spark 엔진 (EMR on EKS) BigQuery 싱크 커넥터 v3 / v10 1부 · 2부 Spark 구조와 SQL 3부 커넥터 pushdown 실측 4부 Dremel — BigQuery의 원형 WHERE 절은 이 경로를 따라 아래로 내려간다
1부 · Spark 기본구조

클러스터 — 계획하는 뇌와 일하는 손

Driver 연산 그래프 기록 · 최적화 · task 분배 Cluster Manager executor를 어디에 몇 개 띄울까 (EMR on EKS) Executor task × 코어 수 실질 병렬도 = executor 수 × 코어 수
1부 · Spark 기본구조

코드 한 줄이 task가 되기까지

df.filter(…).groupBy(…).agg(…).write transformation은 기록만, action이 방아쇠 JOB Stage 1 scan · filter · map — 한 번의 훑기로 이어짐 task task task task Stage 2 groupBy 집계 — 같은 키를 한곳에 모아서 task task task task 하나 = 파티션 하나 셔플 = 가장 비싼 연산이자 stage 배리어 그래서 데이터를 옮기기 전에 줄이는 쪽이 항상 이긴다
1부 · Spark 기본구조

우리 덤프 경로의 실제 숫자

3.5.6
EMR 7.12.0 업그레이드 완료 — 3.2.0(EMR 6.6.0)에서 전환
200
shuffle.partitions 미설정 기본값, AQE coalesce가 정리
0.80
memory.fraction — 기본 0.6보다 높임, 압축은 전부 zstd
1 / 53
파티셔너를 튜닝한 잡
대부분은 읽고 나서 풀 셔플(repartition_without_key=True).
feedentity만 파티셔너 튜닝(128MB · 샘플 100)으로 셔플 자체를 없앴다 — 읽기 단계에서 잘 나누면 옮길 일이 사라진다.
0730 사내 Spark 시스템 구성 실측
2부 · Spark SQL

쿼리는 네 단계의 plan을 지난다

parsed 문법 확인 analyzed 컬럼·타입 확정 optimized 룰이 다듬은 논리 플랜 physical 실행 연산자 Catalyst = 룰의 목록 × 고정점 반복 PushDownPredicates · ColumnPruning · ConstantFolding … (최대 100회 반복) pushdown은 마법이 아니라 이 목록의 룰 하나다 explain(true)가 이 네 장을 다 보여준다
2부 · Spark SQL

룰이 Filter를 트리 아래로 민다

before Aggregate Filter dt ≥ '08-01' Project Scan (전체 읽기) PushDownPredicates deterministic 조건만 ·AND 조각 단위로 내림 after Aggregate Project Filter dt ≥ '08-01' Scan (필터 흡수 가능) 소스 안까지 내려가는 마지막 한 칸은 커넥터의 구현에 달려 있다
3부 · pushdown 실측

질문 — 증분 적재의 필터는 두 번 쓰인다

createAt ≥ 어제 ① 파티션 boundary 산정 읽기 구간을 나누기 위한 범위 계산에도 필터가 반영되는가? ② 실제 읽기 MongoDB가 걸러서 주는가, 아니면 다 받아서 Spark가 거르는가? 커넥터 문서(v3 · v10) 어디에도 답이 없다
3부 · pushdown 실측

방법 — plan을 눈으로 따라간다

transformed.explain(true)

// 실측 대상
karrot_analysis
  · biz_jobs
  · enterprise_managers
// 기준 컬럼
createAt
analyzed plan
Filter (createAt ≥ …) 가 트리 위에 있다
optimized plan
룰이 Filter를 Scan 쪽으로 내렸다
physical plan
Filter가 남아 있는가, 사라졌는가 — 여기가 판정
3부 · pushdown 실측

결과 — v10은 내려가고, v3는 보장이 없다

v10
== Physical Plan ==
Scan MongoTable
  aggregation.pipeline =
    [{ $match: { createAt:
       { $gte: … }}}]  ← 첫 stage

  +- Filter (createAt ≥ …)
     residual Filter 소멸
boundary 산정까지 커버 — 두 단계 모두 내려간다
v3
== Physical Plan ==
Scan MongoRelation
  pipeline에 $match 없음

  +- Filter (createAt ≥ …)
     Spark 쪽에 그대로 남음

// 전량 받아서 엔진이 거른다
어느 단계도 보장 없음 — 문서도, plan도 침묵
3부 · pushdown 실측

결정 — v3 레인만 필터를 손수 내려보낸다

SqlFilter 트리 createAt ≥ 2026-08-21 번역 { $match: { createAt: { $gte: { $date: … }}}} // aggregation pipeline 맨 앞에 MongoDB $date가 핵심이다 BSON 날짜 필드에 문자열을 비교하면 타입이 달라 아무 문서도 매칭되지 않는다 조용히 0건 — 에러조차 나지 않는 종류의 버그
4부 · Dremel (VLDB 2010 · 2020)

BigQuery의 원형 — 수조 행을 초 단위로

적게 읽는다
nested columnar storage
중첩 레코드를 컬럼으로 쪼개고,
구조는 repetition / definition level
두 정수로만 남긴다 →
필요한 leaf 컬럼만 읽는다
나눠 읽는다
multi-level serving tree
쿼리를 트리로 내려보내고
집계를 계층으로 올려받는다 →
스캔도 집계도 수천 대로 병렬화,
노드 수에 거의 선형으로 빨라진다
4부 · Dremel

구조를 정수 두 개로 접는다 — r / d level

Document
├─ DocId              required
└─ Name               repeated
   ├─ Language        repeated
   │  ├─ Code         required
   │  └─ Country      optional
   └─ Url             optional
r = 어느 반복 필드에서 반복됐나 (0 = 새 레코드)
d = 경로가 어디까지 실재했나 (NULL의 위치를 말해줌)
Name.Language.Country 컬럼 (최대 d = 3)

 값     r   d   해석
 us     0   3   새 레코드, 전부 존재
 NULL   2   2   다음 Language, Country만 없음
 NULL   1   1   다음 Name, Language부터 없음
 gb     1   3   다음 Name, Country = gb
 NULL   0   1   새 레코드, Name만 존재
NULL은 값 스트림에 저장할 필요조차 없다.
조립(record assembly)은 FSM이, 집계 쿼리는 조립을 아예 건너뛴다.
4부 · Dremel

serving tree — 집계까지 계층으로 병렬화

Root 쿼리 재작성, tablet 파악 Intermediate Intermediate Intermediate 부분 집계 Leaf 느림! 다른 서버에 재배치 (straggler redispatch) columnar storage (tablet, 3벌 복제) slot = leaf의 실행 스레드 — 3,000대 × 8스레드 = 24,000 slot
4부 · Dremel → 마무리 연결

필터가 내려가는 세 계단

① 엔진 안 — Catalyst 룰 PushDownPredicates가 Filter를 논리 플랜 아래로 ② 커넥터 — $match 번역 v10은 자동, v3는 우리가 손수 (이번 실측과 구현) ③ 스토리지 안 — Capacitor embedded evaluation 저장 포맷이 미니 쿼리 프로세서를 품고 직접 필터링 + skip-index 쿼리의 절반가량이 데이터의 1% 미만만 반환 — 그래서 내려보낼수록 이긴다
마무리

쿼리 필터가 엔진에서 스토리지까지 내려가는 길을,
physical plan과 논문으로 직접 확인했다

"문서가 침묵하면 plan을 직접 본다."
같은 도구는 누구에게나 있다 — explain(true)