LabHub
배우기 러닝패스 코스

データパイプライン

ファイルの置かれ方が読み取り費用を決める — パーティションとコンパクション

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

파일 시스템 위의 파티션 레이크를 다루는 도구 pq.py 를 만든다. 키를 바꿔 가며 파일 수와 크기 분포를 재고, 파티션 키에 걸린 조건이 여는 파일 수를 얼마나 줄이는지 재고, 묶기를 돌리고, 묶는 도중에 읽는 쪽이 무엇을 보는지 확인하고, 키를 바꾸는 비용을 잰다.

왜 중요한가

같은 행을 넣어도 파일이 어떻게 놓여 있느냐에 따라 조회가 여는 파일 수가 수십 배 달라진다. 파티션은 값마다 디렉터리를 나눠 두는 것이고, 열지 않은 파일은 비용이 0 이다. 다만 그 이득은 디렉터리 이름에 들어 있는 칸으로 거를 때만 생긴다. 그렇다고 자주 쓰는 칸을 모두 키로 넣으면 파티션 수가 값의 곱만큼 늘어나고 파일 하나가 몇 줄짜리가 된다. 파일을 여는 고정 비용이 내용을 읽는 비용보다 커지고, 목록의 항목 수가 폭발하고, 압축이 먹지 않는다. 그래서 키를 고르는 일은 언제나 맞바꿈이다. 작은 파일이 쌓이면 묶기를 돌린다. 어려운 것은 묶는 일 자체가 아니라 그 도중에 읽는 사람이 무엇을 보는가다. 디렉터리를 훑어 파일을 모으는 독자는 옛 파일과 새 파일을 모두 집어 같은 줄을 두 번 센다. 읽는 쪽이 목록을 보게 만들고, 목록 교체를 마지막에 한 번만 하면 그 사이가 없어진다. Parquet 은 쓰지 않는다. 실습 이미지에 pyarrow 가 없고 파드는 런타임 설치를 할 수 없다. 행 그룹 같은 개념은 읽기에서 공식 문서로 다뤘고, 여기서는 파티션 디렉터리와 매니페스트와 JSON Lines 로 같은 구조를 손으로 만든다. 채점기는 여러분이 적어 낸 문구를 믿지 않는다. 임시 작업 폴더에 채점기가 만든 원본을 차려 놓고 여러분의 도구를 실제로 돌려, 매니페스트에 적힌 크기가 디스크의 실제 크기와 맞는지, 줄이 보존되는지, 잘라 낸 파일 수가 맞는지를 본다. 원본과 목표 크기는 실행마다 바뀐다.

단계

  1. /root/parts/gen_orders.py 를 만들어 실행해 /root/parts/work/raw.jsonl 을 만드세요.
  2. /root/parts/pq.pywrite 를 만들어 키 하나로 쪼갠 레이크와 매니페스트를 만들게 하세요.
  3. layout 을 더해 파일 수와 크기 분포와 작은 파일 개수를 내게 하세요.
  4. write 가 키를 여럿 받고 한 파일의 줄 수를 제한할 수 있게 해 잘게 쪼갠 레이크를 만드세요.
  5. query 를 더해 파티션 키에 걸린 조건으로 열 파일을 잘라 내게 하세요.
  6. compact 를 더해 한 파티션 안의 작은 파일을 목표 크기에 가깝게 묶게 하세요.
  7. --crash=before-swap--via=glob 을 더해 묶는 도중에 읽는 쪽이 무엇을 보는지 확인하세요.
  8. repartition 을 더해 키를 바꿔 다시 쓰고 /root/parts/work/partition_report.md 에 비용을 적으세요.

참고

파티션 키 후보가 여럿인 원본 만들기

/root/parts/gen_orders.py 를 만들어 실행해 /root/parts/work/raw.jsonl 을 만드세요. 200줄 이상이고, 한 줄은 order_id·day·region·channel·정수 amount 를 담으며, day 는 4가지 이상, region 은 3가지 이상, channel 은 3가지 이상이어야 합니다.

값의 가짓수가 중요합니다. 셋을 다 키로 잡았을 때 파티션이 몇 개로 늘어나는지가 이 실습에서 볼 것이기 때문입니다. 가짓수를 곱해 보고 원본 줄 수로 나누면 파일 하나에 몇 줄이 남을지 미리 보입니다. 씨앗을 고정해야 키를 바꿔 가며 견주는 동안 원본이 흔들리지 않습니다.

키 하나로 쪼개고 목록을 남기기

/root/parts/pq.pywrite <작업폴더> --lake=<이름> --key=<칸> 을 만들어 값마다 칸=값 디렉터리를 만들고 그 아래 part-0000.jsonl 을 쓰고 레이크 안에 _manifest.json 을 남기게 하세요.

매니페스트의 bytes 는 디스크의 실제 파일 크기여야 합니다. 줄 길이를 더해 계산한 값을 적으면 개행이나 인코딩 때문에 어긋나고, 그 어긋남은 나중에 묶기에서 엉뚱한 곳을 끊습니다. 파일을 쓴 뒤 크기를 다시 재세요. 매니페스트도 임시 이름으로 썼다가 바꿔 달면 목록이 반쯤 쓰인 상태를 읽는 일이 없습니다.

파일이 어떤 크기로 놓였는지 재기

layout <작업폴더> --lake=<이름> --small=<바이트> 를 더해 파일 수·파티션 수·줄 수·전체 바이트와 평균·중앙값·최소·최대 크기, 그리고 --small 미만인 파일 수를 내게 하세요.

평균은 거짓말을 합니다. 큰 파일 하나와 작은 파일 수백 개가 섞이면 평균은 멀쩡해 보입니다. 중앙값은 가장 가까운 순위로 잡고 보간하지 마세요. avg_bytes 는 전체 바이트를 파일 수로 나눈 몫(버림)입니다. 이 값들은 전부 매니페스트에서 읽어 계산합니다.

잘게 쪼개 작은 파일 만들어 보기

write--key=day,region,channel 처럼 키를 여럿 받게 하고, --rows=<줄 수> 로 한 파일의 최대 줄 수를 제한할 수 있게 하세요. 그런 다음 잘게 쪼갠 레이크를 만들어 layout 으로 앞의 레이크와 견주세요.

키를 하나 더할 때마다 파티션 수는 그 칸의 값 가짓수만큼 곱해집니다. 디렉터리는 키 차례대로 겹쳐 쌓으세요 — day=2026-01-03/region=seoul/channel=app/ 입니다. --rows 가 없거나 0 이면 파티션마다 파일 하나입니다. 두 레이크의 small_files 를 나란히 놓고 보면 무엇을 잃었는지 숫자로 보입니다.

열지 않은 파일은 비용이 0 이다

query <작업폴더> --lake=<이름> --where=<칸=값[,칸=값]> 을 더해 조건에 맞는 줄 수와 금액을 내되, 파티션 키에 있는 칸으로만 열 파일을 잘라 내게 하세요. 응답에는 files_total 과 실제로 연 files_scanned 가 함께 들어갑니다.

키에 없는 칸은 디렉터리 이름에 들어 있지 않으므로 그 조건으로는 아무것도 자를 수 없습니다. 그때 files_scanned 는 files_total 과 같아야 합니다. 이것을 정직하게 세는 것이 이 단계의 전부입니다 — 여기서 부풀리면 나중에 무엇이 느린지 영영 못 찾습니다. 값은 문자열로 견주세요.

작은 파일 묶기

compact <작업폴더> --lake=<이름> --target=<바이트> 를 더해 한 파티션 안의 파일을 path 차례대로 이어 붙이되 다음 파일을 더하면 목표를 넘길 때 끊게 하세요. 새 파일을 모두 쓴 다음에 매니페스트를 바꿔 달고, 그다음에 옛 파일을 지웁니다.

순서가 중요합니다. 매니페스트를 먼저 바꿔 달면 읽는 쪽이 아직 없는 파일을 엽니다. 새 파일 이름은 옛 이름과 겹치면 안 됩니다 — 겹치면 읽고 있는 파일을 덮어쓰게 되어 그 파티션이 통째로 빕니다. 세대 번호를 이름에 넣으면 겹칠 일이 없습니다. 줄 수는 묶기 전후로 같아야 합니다.

묶는 도중에 읽는 사람이 보는 것

compact--crash=before-swap 을 더해 새 파일을 다 쓴 뒤 매니페스트를 바꿔 달기 직전에 종료 코드 9 로 죽게 하고, query--via=glob 을 더해 매니페스트를 무시하고 디렉터리에서 part-*.jsonl 을 훑는 읽기를 만드세요. 죽인 뒤 두 읽기의 답을 견주세요.

이것이 이 실습의 핵심 장면입니다. 매니페스트로 읽는 쪽은 아무 변화도 보지 못하고, 디렉터리를 훑는 쪽은 같은 줄을 두 번 셉니다. 죽은 뒤에도 옛 파일과 옛 매니페스트는 그대로여야 합니다. 그다음 묶기를 제대로 끝내면 옛 파일이 지워지고 두 읽기가 다시 같아집니다.

키를 바꾸는 값 매기기

repartition <작업폴더> --from=<이름> --to=<이름> --key=<칸[,칸]> 을 더해 원본이 아니라 --from 레이크의 매니페스트를 따라 전부 읽어 새 키로 다시 쓰게 하세요. 그런 다음 /root/parts/work/partition_report.md## 무엇을 어떻게 쪼갰나 ## 작은 파일 문제 ## 묶기 ## 파티션을 바꾸는 비용 네 절로 쓰세요.

파티션 키는 디렉터리 구조 자체이므로 바꾸는 일은 모든 행을 다시 쓰는 일입니다. rows_read 와 rows_written 이 같은지가 첫 확인이고, bytes_read 와 bytes_written 이 그 작업의 실제 값입니다. 보고서에는 그 바이트 수를 숫자로 적으세요 — 다음 회의에서 키를 바꾸자는 말이 나올 때 필요한 유일한 숫자입니다.