이관 뒤에 두 채널에서 옵션 데이터를 확인해 달라는 같은 오류가 나고 있었다. 시점이 시점인 만큼 이관이 무엇을 빠뜨렸는지부터 의심했다.
Table of contents
Open Table of contents
큰 테이블에서 로그 추출
오류 로그가 들어 있는 작업 테이블은 2억 행에 인덱스만 125GB짜리라 조인하면 운영에 영향이 간다. 안전한 패턴으로 뽑았다.
SELECT id, product_id, last_log
FROM distribution_product_jobs
WHERE last_log LIKE '%옵션 데이터%'
ORDER BY id DESC
LIMIT 100;
기본키 역방향으로 걸어가다 조건에 맞는 100개를 찾으면 멈추는 형태라 조인도 집계도 하지 않는다. 조사 쿼리 자체가 사고가 되지 않게 하는 것이 첫 단계였다.
두 시각의 대조
나온 상품들의 옵션 그룹을 보니 제목이 전부 비어 있었는데, 결정적이었던 것은 두 시각이었다.
title1 ~ title5 전부 NULL
created_at 2/3 ← 이관 시점
updated_at 2/21 ← 그 뒤
이관은 2월 3일에 됐고 2월 21일에 무언가가 다시 건드렸다. 즉 이관 시점에는 제목이 정상이었고 그 뒤의 갱신이 비운 것이다. 생성 시각과 갱신 시각을 나란히 놓는 것만으로 이관 탓이라는 첫 가설이 무너졌다.
8건을 전부 확인하니 같은 공급사였고 갱신 시각이 모두 2월 21일이었다. 여러 건의 갱신 시각이 한 지점에 뭉쳐 있으면 일괄 조작이다.
변환기가 터지는 조건
제목이 비었다고 왜 오류가 나는지도 확인했다.
$groupSize = count(array_filter([$g->title1, $g->title2, ...]));
$valueSize = count($options[0]->values);
if ($groupSize !== $valueSize) {
throw new Exception('옵션 데이터를 확인해주세요.');
}
그룹 제목 개수와 값 개수를 비교하는데 제목이 전부 비면 그룹 크기가 0이고 값은 1개라 불일치로 걸린다. 오류 메시지는 옵션 데이터를 확인하라고 하지만 실제로 깨진 것은 제목 컬럼이었다.
다른 원인의 배제
같은 시기에 다른 것도 의심받고 있었다. 옵션 관련 별도 레코드가 이관에서 지원되지 않는다는 건이었는데, 확인해 보니 문제의 채널들은 그 레코드를 아예 사용하지 않았다.
배제도 결과다. 이걸 확인하지 않았으면 필요 없는 보정 작업을 한 차례 돌렸을 것이다. 하려던 것을 안 하게 된 것도 조사의 산출물로 남겼다.
세 번째 채널에서도 오류가 나고 있었는데 그것은 옵션과 무관한 정보 고시 문제였다. 같은 시기에 난 오류라고 원인이 같지 않으므로 각각 로그를 보고 갈랐다.
영향 범위와 금지 패턴
각 채널의 이관 대상 규모도 함께 냈다. 세 채널 모두 15만 건대였으므로 이관 자체가 문제였다면 그 규모가 전부 영향 범위가 된다. 실제 문제가 8건뿐이라는 사실 자체가 이관 탓이 아니라는 방증이었다. 원인의 크기와 증상의 크기를 대조하면 가설이 걸러진다.
조사에서 나온 규칙 하나를 남겼다. 그 작업 테이블은 조인을 금지하고 역순 정렬과 제한 패턴으로만 접근한다는 것이다. 한 번 잘못 조인하면 운영에 영향이 가는 규모라 규칙으로 못박아 두면 다음 사람이 시도조차 하지 않는다. 다른 테이블에 필요한 인덱스가 없다는 것도 지금 병목은 아니지만 인지 사항으로 함께 적었다.
정리
- 생성 시각과 갱신 시각을 비교하면 언제 깨졌는지 나온다
- 이관 시점에 정상이었고 그 뒤 갱신이 비운 것이면 이관 탓이 아니다
- 여러 건의 갱신 시각이 같으면 일괄 조작이다
- 큰 테이블은 역순 정렬과 제한으로 뽑고 조인하지 않는다
- 조사 쿼리 자체가 사고가 되지 않게 하는 것이 첫 단계다
- 의심받던 다른 원인은 배제도 확인한다. 불필요한 작업이 제거된다
- 같은 시기의 오류라고 원인이 같지 않다
- 영향 규모를 세면 원인의 크기와 대조된다