기술

옛 SQL을 버전 관리에서 꺼내 지금 데이터로 돌렸더니, 스냅샷 비교보다 강했습니다

2026.09.128분 읽기

성능 개선 쿼리를 배포하고 나면 질문이 하나 남습니다. 빨라진 것은 응답 시간으로 바로 보이는데, 결과가 예전과 같은지는 따로 확인해야 합니다. 매출 순위처럼 숫자가 그대로 보고서로 넘어가는 화면이면 이 확인이 배포 자체보다 무겁습니다.

어떤 프랜차이즈 ERP의 상품 매출 순위가 그런 경우였습니다. 같은 테이블을 세 번 스캔하던 조회를 한 번으로 통합하는 최적화가 이미 운영에 올라가 있었습니다. 배포 직전에 옛 응답을 8종 떠뒀으니, 그것과 새 응답을 맞춰보면 끝나는 일이라고 생각했습니다.

그 스냅샷 8종이 사라져 있었습니다. 작업 공간을 정리하면서 같이 지워진 것입니다. 비교 대상이 없는 상태에서 사후 검증을 시작했습니다.


스냅샷 비교에는 원래부터 구멍이 있습니다

스냅샷이 없다는 것을 확인하고 나서야, 그 방식이 원래 무엇을 보장하고 무엇을 보장하지 않는지 다시 봤습니다. 배포 전에 뜬 응답과 배포 후에 뜬 응답을 비교하면, 그 사이에 두 가지가 동시에 변합니다. 하나는 제가 바꾼 쿼리 로직이고, 다른 하나는 그동안 쌓인 주문 데이터입니다.

그래서 두 응답의 금액이 다르게 나왔을 때, 그 차이가 로직 때문인지 데이터가 늘어서인지 구분할 방법이 없습니다. 차이가 0으로 나오면 다행이지만 그것도 운에 가깝습니다. 매출 집계는 마감 시점에 따라 과거 기간의 값도 뒤늦게 채워지기 때문에, 시점이 다르면 같은 기간을 물어도 답이 달라집니다.

잃어버린 것이 완벽한 정답지가 아니라 시점이 섞인 참고 자료였다는 정리가 됐습니다. 이 정리가 다음 선택을 바꿨습니다.

154-1.jpg


세 가지 선택지 중 옛 코드를 꺼내는 쪽을 골랐습니다

남은 선택지는 세 개였습니다.

선택지

내용

판단

그냥 종결

테스트가 통과했으니 됐다고 보고 넘어감

결과 동일성이 미검증으로 남음

스냅샷 재생성

배포를 되돌려 옛 응답을 다시 뜸

운영 영향이 크고 시점 차이는 그대로

구 SQL 복원

최적화 직전 커밋의 옛 쿼리를 꺼내 지금 데이터로 실행

같은 데이터라 로직 차이만 남음

세 번째를 골랐습니다. 최적화 커밋의 바로 앞 버전에서 옛 쿼리를 꺼내 지금 데이터베이스에 그대로 돌렸습니다. 그 결과를 운영 API 응답과 직접 맞췄습니다. 신구가 같은 순간의 같은 데이터를 보고 있으니 남는 차이는 로직 차이뿐입니다.

버전 관리 시스템이 검증 도구가 되는 지점입니다. 옛 코드는 어차피 저장소에 남아 있고, 읽기 전용 조회라면 운영에 영향을 주지 않고 다시 돌릴 수 있습니다.


비교 전에 무엇이 같으면 같은지부터 맞췄습니다

돌리기 전에 확인한 것이 하나 있습니다. 신구가 같은 키로 행을 묶는가 하는 점입니다. 이 조회는 브랜드와 상품 코드를 묶어 한 행을 만듭니다. 상품명은 원래부터 키가 아니었습니다.

이 정의를 맞추지 않으면 비교 자체가 성립하지 않습니다. 이름이 다르다는 이유로 같은 행을 다르다고 세거나, 이름이 같다는 이유로 다른 행을 같다고 세게 됩니다. 차이 0이라는 결론은 무엇을 기준으로 셌는지에 통째로 달려 있습니다.


원 단위까지 맞았고, 안 맞는 한 행은 설명했습니다

검증 범위는 7개월, 코호트 2개로 잡았습니다.

전 매장 무필터 조회에서 랭킹 140행을 양방향 차집합으로 대조해 양쪽 다 0

채널 분해(홀·포장·배달) 불일치 0, 총액·매핑액·매장 수 일치

실제 계약 매장 218곳 코호트에서 운영 API 응답과 구 SQL 결과가 원 단위까지 일치

성격이 다른 매장 126곳으로 교차 확인

차집합을 양방향으로 본 이유는 한 방향만 보면 절반을 놓치기 때문입니다. 새 결과에 없는 옛 행과, 옛 결과에 없는 새 행은 서로 다른 사고입니다. 한쪽만 확인하고 0이라고 적으면 나머지 절반은 안 본 것입니다.

차이가 완전히 0은 아니었습니다. API 응답에만 있는 1행이 남았고, 금액도 수량도 0인 행이었습니다. 커밋 메시지를 찾아보니 배달앱 전용 상품의 0원 행을 유지한다는 의도가 적혀 있었고, 그 의도대로 동작한 결과였습니다. 차이가 0이 아닐 때 그 차이를 설명하는 데까지가 검증입니다. 설명되지 않는 1행이 남아 있으면 아직 안 끝난 것입니다.

154-2.jpg


계획서에 적어둔 성능 수치가 실측과 달랐습니다

결과 동일성이 확인됐으니 성능을 잴 차례였습니다. 커밋과 계획 문서에는 45초를 16초로 줄인다고 적혀 있었습니다. 운영 API로 전체 코호트 7개월을 실측하니 22.7초가 나왔습니다.

구분

계획에 적힌 값

실측

개선 전

45초

이번에 측정 안 함

개선 후

16초

22.7초

개선은 분명합니다. 다만 16초는 아니었습니다. 계획 단계의 추정치가 그대로 커밋 메시지에 남았고, 아무도 다시 재지 않은 채 사실처럼 굳어 있었습니다.

이 숫자를 그대로 두면 다음 사람이 그 위에서 판단합니다. 이미 16초까지 내렸으니 여기는 더 손댈 데가 없다는 결론이 나오고, 실제로는 6초가 넘게 남아 있는데 아무도 보지 않게 됩니다. 그래서 22.7초로 갱신하고, 원래 16초라고 적혀 있었다는 사실까지 같이 기록에 남겼습니다.

154-3.jpg


총 시간을 구간으로 쪼개니 다음 할 일이 정해졌습니다

22.7초를 그대로 두면 느리다는 말밖에 안 남습니다. 그래서 구간별로 분해했습니다.

통합 스캔 13.5초 — 3회 측정에서 13.48초, 13.57초, 13.55초로 편차가 거의 없음

매장 수 집계 3.4초 — 전체의 약 15%

나머지 5.8초 — 매핑 데이터 로드, 직렬화, 네트워크

여기서 예상 밖의 것이 나왔습니다. 매장 수 집계 쪽 매퍼 주석에는 전범위 스캔 없이 수 밀리초에 끝난다고 적혀 있었습니다. 실측은 3.4초였습니다.

주석이 틀린 것보다 그 주석이 그동안 한 일이 문제였습니다. 성능을 볼 때마다 그 구간은 이미 검토가 끝난 곳으로 읽혔고, 그래서 아무도 재보지 않았습니다. 코드 주석에 적힌 성능 주장은 문서가 아니라 검증 대상입니다. 이 발견이 그다음 개선 작업의 출발점이 됐습니다.


이 방법이 안 통하는 경우도 있습니다

옛 코드를 꺼내 돌리는 방식이 항상 되는 것은 아닙니다. 이번에 성립한 조건이 세 가지 있었습니다.

스키마가 그대로였습니다. 최적화하면서 컬럼이나 테이블을 함께 바꿨다면 옛 쿼리는 아예 안 돕니다

읽기 전용 조회였습니다. 쓰기가 섞인 로직이면 신구를 나란히 돌릴 수 없습니다

시간 측정은 근사였습니다. 운영 API로 잰 값에는 네트워크와 직렬화가 섞이므로, 순수 쿼리 시간이 필요하면 별도 측정을 붙여야 합니다


성능 개선을 배포한 뒤 점검할 것

결과 동일성을 시점이 다른 스냅샷이 아니라 같은 데이터 위에서 확인했는가

무엇이 같으면 같은 행인지 키를 먼저 정의했는가

차집합을 양방향으로 봤는가

0이 아닌 차이가 남았다면 그 차이를 설명했는가

계획서와 커밋에 적힌 성능 수치를 실측으로 갱신했는가

총 시간을 구간으로 쪼개 다음 병목을 숫자와 함께 특정했는가


검증 근거는 임시 폴더 밖에 둡니다

이번 건에서 가장 아팠던 것은 스냅샷을 잃은 일 자체가 아니라, 검증 근거를 작업 공간의 임시 파일로 두고 있었다는 사실입니다. 세션이 정리되면 같이 사라지는 자리에 근거를 두면, 나중에 확인했다는 말만 남고 무엇으로 확인했는지는 남지 않습니다.

근거로 쓸 산출물은 저장소나 문서로 옮겨두면 됩니다. 다만 이번처럼 잃어버린 뒤에도 방법은 남아 있습니다. 옛 코드는 버전 관리에 그대로 있고, 그것을 지금 데이터로 다시 돌리면 스냅샷보다 강한 비교가 됩니다.

성능 개선의 마무리는 빨라졌다는 응답 시간이 아니라, 결과가 같다는 증거와 실측으로 갱신된 숫자입니다. 둘 다 남기고 나서야 다음 사람이 그 위에서 일할 수 있습니다.

#사후검증#동일성검증#쿼리최적화#성능측정#버전관리