데이터 파이프라인 · 계보와 재현 · 이론
이 숫자는 어디서 왔나 — 계보와 재현
한 줄 요약
산출물마다 어떤 입력(내용 해시)과 어떤 코드 판과 어떤 매개변수로 만들어졌는지를 옆에 적어 두고, 같은 입력으로 다시 돌려 바이트가 같은지로 그 기록을 증명한다.
왜 이게 필요했나
분기 보고서의 매출이 회계 쪽 숫자와 3천만 원 어긋난다. 파이프라인은 그날 정상 종료했고 로그도 깨끗하다. 질문은 하나다 — 이 숫자는 어디서 왔나.
답하려고 보면 손에 쥔 것이 별로 없다. 산출물 파일 하나, 그리고 "그날 돌았다" 는 로그 한 줄. 어떤 드롭 파일이 들어갔는지, 그 파일이 지금도 그때와 같은 내용인지, 코드가 그 뒤에 바뀌지는 않았는지, 임계값 매개변수가 그때 몇이었는지 — 어느 것도 남아 있지 않다. 그래서 대부분의 조사는 "다시 돌려 보자" 로 간다. 그런데 다시 돌린 결과가 그때와 다르면, 무엇이 달라져서 다른지를 또 모른다.
파일 이름과 수정 시각은 근거가 못 된다. 이름은 같은데 상류가 파일을 덮어쓴 경우가 흔하고, 수정 시각은 복사·이동·백업 복원만으로도 바뀐다. 반대로 내용이 바뀌었는데 시각이 그대로인 경우도 있다. 근거가 되는 것은 내용 그 자체의 해시뿐이다.
이 코스와 fde-data 가 갈리는 곳
fde-data 는 고객이 준 파일 한 덩어리를 뜯어 내는 일이다. 그 파일은 한 번 오고, 이상하면 보내는 쪽에 물어보면 된다. 이 코스는 다르다 — 매일 같은 자리에 같은 이름으로 드롭이 떨어지고, 같은 변환이 반복해서 돈다. 그래서 질문이 "이 파일을 어떻게 읽나" 가 아니라 "석 달 전 그 실행은 어떤 파일을 읽었나" 가 된다. 반복이 있는 자리에서만 계보가 문제가 되고, 반복이 있어서 재현이 가능해진다.
매니페스트에 무엇을 적는가
산출물 옆에 같은 이름의 JSON 을 하나 남긴다. 적을 것은 네 가지다.
- 입력: 파일 이름과 내용 해시와 바이트 수와 레코드 수. 이름만 적으면 소용이 없다.
- 코드: 변환기 소스의 해시(또는 커밋). "버전 1.2" 같은 사람이 쓴 이름은 거짓말을 한다.
- 매개변수: 그 실행에 준 값 전부. 기본값도 적는다 — 기본값이 나중에 바뀐다.
- 출력: 산출물의 내용 해시와 레코드 수.
{ "code_sha256": "9f2c...", "params": {"min_qty": 1}, "inputs": [{"name": "orders-2026-03-01.csv", "sha256": "4a1e...", "rows": 12}], "output": {"name": "shops.csv", "sha256": "7b30...", "rows": 4}, "run_id": "a41c9d02f7e3b118", "created_at": "2026-03-04T02:11:00+00:00"}이 한 장이 있으면 앞의 질문이 전부 대답 가능해진다. 입력 파일을 지금 다시 해시해 보면 그때와 같은지 알 수 있고, 코드 해시를 비교하면 그 뒤에 바뀌었는지 알 수 있고, 다시 돌려 출력 해시를 대조하면 기록이 맞는지 알 수 있다.
재현을 깨뜨리는 것들
"같은 입력이면 같은 출력" 은 저절로 되지 않는다. 조용히 깨뜨리는 것이 네 가지 있다.
첫째, 현재 시각. 산출물 안에 생성 시각을 적어 넣으면 두 번 돌린 결과는 반드시 다르다. 시각이 필요하면 산출물이 아니라 매니페스트에 적고, 재현 비교에서 제외되는 칸이라고 못 박는다.
둘째, 난수로 뽑은 식별자. 실행마다 uuid4 로 뽑은 run_id 를 매니페스트에 넣으면, 같은 입력으로 돌려도 매니페스트가 달라진다. 식별자는 내용에서 파생하면 된다 — 코드 해시와 매개변수와 출력 해시를 이어 붙여 해시하면, 같은 실행은 같은 이름을 갖고 다른 실행은 다른 이름을 갖는다.
셋째, 순서가 약속되지 않은 것들. os.listdir 의 순서는 정해져 있지 않고, 집합(set)의 순회 순서는 같은 프로그램에서도 실행마다 달라진다(파이썬은 문자열 해시에 실행마다 다른 씨앗을 쓴다). 그래서 파일 목록도 그룹 목록도 정렬해서 쓴다.
넷째, 부동소수점을 더하는 순서. (a + b) + c 와 a + (b + c) 는 부동소수점에서 같은 값이 아니다. 줄의 순서가 흔들리면 합계의 마지막 자리가 흔들리고, 그러면 바이트가 달라진다. 금액처럼 소수 자리가 정해진 값은 정수(센트)로 바꿔 더하면 이 문제가 아예 사라진다.
자료 집합 단위와 칼럼 단위
계보에는 굵기가 있다. 자료 집합 단위는 "이 표는 저 세 파일에서 나왔다" 까지다. 만들기 쉽고, 장애가 났을 때 영향 범위를 훑는 데는 이것으로 충분하다.
칼럼 단위는 "이 표의 amount_cents 칼럼은 입력의 amount 와 order_id 와 shop 에서 나왔다" 까지 내려간다. 상류가 칼럼 하나를 바꾼다고 알려 왔을 때, 우리 산출물의 어느 칸이 흔들리는지를 답할 수 있는 굵기가 이것이다.
칼럼 단위 계보는 적어 두는 것으로 끝나면 안 된다. 코드가 바뀌면 그 문서는 곧바로 낡는다. 증명하는 방법은 실험이다 — 입력 칼럼 하나를 흔들어 다시 돌리고, 어느 출력 칼럼이 바뀌는지를 본다. 바뀌면 의존이 있는 것이고, 안 바뀌면 없는 것이다. 이렇게 재면 "쓰는 줄 알았는데 안 쓰는 칼럼" 도 같이 드러난다.
현장에서 만나는 모습
- 같은 이름, 다른 내용. 상류가 어제 파일을 고쳐 다시 올렸는데 이름이 같아서 아무도 몰랐다. 내용 해시를 남겨 두었다면 다음 실행에서 바로 걸린다.
- "코드는 안 바꿨는데요". 라이브러리 판이 올라갔거나 기본 매개변수가 바뀐 경우다. 코드 해시와 매개변수를 함께 적어 두면 이 대화가 30초에 끝난다.
- 재현이 안 되는 재현 스크립트. 조사하려고 다시 돌렸더니 세 번 다 다른 답이 나온다. 대개 위의 넷 중 하나다.
- 계보 문서와 코드의 불일치. 위키에 적힌 계보는 반년 전 것이다. 실험으로 다시 재지 않으면 이것을 알아챌 방법이 없다.
실무에서 진짜 중요한 것
- 산출물과 매니페스트를 같이 만든다. 나중에 붙이면 반드시 빠진다.
- 이름과 시각이 아니라 내용 해시로 같음을 판정한다.
- 재현은 주장이 아니라 두 번 돌려 바이트를 대조한 결과로 말한다.
- 재현을 깨뜨리는 칸(생성 시각)은 하나로 몰고, 그 칸이 비교에서 빠진다고 적어 둔다.
- 칼럼 계보는 문서가 아니라 실험으로 다시 잰다.
다음 실습에서 할 것
매일 떨어지는 주문 드롭을 집계하는 변환기 lineage.py 를 한 단계씩 키운다. 내용 해시를 내는 digest 부터 만들어 이름과 시각이 왜 근거가 못 되는지 확인하고, 집계 산출물과 매니페스트를 같이 내고, 두 번 돌려 바이트가 같은지를 증명하고, 칼럼 단위 계보를 실험으로 확인하고, 매니페스트대로 다시 만들어 대조하는 verify 와 "그 숫자가 어느 입력에서 왔는지" 를 답하는 trace 를 붙인다. 채점기는 매번 다른 상호와 금액으로 자기 드롭을 차려 여러분의 변환기를 실제로 돌리고, 입력 칼럼을 직접 흔들어 여러분이 적어 낸 칼럼 계보가 맞는지 확인한다.