Skip to content

정합성 회귀 체크리스트 (템플릿) ​

08화에서 제정했다. T1의 「8. 정합성 회귀 검증」 칸에 이 표를 그대로 붙인다.08화부터는 모든 리포트(T1·T2·T3·T4)에 반드시 붙인다. 이 표가 없는 리포트는 성능이 아무리 좋아도 반려한다.

느린 코드를 빠르게 만드는 것이 튜닝이고, 맞는 코드를 틀리게 만드는 것은 장애다. (02화)


왜 이 문서가 있는가 ​

이런 일이 세 번 있었다. 세 번 모두 Buffers로는 보이지 않았다.

화무엇숨은 이유결과
02날짜 범위를 BETWEEN … AND DATE '2024-06-30'으로 교정T_ORD_BIG의 ORD_DT가 전부 자정이라 결과가 같았다옆 테이블 T_ORD에서 137건 / 34,159,700원 누락
03영업시간 조건 SLE_HMS BETWEEN '090000' AND '180000'0을 채우지 않은 시각이 문자 비교에서 뒤로 밀렸다36,290건 / 297,291,000원 누락
08Q3 상품평가 조인 (M:1:M)평가 수가 모든 상품에서 50개라 순위가 원장과 같았다금액 50배. TOP 5 합계가 회사 전체 매출의 3.6배

공통점은 하나다. "이 데이터에서 같다"를 "맞다"로 읽었다.

그리고 08화에서 하나가 더 드러났다. 옵티마이저의 쿼리 변환은 답을 지킨다. 틀린 답도 지킨다. Q3 rev00은 옵티마이저가 이미 '먼저 집계하고 곱하는' 모양으로 고쳐 쓰고 있었다(PLACE_GROUP_BY). 그래서 839블록으로 빨랐지만, 금액은 여전히 50배였다. "옵티마이저가 알아서 한다"는 정합성의 근거가 되지 않는다.


체크리스트 — 여덟 항목 ​

#항목묻는 것어떻게08화에서 걸린 것
A기준을 정한다무엇과 대조하는가직전 리비전과 원장(업무 정의를 가장 단순하게 쓴 SQL) 둘 다. 직전 리비전이 틀렸을 수도 있다Q3 rev00은 처음부터 틀렸다. 원장 = 평가를 붙이지 않은 상품별 합계
B세 가지로 대조한다건수 · 합계 · 양방향 MINUS셋 다 한다. 하나라도 빠지면 통과로 치지 않는다MINUS만 보면 0 / 0인데 건수는 5,000 대 100인 안이 있었다. MINUS는 중복을 보지 못한다
C잘라 내기 전 전체로TOP N · 페이징 · FETCH FIRST를 뗀 결과로 대조했는가화면에 나오는 5줄이 아니라 그 앞의 전체 집합으로 대조TOP 5는 순위가 같아 보였다. 100개 상품 전체로 대조하니 MINUS 100 / 100
D조인마다 '1 쪽'을 확인한다조인 상대가 그 키로 정말 한 줄인가PK·UK 확인 + A-Rows가 조인 전후로 불어나는가T_ITM_EVL은 ITM_ID로 50줄. 주문상세 200,000 → 조인 10,000,000
E경계 데이터를 만든다해당되는 경계를 복사본에 만들어 대조했는가아래 「경계 목록」. 원본에는 쓰지 않는다평가 없는 상품 · 코드에 없는 키(고아) · 코드 중복 · NULL · 고르지 않은 분포
F계획이 같아도 대조한다Plan hash value가 같으니 답도 같다고 넘기지 않았는가계획 해시에는 연산의 모양만 담기고 식은 담기지 않는다틀린 SUM(DISTINCT) 안과 맞는 EXISTS · IN의 계획 해시가 같았다(2231864090)
G변환에 기대지 않는다"옵티마이저가 고쳐 쓰니까 괜찮다"를 근거로 쓰지 않았는가변환은 뜻을 보존할 뿐 고치지 않는다Q3 rev00은 PLACE_GROUP_BY로 빨랐지만 여전히 50배
H영향 분석카디널리티를 바꾸는 변경(PK·UK 변경, 코드 테이블 재적재, 다건 허용)이 있었는가그 테이블을 조인하는 SQL 전부를 회귀 대상에 올린다2020-02 T_ITM_EVL PK 변경 커밋에 Q3가 빠져 있었다

경계 목록 — E 항목에서 고른다 ​

경계무엇을 만드나 (복사본에서)무엇이 갈리나처음 본 화
시분초자정이 아닌 시각날짜 BETWEEN의 끝값02
0 패딩자릿수가 모자란 문자 숫자문자 범위 비교03
NULL서브쿼리 쪽 컬럼에 NULL 한 줄NOT IN은 0행, NOT EXISTS는 정상(EMP 실측 0명 대 8명)08
1 쪽에 없는 키 (고아)코드 테이블에 없는 코드내부조인은 그 행을 지우고, 외부조인·스칼라는 남긴다(Q2 실측: 내부조인만 주문 10,000건 감소)08
1 쪽 중복코드 테이블을 두 번 적재조인은 조용히 두 배가 되고, 스칼라는 ORA-01427로 멈춘다(Q2 실측: A 등급 2,564,322,100 → 5,128,644,200)08
고르지 않은 분포곱해지는 수가 행마다 다른 부분집합모두 같은 배수일 때는 숨어 있던 순위 뒤집힘(Q3 실측: 12월 평가 기준으로 원장 6위 ITM00041이 1위로)08
요구 조건이 실제로 거르는 경우거름 조건에 걸리는 행(예: 평가 없는 상품)조건을 지운 안과 지키는 안(Q3 실측: MINUS 1 / 0)08
시각 · 세션 (18화 추가)같은 대조를 경계 시각(18시 정각 · 자정 · 금 → 월)과 다른 세션(시간대 · NLS · 직원)에서 한 번 더 한다. 시각을 인자로 넣을 수 있게 고친 판으로 돌린다시계 · 세션 설정에 기대는 함수 · 결과 캐시 · DETERMINISTIC. 같은 날 안에서 하는 대조는 전부 같게 나온다(15b화: 경계 행이 있으면 목록 100,001 vs 100,000 · MINUS 1 / 0 · 03화: NLS DD-MON-RR 27,390건 ↔ YYYY-MM-DD 1,000,000건)03 · 15b

대조 SQL 틀 — 복붙해서 쓴다 ​

sql
-- a = 새 리비전, b = 원장 또는 직전 리비전.  ORDER BY · FETCH FIRST 는 떼고 넣는다 (항목 C)
WITH a AS ( /* 새 리비전 */ ),
     b AS ( /* 원장 */ )
SELECT (SELECT COUNT(*) FROM a)                                     AS a_rows,
       (SELECT COUNT(*) FROM b)                                     AS b_rows,
       (SELECT SUM(/* 금액 컬럼 */) FROM a)                         AS a_sum,
       (SELECT SUM(/* 금액 컬럼 */) FROM b)                         AS b_sum,
       (SELECT COUNT(*) FROM (SELECT * FROM a MINUS SELECT * FROM b)) AS a_minus_b,
       (SELECT COUNT(*) FROM (SELECT * FROM b MINUS SELECT * FROM a)) AS b_minus_a
  FROM dual;

-- 키가 중복되지 않는가 (MINUS 의 맹점을 막는다 - 항목 B)
SELECT /* 키 컬럼 */, COUNT(*) FROM ( /* 새 리비전 */ ) GROUP BY /* 키 컬럼 */ HAVING COUNT(*) > 1;

통과 조건: a_rows = b_rows · a_sum = b_sum · a_minus_b = 0 · b_minus_a = 0 · 중복 키 0행. 다섯 개를 모두 만족해야 한다.


양식 — 리포트에 붙이는 표 ​

#항목대상 / 방법결과판정
A기준
B건수 · 합계 · MINUS 양방향 · 중복 키
C잘라 내기 전 전체
D조인마다 1 쪽
E경계 데이터 (고른 것)
F계획 해시와 무관하게 대조했나
G변환을 근거로 쓰지 않았나
H영향 분석
열적는 것비었을 때
대상 / 방법대조한 SQL 이름 · 쓴 복사본 이름반려
결과숫자 그대로 (건수 · 합계 · MINUS 행 수)반려
판정통과 / 불일치 / 해당 없음「해당 없음」은 이유를 같은 칸에 쓴다. 빈칸과 다르다

작성 예 (08화 · T1 6호 · Q3 rev00 → rev01) ​

#항목대상 / 방법결과판정
A기준원장 = T_ORD_DET 상품별 합계(평가 조인 없음). 직전 리비전 rev00은 틀린 것으로 밝혀짐—정함
B건수 · 합계 · MINUS · 중복 키rev01 vs 원장, 100개 상품 전체100 = 100 · 30,272,557,700 = 30,272,557,700 · 0 / 0 · 중복 0통과
C잘라 내기 전 전체FETCH FIRST 5를 뗀 100행으로(B와 같음)통과
D조인마다 1 쪽T_ITM_EVL은 ITM_ID로 50줄 → 조인을 세미 조인(EXISTS)으로 바꿈해시 조인 입력 200,000 → 출력 200,000 (불어나지 않음)통과
E경계 데이터복사본 T_ITM_EVL_C08에서 ITM00071의 평가를 뺌(평가 없는 상품)rev01은 ITM00071 제외(요구대로). 조인 삭제안은 포함 → MINUS 1 / 0통과(조인 삭제안 기각 근거)
F계획 해시틀린 SUM(DISTINCT) 안과 rev01의 계획 해시가 같음(2231864090)계획 해시를 근거로 쓰지 않음통과
G변환rev00의 PLACE_GROUP_BY · rev01의 UNNEST는 성능 설명에만 씀—통과
H영향 분석T_ITM_EVL을 조인하는 SQL 목록6조각 중 Q3 하나통과

소급 — 08화 이전 리포트에도 붙인다 ​

08화 이전 리포트도 이 표를 붙여 다시 낸다. 우선 B 항목만 채운다(08화 실측).

리포트조각대조결과판정
T1 1호 (02화)Q1 rev00 vs rev02건수 · 합계 · MINUS27,390 = 27,390 · 6,982,065,200 = 6,982,065,200 · 0 / 0통과(경계: 시분초는 T_ORD에서 따로 확인함)
T1 4호 (06화)Q2 rev00 vs rev01건수 · 합계 · MINUS3 = 3 · 255,398,623,700 = 255,398,623,700 · 0 / 0통과(경계: 지역 코드 중복 · 고아는 08화 실습/03 [C])

운용 ​

언제무엇
리비전을 올릴 때마다이 표를 T1 8번 칸에 붙인다. 표가 없으면 리비전을 올리지 않는다
인덱스·힌트만 바꿀 때도SQL이 한 글자도 바뀌지 않았어도 붙인다(계획이 바뀌면 경계에서의 동작이 달라질 수 있다)
카디널리티를 바꾸는 DDL이 있을 때H 항목에 따라 해당 테이블을 조인하는 SQL을 모두 찾아 B 항목을 다시 돌린다
18화 졸업 판정6조각 모두 원장 대조를 다시 돌린다. 불일치가 하나라도 남아 있으면 미완성이다
(18화 결과)6조각 원장 대조 결과 불일치 0(W01 원장 18화 절)

개정 이력: 08화에 제정했다(A~H · 경계 목록 일곱 줄). 18화에 경계 목록의 '시각 · 세션' 줄(15b화 T1 11호 · 03화 T1 2호, 사내 튜닝 표준 제15조)과 졸업 판정 결과를 더했다.