데이터 파이프라인 · 품질 검사와 격리 · 실습
격리를 운영한다 — 쌓고, 끊고, 다시 넣는다
목표
기대치를 파일로 적어 두고, 드롭을 실행마다 줄 단위로 걸러 sqlite 의 사실 표와 격리 표로 나눠 쌓는 문지기 qgate.py 를 만든다. 너무 많이 틀린 실행은 통째로 끊고, 고친 줄을 다시 넣되 이중 계산이 안 나게 하고, 나이 든 격리를 잡아내고, 실행 상태와 자료 상태를 갈라 보고한다.
왜 중요한가
못 쓰는 줄을 버리지 않고 격리에 남기는 것까지는 대개 한다. 문제는 그다음이다. 격리는 실행마다 다시 쌓이고, 반년 뒤에 열어 보면 4만 줄이 들어 있고, 그동안 집계는 그만큼 빠진 채로 나가고 있었다. 파이프라인은 매일 성공했다 — 성공은 오류 없이 끝났다는 뜻이지 넣어야 할 것을 다 넣었다는 뜻이 아니다.
줄 하나가 틀린 것과 파일 전체가 틀린 것도 다른 사건이다. 상류가 칼럼 순서를 바꿔 보내면 거의 모든 줄이 어긋나는데, 그때 줄 단위로 격리하면 격리 표에 만 줄이 들어가고 집계는 빈 채로 나간다. 그런 파일은 한 줄도 넣지 않고 실행을 끊는 편이 낫다. 가르는 기준은 비율이고, 그 비율은 둥근 수가 아니라 평소 실패 비율에서 정한다.
그리고 고친 줄을 다시 넣는 고리가 필요하다. 재투입은 사람이 손으로 돌리는 일이라 반드시 여러 번 실행되고, 그때 사실 표에 두 번 들어가거나 이미 닫힌 격리를 또 닫아 "3건 해결" 이 두 번 보고되기 쉽다.
채점기는 여러분이 적어 낸 문구를 믿지 않는다. 임시 디렉터리에 채점기가 만든 드롭과 기대치를 차려 놓고 여러분의 문지기를 실제로 실행한 뒤, 만들어진 sqlite 파일을 직접 열어 사실과 격리와 실행 기록을 채점기가 직접 센 값과 대조한다. 임계치와 상호와 금액은 실행마다 바뀐다.
단계
1. /root/quarantine/gen_drops.py 를 만들어 실행해 /root/quarantine/drops 아래 드롭 넷과 /root/quarantine/expectations.json 을 만드세요.
2. /root/quarantine/qgate.py 에 check 를 만들어 기대치로 세어만 보게 하세요.
3. run 을 더해 sqlite 에 사실과 격리와 실행 기록을 쌓게 하세요.
4. run --max-fail-ratio 를 더해 너무 많이 틀린 실행을 통째로 끊게 하세요.
5. requeue 를 더해 고친 줄을 다시 넣되 두 번 넣어도 숫자가 안 변하게 하세요.
6. aging 을 더해 나이 든 격리를 잡아내게 하세요.
7. status 를 더해 실행 상태와 자료 상태를 갈라 보고하게 하세요.
8. 자기 드롭 넷을 차례로 돌려 /root/quarantine/pipeline.db 를 만들고 /root/quarantine/status.json 과 /root/quarantine/quarantine_report.md 를 쓰세요.
참고
- 실행 계약:
python3 /root/quarantine/qgate.py <명령> .... 답은 JSON 한 덩어리로 표준출력에 냅니다. 성공하면 종료 코드 0, 파일이 없으면 3, 사용법이 틀리면 2, 실행이 끊기면 5 입니다. - 드롭과 재투입 파일의 머리글은
order_id,shop,qty,amount,status입니다. 재투입 파일은 아무 데나 두어도 됩니다 — 5단계 정답지는/tmp/fixes.csv에 만듭니다. - 기대치 파일은
{"version": 1, "rules": [...]}이고 규칙마다name·column·type이 있습니다.type은 다섯 가지입니다 —not_null,regex(pattern),range(min·max, 경계 포함),enum(values),unique(그 파일 안에서 처음 나온 값만 통과). - 규칙 적용 규칙: 값은 앞뒤 공백을 떼고 봅니다. 빈 값은
not_null말고는 전부 실패입니다. 정규식은 값 전체와 맞아야 합니다. 유일성은 파일의 앞줄부터 봅니다. check응답:{"drop": 이름, "rows": 정수, "passed": 정수, "failed": 정수, "fail_ratio": 소수, "by_rule": {규칙이름: 정수}}.failed는 한 규칙 이상을 어긴 줄의 수이고by_rule은 규칙마다 어긴 줄의 수라 합이 다를 수 있습니다.by_rule에는 기대치의 모든 규칙 이름이 0 이어도 들어갑니다.fail_ratio는 소수점 여섯째 자리에서 반올림합니다.- sqlite 표는 셋입니다.
runs(run_id, drop_name, status, rows, passed, failed, loaded, quarantined),facts(order_id, shop, qty, amount_cents, run_id),quarantine(q_id, order_id, rule, detail, raw, first_run_id, resolved_run_id).run_id와q_id는 자동 증가이고order_id는 사실 표의 기본 키입니다.amount_cents는 금액을 정수 센트로 바꾼 값입니다. run응답:check의 키에run_id·status·loaded·quarantined·max_fail_ratio가 더해집니다.status는loaded또는broken입니다.- 격리는 같은 줄의 같은 사유가 다시 와도 한 번만 엽니다(열려 있는 같은
order_id·rule이 있으면 새로 넣지 않습니다). 한 줄이 두 규칙을 어기면 격리는 두 줄이 됩니다. --max-fail-ratio기본값은 1.0 이라 끊지 않습니다.fail_ratio가 임계치를 넘으면(같으면 통과) 실행을 끊습니다. 끊긴 실행은runs에broken으로 남기고 사실도 격리도 한 줄도 넣지 않으며 종료 코드 5 입니다.requeue응답:{"run_id": 정수, "status": "requeued", "rows": 정수, "fixed": 정수, "still_failing": 정수, "resolved": 정수, "facts": 정수}. 재투입도 실행이므로runs에requeued로 한 줄 남습니다. 통과한 줄은 사실 표에 갱신으로 넣고, 그order_id의 아직 열려 있는 격리만 닫습니다.facts는 넣은 뒤의 사실 표 건수입니다.aging응답:{"max_age_runs": 정수, "latest_run_id": 정수, "open": 정수, "aged": 정수, "by_rule": {규칙이름: 정수}, "oldest": {"q_id": 정수, "order_id": 문자열, "rule": 문자열, "age": 정수} 또는 null, "alert": 참거짓}. 나이는latest_run_id - first_run_id이고max_age_runs이상이면 나이 든 것입니다.by_rule에는 나이 든 격리가 있는 규칙만 담고,oldest는 열려 있는 격리 중first_run_id가 가장 작은 것(같으면q_id가 작은 것)입니다.status응답:{"runs": {"total": 정수, "loaded": 정수, "broken": 정수, "requeued": 정수, "last_status": 문자열}, "data": {"facts": 정수, "open": 정수, "resolved": 정수, "aged": 정수}, "runs_ok": 참거짓, "data_ok": 참거짓}.runs_ok는 실행이 있고 마지막 실행이broken이 아닌가이고,data_ok는 열린 격리가 0 이고 나이 든 격리가 0 인가입니다.--max-age-runs기본값은 3 입니다.- 8단계의 보고서는
## 이번 실행## 격리에 무엇이 쌓였나## 재투입과 이중 계산## 임계치를 무엇으로 정했나네 절로 쓰고, 사실 건수와 열린 격리 건수를 숫자로 적습니다. - 공식 문서: [sqlite3 모듈](https://docs.python.org/3/library/sqlite3.html) · [UPSERT](https://www.sqlite.org/lang_upsert.html) · [re.fullmatch](https://docs.python.org/3/library/re.html#re.fullmatch) · [csv](https://docs.python.org/3/library/csv.html)
- 데이터베이스를 눈으로 보려면
sqlite3 /root/quarantine/pipeline.db 'select * from runs'를 쓰세요. - 흔한 실수: 격리를 실행마다 다시 넣어 같은 줄이 쌓이기, 재투입을 삽입으로 넣어 사실이 두 배가 되기, 이미 닫힌 격리를 또 닫기, 임계치를 평소 값이 아니라 둥근 수로 정하기.
단계 8개
- 드롭과 기대치를 파일로 두기
- 기대치로 세어만 보기
- 사실과 격리를 나눠 쌓기
- 너무 많이 틀린 실행은 통째로 끊기
- 고친 줄을 다시 넣기
- 나이 든 격리를 잡아내기
- 성공했다와 맞다를 갈라 보고하기
- 자기 드롭으로 한 주를 돌리고 보고하기