LabHub
배우기 러닝패스 코스

데이터 파이프라인 · 품질 검사와 격리 · 이론

격리한 줄은 어디로 가는가

LabHub 에서 이어서 보기

한 줄 요약

격리는 줄을 버리지 않는 장치이지 그 자체로 해결이 아니다. 고쳐서 다시 넣는 고리와 나이를 보는 눈이 없으면, 격리는 아무도 안 여는 서랍이 된다.

왜 이게 필요했나

품질 검사를 붙였다. 못 쓰는 줄은 버리지 않고 격리 표에 사유와 함께 남긴다. 여기까지는 대개 한다.

반년 뒤 그 표를 열어 보면 4만 줄이 들어 있다. 사유는 여섯 가지인데 그중 하나가 3만 줄이고, 그 3만 줄은 전부 같은 날 상류가 필드를 하나 옮겨 담으면서 생긴 것이다. 그날 이후 아무도 그 표를 보지 않았다. 매출 집계는 그동안 3만 건이 빠진 채로 나가고 있었고, 대시보드는 계속 초록불이었다.

여기서 드러나는 것이 실행 상태와 자료 상태가 다른 축이라는 사실이다. 파이프라인은 매일 성공했다. 성공했다는 것은 "오류 없이 끝났다" 는 뜻이지 "넣어야 할 것을 다 넣었다" 는 뜻이 아니다. 두 가지를 한 칸에 뭉쳐 보고하면 둘 다 못 본다.

이 코스와 fde-data 가 갈리는 곳

fde-data 의 CSV 실습은 고객이 준 파일 한 덩어리에서 못 쓰는 줄을 떼어 내 거절 파일로 남기는 데까지 간다. 그 파일은 한 번 오고, 거절 목록은 고객에게 보내면 끝난다. 이 코스는 매일 도는 파이프라인이라 거기서 끝나지 않는다 — 격리는 실행마다 다시 쌓이고, 어제 격리한 줄이 오늘 또 오고, 누가 고쳐 넣으면 그 줄이 두 번 세어질 수 있다. 한 번 뜯는 일과 계속 도는 일은 설계가 다르다.

기대치를 코드가 아니라 파일로

기대(expectation)는 "이 자료가 맞다면 이래야 한다" 를 적은 것이다. 조건문으로 코드 안에 흩어 두면 무엇을 보고 있는지 아무도 모르게 된다. 한 파일에 목록으로 적어 두면 세 가지가 달라진다 — 상류와 그 파일을 놓고 이야기할 수 있고, 규칙이 늘어도 코드가 안 바뀌고, 어긴 규칙의 이름이 격리 사유가 된다.

{"rules": [  {"name": "order_id_format", "column": "order_id", "type": "regex",   "pattern": "^ORD-[0-9]{6}$"},  {"name": "qty_range", "column": "qty", "type": "range", "min": 1, "max": 50},  {"name": "status_enum", "column": "status", "type": "enum",   "values": ["NEW", "PAID", "CANCELLED"]}]}

사유를 규칙 이름으로 두면 "왜 격리됐나" 가 자유 문장이 아니라 셀 수 있는 값이 된다. 셀 수 있어야 사유별로 몇 건인지 보고, 어느 사유가 늘고 있는지 본다.

임계치를 무엇으로 정하는가

줄 하나가 틀린 것과 파일 전체가 틀린 것은 다른 사건이다. 앞엣것은 줄 단위 격리로 다루고, 뒤엣것은 실행 단위 중단(circuit break)으로 다룬다. 상류가 칼럼 순서를 바꿔 보내면 거의 모든 줄이 어긋나는데, 그때 줄 단위로 격리하면 격리 표에 만 줄이 들어가고 집계는 빈 채로 나간다. 그런 파일은 한 줄도 넣지 않는 편이 낫다.

가르는 기준은 비율이다. 그리고 그 비율은 평소 값에서 정한다. "5%" 같은 둥근 수를 먼저 정하면 둘 중 하나가 된다 — 평소가 7%인 자료에서는 매일 끊기고, 평소가 0.1%인 자료에서는 20배가 망가져도 안 끊긴다. 최근 실행들의 실패 비율을 재어 두고, 그 위로 넉넉히 잡는다. 임계치는 자료마다 다르고, 그래서 자료마다 적어 두어야 한다.

중단할 때는 아무것도 넣지 않는다. 절반만 넣고 멈추면 다음 사람이 어디까지 들어갔는지 판단해야 하고, 그 판단은 언젠가 틀린다.

다시 넣는 고리와 이중 계산

격리한 줄은 고쳐서 다시 넣어야 비로소 끝난다. 여기서 두 가지가 망가지기 쉽다.

첫째, 사실(fact) 표에 두 번 들어간다. 고친 줄을 그냥 INSERT 하면 원래 줄과 고친 줄이 둘 다 남는다. 자연 키에 유일 제약을 걸고 갱신으로 넣으면 이 문제가 사라진다.

둘째, 격리를 두 번 닫는다. 같은 고침을 두 번 실행하면, 이미 닫힌 격리를 또 닫으면서 "이번에 3건을 해결했다" 가 두 번 보고된다. 닫을 때 아직 열려 있는 것만 닫으면 두 번째 실행은 0건이 된다. 재투입은 반드시 여러 번 실행된다 — 사람이 손으로 돌리는 일이기 때문이다.

그리고 고칠 수 없는 격리도 있다. 열쇠 자체가 망가진 줄이 그렇다. 전표 번호가 규격을 안 지켜 격리됐다면, 번호를 고치는 순간 그것은 다른 줄이 된다. 이런 것은 재투입이 아니라 상류에 돌려보내야 한다.

나이 든 격리에 경보를 건다

격리는 쌓이는 것이 정상이다. 문제는 안 줄어드는 것이다. 그래서 "몇 건이 격리됐나" 가 아니라 "가장 오래된 격리가 몇 번째 실행 것인가" 를 본다.

나이는 시각보다 실행 횟수로 재는 편이 다루기 쉽다. 하루 한 번 도는 파이프라인에서 "3회 이상 지난 격리" 는 사흘을 뜻하고, 배포가 멈춰 파이프라인이 안 돌면 나이도 안 먹는다 — 그 편이 사람의 감각과 맞다.

현장에서 만나는 모습

실무에서 진짜 중요한 것

다음 실습에서 할 것

기대치를 파일로 적어 두고 문지기 qgate.py 를 한 단계씩 키운다. 세어만 보는 check 부터 만들어 sqlite 에 사실과 격리를 나눠 쌓고, 너무 많이 틀린 실행은 통째로 끊고, 고친 줄을 다시 넣되 두 번 넣어도 숫자가 안 변하게 하고, 나이 든 격리를 잡아내고, 실행 상태와 자료 상태를 갈라 보고한다. 채점기는 매번 다른 임계치와 다른 상호로 자기 드롭과 기대치를 차려 여러분의 문지기를 실제로 돌리고, 데이터베이스를 직접 열어 사실과 격리가 맞게 들어갔는지 센다.