일괄 중지 작업에서 544건이 실패했고 로그를 몇 개 열어 보니 식별자 충돌로 보였다. 그런데 표본 몇 개가 그렇다는 것과 전체가 그렇다는 것은 다른 얘기였다.
Table of contents
Open Table of contents
544건의 실패와 첫 표본
표본에서 본 것을 전체로 옮기려면 나머지도 같은 원인인지를 확인해야 했다. 그 확인 없이 처리 방안을 정하면 소수의 다른 원인이 그 방안에 섞여 들어간다.
그래서 실패 목록 전체를 대상 표와 JOIN 해 COUNT(*) 를 내기로 했다. 몇 건인지가 아니라 전체 중 몇 퍼센트인지가 필요한 숫자였다.
세 단계의 조인
첫 단계는 실패 건 가운데 다른 채널 행과 channel_product_id 가 겹치는 것이 몇 건인지였다.
SELECT COUNT(DISTINCT dpj.id)
FROM distribution_product_jobs dpj
JOIN distribution_products dp ON dp.id = dpj.dp_id
JOIN product_options po
ON po.channel_product_id = dp.channel_product_id
AND po.channel <> dp.channel
WHERE dpj.id IN (<실패 544건>);
543이 나왔다. 544건 중 99.8%라 사실상 단일 원인이라고 볼 수 있는 비율이었다.
두 번째 단계는 그 충돌을 po.channel 로 GROUP BY 한 것이고 543건이 전부 한 채널이었다. 분포가 몰려 있으면 원인이 하나다. 세 번째 단계는 그 채널에서 실제로 자리를 차지하고 있는 행을 세는 것이었다.
SELECT COUNT(*) FROM product_options
WHERE channel = '<그 채널>'
AND (channel_item_id IS NULL OR channel_item_id = '')
AND channel_product_id IS NOT NULL;
channel_item_id 가 비었는데 channel_product_id 는 잡고 있는 행들이었다.
각 단계가 다음 단계의 조건을 만든다
첫 단계에서 비율이 낮게 나왔다면 두 번째 단계를 볼 이유가 없었다. 여러 원인이 섞여 있다는 뜻이므로 원인별로 나누는 것이 먼저였을 것이다.
두 번째에서 여러 채널에 흩어져 있었다면 채널 공통 문제가 아니라 개별 사정으로 갈렸을 것이다. 각 단계의 결과가 다음 단계를 볼지 말지와 무엇을 볼지를 정해 주는 구조였다.
대응 — 처리 방안과 안전 근거
세 번째 단계에서 나온 방해 행을 DELETE 하면 나머지가 통과한다는 것이 방안이었다. 다만 운영 자료를 지우는 것이므로 왜 안전한지를 함께 밝혀야 했다.
그 행들이 이미 종료 상태이고 어느 활성 건에서도 참조되지 않는다는 것을 NOT EXISTS 로 적었다. 안전하다는 말이 아니라 어떤 조건에서 안전한지를 SELECT 로 보이는 것이 근거가 된다.
나머지 한 건의 분리
543건이 확인됐으므로 남은 한 건은 다른 원인이었고 그것을 별도 항목으로 뺐다. 비율이 높다고 해서 전부라고 쓰면 그 한 건이 조용히 남는다.
그 한 건은 따로 로그를 열어서 원인을 확인하고 별도로 처리했다. 큰 덩어리를 처리하는 문서에 작은 예외를 함께 적어 두는 것이 그 예외를 살리는 방법이었다.
비율이 처리 방식을 정한다
99.8%가 한 원인이라는 것이 일괄 처리를 정당화하는 근거가 됐다. 절반쯤이었다면 원인별로 나눠서 각각 다른 방식으로 처리했을 것이다.
즉 비율은 원인을 확인하는 숫자이기도 하지만 처리 방식을 고르는 숫자이기도 했다. 세어 보기 전에 방식을 정하면 그 숫자가 쓰일 자리가 없어진다.
정리
- 표본이 그렇다는 것과 전체가 그렇다는 것은 다르다
- 실패 원인을 단계적 조인으로 확정한다
- 1단계는 몇 건이 그 원인인가를 비율로 본다
- 2단계는 원인이 어디에 몰려 있는지 본다
- 3단계는 실제 방해 행이 몇 건인지
COUNT(*)한다 - 각 단계의 결과가 다음 단계의 조건을 만든다
- 운영 자료 변경은 안전한 조건을
NOT EXISTS로 보인다 - 전부라고 쓰지 않고 나머지를 별도 항목으로 뺀다