버그 재현은 종종 여행보다 어렵다.
여행은 지도를 열고 “여기서 밥 먹고, 저기서 사진 찍고, 마지막에 노을 보자”라고 정하면 된다. 그런데 버그 재현은 이상하다. 분명 어제는 봤던 풍경인데 오늘은 길이 사라진다. 고객 환경에는 있었던 데이터가 로컬에는 없다. 권한은 있는 것 같은데 UI에서는 안 보인다. API는 성공했다고 하는데 실제 쿼리는 실패한다.
그래서 이번에는 재현 스크립트를 그냥 “스크립트 모음”으로 두지 않고, 작은 여행사처럼 정리해보기로 했다.
이름은 QueryPie ReproKit.
한 줄로 말하면, 버그 재현을 “감으로 찾아가는 골목길”에서 “코스가 있는 여행 상품”으로 바꾸는 도구다.
재현에도 코스가 필요하다
예전 재현 스크립트들은 각자 성격이 강했다.
어떤 스크립트는 API를 먼저 불렀고, 어떤 스크립트는 DB를 바로 만졌고, 어떤 스크립트는 Docker 컨테이너를 띄웠고, 어떤 스크립트는 Slack 메시지를 보냈다. 하나하나는 유용했지만, 새로운 사람이 보면 이런 느낌이었다.
“이 여행은 어디서 출발해서 어디서 끝나는 거지?”
그래서 ReproKit은 모든 재현을 여섯 단계로 맞췄다.
doctor 준비 상태 확인
setup 테스트 데이터와 환경 만들기
reproduce 실제 문제 조건 실행
verify 기대 결과 확인
report 결과 요약
cleanup 만든 것 정리
이 단순한 여섯 단어가 꽤 큰 차이를 만든다.
이제 어떤 재현이든 먼저 이렇게 물어볼 수 있다.
“doctor는 통과했나?”
“setup이 진짜 Admin에서 보이는 데이터를 만들었나?”
“verify는 API 응답만 본 건가, 실제 쿼리까지 본 건가?”
“cleanup은 내가 만든 것만 지우나?”
재현이 대화 가능해진다.
지도는 repro.yaml, 짐 목록은 fixture.yaml
ReproKit에서 여행 지도 역할은 repro.yaml이 맡는다.
이 파일에는 시나리오 이름, 관련 명령, 어떤 단계에서 어떤 스크립트를 실행할지가 들어간다.
반면 짐 목록은 fixture.yaml이다.
사용자, 그룹, 서버 이름, 대상 DB, 테스트 쿼리, 원본 값 같은 “데이터”는 여기에 둔다. 코드 안에 박아두지 않는다.
이 구분이 중요하다.
코드는 여행 가이드다. 데이터는 여행객이 가져온 캐리어다. 둘을 섞으면 나중에 캐리어 하나 찾으려고 가이드북 전체를 뒤지게 된다.
기능별 가이드도 나눴다
단계만 맞춘다고 충분하지는 않았다.
재현 스크립트가 자주 하는 일은 반복된다.
사용자를 만들고, 그룹에 넣고, 서버를 등록하고, DB를 띄우고, 권한을 주고, 프록시를 통해 쿼리를 실행하고, 감사 로그를 확인한다.
그래서 기능별 모듈도 나눴다.
qpie_repro.iam 사용자와 그룹
qpie_repro.dac.* DB 접근 제어 쪽 기능
qpie_repro.sac.* 서버 접근 제어 쪽 기능
qpie_repro.target_db.* MySQL, MongoDB 같은 대상 DB
qpie_repro.target_server.* Ubuntu SSH 같은 대상 서버
qpie_repro.workflow 워크플로우
qpie_repro.slack_dm Slack DM
특히 DAC와 SAC는 가능한 한 비슷한 구조로 맞췄다.
DAC에는 DB Connection이 있고, SAC에는 Server가 있다.
DAC에는 Access Control이 있고, SAC에도 Access Control이 있다.
DAC에는 Proxy를 통해 실제 쿼리를 실행하는 길이 있고, SAC에는 Shell을 열어 명령을 실행하는 길이 있다.
제품 안의 개념은 다르지만, 재현 자동화 입장에서는 이렇게 볼 수 있다.
“대상 리소스를 만들고, 계정을 붙이고, 권한을 주고, 실제 실행을 해보고, 로그를 확인한다.”
이 공통 문장을 코드 구조에도 반영했다.
API 우선, DB는 마지막 비상문
ReproKit의 중요한 원칙이 하나 있다.
가능하면 External API를 먼저 쓴다.
DB를 직접 수정하면 빠르다. 하지만 빠른 만큼 위험하다. 제품이 실제로 제공하는 경로를 건너뛰기 때문이다. UI에서는 안 보이는 데이터, 캐시에 안 올라간 데이터, 이벤트가 안 나간 데이터가 생길 수 있다.
그래서 순서는 이렇게 정했다.
External API
→ MCP/gRPC
→ UI checklist 또는 Playwright
→ DB read-only
→ DB write fallback
DB write fallback은 비상문이다. 평소에는 잠겨 있어야 한다. 열 때도 “이건 local-only fallback입니다”라고 크게 적어둬야 한다.
함수 이름도 일부러 길게 쓴다.
set_prompt_separators_local_db_fallback(...)
이름만 봐도 알 수 있다.
“아, 이건 제품 공식 API가 아니라 로컬 DB fallback이구나.”
좋은 자동화는 똑똑한 척하는 자동화가 아니라, 자신이 뭘 하고 있는지 숨기지 않는 자동화다.
SAC도 API를 타기 시작했다
재미있었던 지점은 SAC였다.
처음에는 SAC 쪽은 MCP나 DB 직접 수정에 의존해야 한다고 생각하기 쉬웠다. 그런데 OpenAPI spec을 확인해보니 SAC External API v2에 꽤 많은 길이 열려 있었다.
예를 들면 이런 것들이다.
/api/external/v2/sac/servers
/api/external/v2/sac/server-groups
/api/external/v2/sac/server-groups/{serverGroupUuid}/accounts
/api/external/v2/sac/access-controls/{userUuid}/permissions
그래서 SAC 정적 설정은 External API v2를 우선 사용하도록 잡았다.
서버 만들기, 서버 그룹 만들기, 계정 추가, 권한 부여는 API로 간다.
대신 실제 Web Terminal을 열고 명령을 실행하고 Command Audit을 보는 런타임 작업은 MCP가 맡는다.
정적 설정은 API.
실제 세션은 MCP.
Prompt Separator처럼 아직 API 표면이 부족한 것은 명시적 fallback.
경계가 생기니 코드가 훨씬 읽기 쉬워졌다.
재현 데이터는 진짜 보여야 한다
이번 작업에서 가장 뼈아픈 교훈은 이것이었다.
“테스트 데이터를 넣었다”고 말하는 것과 “Admin에서 보인다”는 것은 다르다.
API로 만들었다고 끝이 아니다. 사용자가 QueryPie Admin에서 확인했을 때 보이지 않으면, 그건 재현 데이터로서 반쪽짜리다.
그래서 ReproKit은 fixture를 만들 때 이런 질문을 계속 던지게 한다.
- 사용자가 보이는가?
- 그룹이 보이는가?
- 정책이 보이는가?
- 대상 DB에 row/document가 있는가?
- 권한이 실제 실행 경로에 적용되는가?
- 프록시를 통해 진짜 쿼리를 날려봤는가?
- 감사 로그나 결과 report가 남았는가?
버그 재현은 “내 스크립트가 성공했다”가 아니라 “제품 경로에서 같은 현상을 다시 볼 수 있다”가 목표다.
작은 smoke test가 주는 안심
ReproKit에는 작은 smoke 예제도 넣었다.
python3 dev-scripts/reprokit-examples/smoke-functional-modules.py
이 예제는 실제 OpenAPI spec을 읽어서 SAC v2와 IAM v3 endpoint가 있는지 확인한다. 그리고 사용자/그룹/SAC 서버/계정/권한 생성 호출 모양을 recording 방식으로 검증한다.
실제 mutation은 하지 않는다.
이게 마음에 든다.
큰 테스트를 돌리기 전에 “지도는 살아 있나?”, “길 이름은 맞나?”, “비밀번호는 로그에 안 새나?”를 빠르게 보는 작은 건강검진이기 때문이다.
좋은 자동화에는 이런 작은 안심 장치가 필요하다.
결국 ReproKit은 버그용 여행 가이드다
ReproKit을 만들고 나니 재현 스크립트가 조금 덜 무서워졌다.
전에는 이런 느낌이었다.
“이 스크립트 실행하면 뭐가 생기지?”
“이 DB update는 괜찮은 건가?”
“왜 Admin에는 안 보이지?”
“어디까지가 준비고 어디부터가 재현이지?”
이제는 이렇게 묻는다.
“doctor는 통과했나?”
“setup fixture가 제품 경로에서 보이나?”
“reproduce가 실제 실행 경로를 탔나?”
“verify가 결과를 증명했나?”
“cleanup은 안전한가?”
질문이 좋아지면 디버깅도 좋아진다.
그리고 재현 자동화의 진짜 가치는 코드를 줄이는 데만 있지 않다. 팀이 같은 언어로 이야기하게 만드는 데 있다.
“그거 ReproKit setup까지 된 상태야?”
“verify는 proxy query까지 본 거야?”
“SAC는 API로 만든 거야, DB fallback이야?”
이런 대화가 가능해지면, 버그는 더 이상 미로가 아니다.
그냥 조금 복잡한 여행지가 된다.
지도도 있고, 체크리스트도 있고, 돌아오는 길도 있다.
그 정도면 꽤 괜찮은 여행이다.