+3,000 lines -800 lines 47 files changed // 설계 + 구현 + 마이그레이션 // + 테스트가 한 diff에
// 앞 PR이 수정됐다 (커밋 G) B──C──D──G add-bread \ E──F add-peanut-butter // 후속 커밋을 새 tip 위로 git rebase add-bread B──C──D──G──E'──F' // 해시가 바뀌었으니 git push --force-with-lease
// squash·rebase merge 후: // 후속 커밋만 명시적으로 옮긴다 git rebase --onto \ origin/main \ 새 기반 add-bread \ 제외할 기반 add-peanut-butter 옮길 브랜치
// 처음 쓴 소박한 재귀 def reverse[A](ls: List[A]): List[A] = ls match { case Nil => Nil case head :: tail => reverse(tail) :+ head } // 동작은 한다. 그런데…
reverse(tail)이 끝난 뒤에도 :+ head라는 일이 남아 있다.// 개념적으로는 재귀 ADT enum Chain[+A]: case End // 멈추는 모양 case Link(head: A, tail: Chain[A]) // 다시 자신
def length[A](xs: List[A]): Int =
xs match {
case Nil => 0
case _ :: tail =>
1 + length(tail)
}
+ 1"을 스택이 기억해야 한다.length(List(1, 2, 3)) = 1 + length(List(2, 3)) = 1 + (1 + length(List(3))) = 1 + (1 + (1 + length(Nil))) // 미룬 계산이 스택에 쌓인다 // 입력이 크면 StackOverflowError
@tailrec
def loop(remaining: List[A],
count: Int): Int =
remaining match {
case Nil => count
case _ :: tail =>
loop(tail, count + 1)
}
// 호출 뒤에 남은 일이 없다
loop(List(1, 2, 3), 0) loop(List(2, 3), 1) loop(List(3), 2) loop(Nil, 3) → 3 // remaining = tail // count = count + 1 // 처음으로 이동 — 그냥 루프다
reverse(tail) :+ head는 비꼬리에 끝 붙이기 비용까지 더해 O(n²)이 될 수 있고, 누산기 방식 loop(tail, head :: result)는 앞 붙이기 O(1)로 전체 O(n)이다.df.cache()는 여전히 있다.// MongoDumpParquet.scala .transform(df => if (transferBigQuery) df.cache() else df) // 8/25 Wrapup: // "v10에서는 필요 없지 않나?" // → 존재 이유를 조사하기 시작
| Spark 3.5.6 · 300만 행 · 같은 잡 | no-cache | cache | 배율 |
|---|---|---|---|
| Spark job 수 | 1 | 2 | 적재 잡이 따로 생긴다 |
| wall time | 1,348ms | 2,570ms | 1.9배 |
| 태스크 시간 합 | 13.3초 | 26.3초 | 2.0배 |
| GC 시간 합 | 632ms | 2,083ms | 3.3배 |
| 피크 힙 | 1.3GB | 3.1GB | 2.4배 |
| 산출물 체크섬 | 동일 | 동일 | 기능 동치 |
| business_crm_kr | 제거 전 (cache) | 제거 후 | 변화 |
|---|---|---|---|
| 잡 수 | 23 (컬렉션당 save 2개) | 14 (save 1개) | 구조 변화의 지문 |
| GC 합 | 1,266초 | 140초 | −89% |
| 태스크 시간 합 | 64,076초 | 56,308초 | −12.1% |
| 캐시 읽기 | 172GB | 0 | 소멸 |
| output | 11.6억 행 | 11.6억 행 | 동등 — 기능 동치 |