데이터 파이프라인 · 계보와 재현 · 실습
계보와 재현 — 두 번 돌려 바이트가 같은가
목표
매일 떨어지는 주문 드롭을 집계하는 변환기 lineage.py 를 만든다. 산출물마다 어떤 입력(내용 해시)과 어떤 코드와 어떤 매개변수로 만들어졌는지를 매니페스트에 남기고, 같은 입력으로 두 번 돌려 바이트가 같은지를 증명하고, 칼럼 하나가 어느 입력 칼럼에서 왔는지를 실험으로 확인한다.
왜 중요한가
보고서 숫자가 회계와 어긋났을 때 답해야 하는 질문은 하나다 — 이 숫자는 어디서 왔나. 파일 이름과 수정 시각으로는 답할 수 없다. 이름은 같은데 상류가 덮어쓴 경우가 흔하고, 수정 시각은 복사만으로도 바뀐다. 근거가 되는 것은 내용 그 자체의 해시다.
그리고 "다시 돌려 보자" 가 통하려면 같은 입력이 같은 출력을 내야 한다. 그것은 저절로 되지 않는다. 산출물에 적어 넣은 현재 시각, 실행마다 새로 뽑은 식별자, 정렬하지 않은 파일 목록과 집합 순회, 부동소수점을 더하는 순서가 조용히 재현을 깨뜨린다. 이 실습은 그 넷을 하나씩 찾아 없앤다.
계보에는 굵기도 있다. 자료 집합 단위는 "이 표는 저 세 파일에서 나왔다" 까지이고, 칼럼 단위는 "이 칼럼은 저 입력 칼럼들에서 나왔다" 까지다. 상류가 칼럼 하나를 바꾼다고 알려 왔을 때 답할 수 있는 굵기는 뒤엣것이다. 그리고 칼럼 계보는 적어 두면 곧 낡으므로 실험으로 다시 잰다.
채점기는 여러분이 적어 낸 문구를 믿지 않는다. 임시 디렉터리에 채점기가 만든 드롭을 차려 놓고 여러분의 변환기를 실제로 실행해 집계 값과 매니페스트를 채점기가 직접 센 값과 대조하고, 두 번 돌려 바이트를 비교하고, 입력 칼럼을 하나씩 흔들어 여러분이 적어 낸 칼럼 계보와 맞는지 확인한다. 상호와 금액은 실행마다 바뀐다.
단계
1. /root/lineage/gen_drops.py 를 만들어 실행해 /root/lineage/drops 아래 날짜별 드롭 세 개를 만드세요.
2. /root/lineage/lineage.py 에 digest 를 만들어 내용 해시와 크기와 레코드 수를 내게 하세요.
3. run 을 더해 집계 산출물 /root/lineage/out/shops.csv 를 내게 하세요.
4. run --manifest 로 매니페스트 /root/lineage/out/shops.manifest.json 을 같이 내게 하세요.
5. 두 번 돌려 바이트가 같아지게 고치고, 그 결과를 /root/lineage/repro.json 에 남기세요.
6. columns 를 더해 칼럼 단위 계보를 내고 /root/lineage/columns.json 에 남기세요.
7. verify 를 더해 매니페스트대로 다시 만들어 대조하게 하세요.
8. trace 를 더하고 /root/lineage/lineage_report.md 를 쓰세요.
참고
- 실행 계약:
python3 /root/lineage/lineage.py <명령> .... 답은 JSON 한 덩어리로 표준출력에 냅니다. 성공하면 종료 코드 0, 파일이 없으면 3,verify·trace가 어긋나면 4, 사용법이 틀리면 2 입니다. - 드롭 파일 이름은
orders-<날짜>.csv이고 머리글은order_id,shop,qty,amount,dropped_at입니다.dropped_at은 집계가 쓰지 않는 칼럼입니다. - 집계 규칙(순서가 답을 바꿉니다): 파일을 이름순으로 읽고, 같은
order_id는 처음 나온 줄만 남기고(중복 제거를 먼저 합니다), 그다음qty가min_qty미만인 줄을 버리고,shop으로 묶습니다.min_qty기본값은 1 입니다. - 산출물 머리글은
shop,orders,qty,amount_cents이고shop오름차순으로 정렬합니다. 줄 끝은 LF 입니다.amount_cents는amount를 정수 센트로 바꿔 더한 값입니다 — 부동소수점을 안 쓰면 더하는 순서가 답을 못 바꿉니다. digest응답:{"path": 절대경로, "sha256": 문자열, "bytes": 정수, "rows": 정수}.rows는 머리글을 뺀 레코드 수입니다.run응답:{"out": 절대경로, "rows": 정수, "sha256": 문자열, "min_qty": 정수}이고 매니페스트를 냈으면manifest와run_id가 더해집니다.- 매니페스트 키:
tool·code_sha256·drops_dir·params·inputs·output·run_id·created_at.inputs는 이름순 목록이고 항목마다name·sha256·bytes·rows입니다.output은name·path·sha256·rows입니다.code_sha256은lineage.py자신의 내용 해시입니다. - 재현 비교에서 빠지는 칸은
created_at하나뿐입니다. 나머지는 두 번 돌려도 같아야 합니다. columns응답과columns.json: 출력 칼럼 이름을 키로, 그 칼럼이 기대는 입력 칼럼 이름의 정렬된 목록을 값으로 둡니다. 판정 기준은 실험입니다 — 그 입력 칼럼을 흔들었을 때 이 출력 칼럼의 값이 바뀌면 의존이 있는 것이고, 출력의 줄(열쇠) 자체가 달라지면 모든 출력 칼럼이 그 입력 칼럼에 기댑니다.verify응답:{"manifest": 절대경로, "ok": 참거짓, "input_missing": [이름], "input_changed": [이름], "code_changed": 참거짓, "output_changed": 참거짓}. 하나라도 어긋나면 종료 코드 4 입니다.trace응답:{"key": 상호, "orders": 정수, "qty": 정수, "amount_cents": 정수, "sources": [{"name": 이름, "sha256": 문자열, "rows": 정수}]}.sources는 그 상호의 줄을 실제로 보탠 파일만 이름순으로 담고,rows는 그 파일이 보탠 줄 수입니다. 없는 상호면 종료 코드 4 입니다.- 재현을 깨뜨리는 것: 산출물 안의 현재 시각, 난수로 뽑은 식별자,
os.listdir와 집합(set)의 순회 순서, 부동소수점을 더하는 순서. 파이썬은 실행마다 문자열 해시 씨앗이 달라서 집합 순회 순서가 실행 사이에 바뀝니다. - 공식 문서: [hashlib](https://docs.python.org/3/library/hashlib.html) · [csv](https://docs.python.org/3/library/csv.html) · [json](https://docs.python.org/3/library/json.html) · [os.listdir](https://docs.python.org/3/library/os.html#os.listdir) · [PYTHONHASHSEED](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED)
- 흔한 실수: 파일 이름과 수정 시각으로 같음을 판정하기, 매니페스트를 나중에 붙이기, 기본값 매개변수를 안 적기, 정렬 없이
os.listdir쓰기. - 두 번 돌린 결과를 눈으로 비교하려면
cmp -s 파일A 파일B또는sha256sum 파일A 파일B를 쓰세요.
단계 8개
- 매일 떨어지는 드롭 만들기
- 이름이 아니라 내용으로 같음을 판정하기
- 집계 산출물 내기
- 산출물과 매니페스트를 같이 내기
- 두 번 돌려 바이트가 같은가
- 칼럼 하나가 어디서 왔는지 실험으로 재기
- 매니페스트대로 다시 만들어 대조하기
- 그 숫자가 어느 입력에서 왔는지 답하기