Infra

[디버깅] user thread 완전복제 재현 원본 오염 방지

jykim23 2026. 7. 9. 11:11
반응형

user thread 완전복제 PK FK 재생성 원본 오염 방지 디버깅

부제: 운영 버그를 원본을 건드리지 않고 재현하기

특정 유저, 특정 대화에서만 나는 버그가 있다.
재현하려고 그 유저에게 채팅을 한 번 쏘면, 그 순간 메모리·요약·체크포인트가 바뀐다.
상태가 오염돼서 두 번째 디버깅은 이미 다른 조건이 된다.

원본에 쏘면 오염된다

손으로 재현 데이터를 만들 수도 없었다.
수백 개의 대화 스레드에 분석 기록까지 누적된 상태를,
픽스처 코드로 흉내 내는 건 사실상 불가능하다.

그래서 방향은 하나였다.
원본과 똑같은 상태의 사본을 만들고, 실험은 사본에서 한다.

복제의 정의부터 못 박았다.
모든 기본 키(PK)와 외래 키(FK)만 새로 생성하고,
나머지 컬럼은 JSONB든 임베딩이든 파일이든 타임스탬프든 바이트 그대로 복사한다.
즉 ID만 빼면 원본과 구별이 안 되는 사본이다.

복제 범위는 손으로 세지 않는다

어떤 테이블을 복제해야 하는지가 문제였다.
유저에 딸린 테이블을 손으로 나열하면, 나중에 테이블이 하나 늘 때마다 빠뜨린다.

그래서 데이터베이스의 제약 카탈로그(pg_constraint)를 진실의 원천으로 삼았다.
어떤 테이블이 유저를 외래 키로 물고 있는지는 DB가 이미 알고 있다.
복제 SQL 자체는 명시적으로 두되, 데이터 모델이 자라면 테스트가 어긋남을 잡게 했다.
새로 유저에 딸린 테이블이 생겼는데 복제 목록에 없으면 CI가 실패하고,
테이블에 컬럼이 추가됐는데 복사에서 빠지면 그것도 잡힌다.

가장 아팠던 건 격리 수준이었다

복제는 여러 쿼리가 한 스냅샷을 봐야 한다.
중간에 원본이 바뀌면 사본이 뒤틀리니, 트랜잭션 격리 수준을 REPEATABLE READ로 올렸다.

그런데 이걸 요청을 처리하던 세션에 걸었더니 이상한 일이 났다.
테스트에선 멀쩡한데 운영에선 5분에 한 번씩 복제가 어긋났다.

원인은 인증이었다.
API 키를 검증하는 로직이 5분마다 한 번씩 키 테이블을 UPDATE하는데,
그게 트랜잭션의 첫 DB 작업을 먼저 차지해 버렸다.
격리 수준은 트랜잭션의 첫 쿼리보다 먼저 정해야 적용되는데,
그 UPDATE가 비결정적으로 끼어들면서 격리가 조용히 안 걸린 것이다.
테스트는 통과하고 운영만 주기적으로 깨지는, 잡기 고약한 버그였다.

해결은 세션을 분리하는 거였다.
요청 세션을 쓰지 않고 복제 전용 세션을 새로 열어, 거기서 격리 수준을 걸었다.

오래 걸리는 작업의 안전장치

복제는 파일 복사까지 끼면 30초쯤 걸린다.
그 사이 어딘가에서 멈추면(특히 파일 저장소 복사가 유휴 상태로 걸리면),
커넥션과 DB의 정리 기준점을 오래 붙들어 다른 서비스에 영향을 줄 수 있다.

그래서 이 세션에만 문장 타임아웃·유휴 트랜잭션 타임아웃·잠금 타임아웃을 걸었다.
정해진 시간을 넘기면 스스로 끊긴다.
파일은 먼저 복사하고 DB를 마지막에 커밋하되,
DB가 실패하면 복사해 둔 파일을 되돌려 청소하게 했다.

정리

  • 버그 재현은 원본을 건드리지 않고, ID만 새로 만든 완전 사본에서 한다
  • 복제 범위를 손으로 나열하지 않고 DB 제약 카탈로그를 진실로 삼아, 어긋남은 테스트가 잡게 했다
  • 격리 수준은 "무엇을 거느냐"만큼 "어디에 거느냐"가 중요했다 — 요청 세션이 인증 UPDATE로 오염돼, 전용 세션으로 분리
  • 오래 걸리는 작업엔 타임아웃 안전장치를 걸어, 멈춰도 다른 서비스를 붙들지 않게 했다

재현이 안 되는 버그의 절반은 "깨끗한 재현 환경이 없어서"다.
원본을 오염 없이 통째로 복제하는 도구 하나가 그 절반을 없앤다.

반응형