dev 환경의 조용한 함정 둘

하루에 네 번 "수정이 안 먹는다"로 코드를 다시 읽었다. 범인은 코드가 아니라 dev와 build가 공유하는 .next 디렉터리, 그리고 로컬 DB를 고쳐도 옛 응답을 주는 두 겹 캐시였다. 사용자 눈에는 한 번도 안 보인 함정

의정활동 트래커를 데스크톱으로 다시 그리던 나흘의 마지막 날 이야기다. dev는 Next.js 개발 서버와 Go 백엔드와 로컬 PostgreSQL로 돌고, 화면 검증은 헤드리스 크롬 실측으로 한다. 그 검증 중에 수정이 화면에 안 나타나는 일이 하루에 네 번 났고, 그때마다 코드를 다시 읽었다.

하려던 것

이 글의 사건은 내가 시킨 게 아니다. 38화의 리디자인 나흘 중 마지막 날, 에이전트가 검증을 하다가 같은 함정을 네 번 밟았다. 나는 “진행해”, “A안 가자”, “1번으로 가”만 했고 함정을 한 번도 직접 보지 못했다.

의정활동 트래커의 dev 구성은 이렇다. Next.js dev 서버, Go 백엔드, 로컬 PostgreSQL. 맥미니의 터널로 dev 미리보기 도메인이 붙어 있어 아이패드에서도 본다. 검증은 헤드리스 Chrome으로 실측한다. 그래서 “화면이 안 바뀐다”가 곧 오측정으로 이어진다.

어떻게 시켰나

시킨 건 리뷰 반영과 테스트였다. 오전 10시 49분에 첫 보고가 왔다. 프론트가 500을 뱉는데 원인은 rm -rf .next 뒤에 살아남은 옛 dev 프로세스였다. 11시에 두 번째. 테스트 중 측정값을 버리고 다시 쟀다. 포트는 LISTEN 중이라 “실행 중”으로 보였다.

오후 2시 57분에 세 번째. “스테일 CSS 캐시에 또 걸렸습니다. 이번 세션 세 번째.” 밤 11시 31분에 네 번째. “globals.css 수정이 반영 안 됩니다. 이번 세션 4번째. 문서화 대기 항목이 점점 확실해집니다.”

어디서 막혔나

함정 1. .next는 dev와 build가 공유한다. 증상이 두 갈래다. dev가 살아 있는 채로 npm run build를 돌리면 build가 dev의 라우트 매니페스트를 지운다. dev 프로세스는 안 죽고 포트도 열려 있는데 모든 요청이 500이다. pkill로 안 죽는 경우가 있어 포트로 찾아 강제 종료하고 .next를 지우고 재기동해야 한다.

두 번째 증상은 build 없이도 난다. dev의 HMR이 globals.css 변경을 통째로 놓친다. 변수뿐 아니라 셀렉터 규칙 자체가 컴파일 결과에 없다. :active 규칙에 조건을 하나 추가했는데 브라우저의 CSS엔 옛 규칙 그대로였다. 소스는 멀쩡해서 “내 코드가 틀렸나”로 코드를 다시 읽게 된다.

함정 2. 로컬 DB를 고쳐도 API는 옛 응답을 준다. 밤 10시 22분에 밟았다. 신구조문 좌우 대조 화면을 보려고 로컬 DB에 시드를 넣었는데 API가 그대로였다. “시드가 안 들어갔나”로 오진했다. 백엔드에 핸들러별 인메모리 캐시가 있었다. 의원 10분, 법안 5분. 캐시는 정책 문서에 적혀 있었지만, 개발 중 화면 검증을 막는다는 사실은 어디에도 없었다. 그날은 아직 한 번도 요청하지 않은 다른 법안에 시드를 넣어 넘어갔다.

어떻게 풀었나

다음 날 환경 문서에 두 절을 썼다. 여기서부터가 이 글의 진짜 내용이다. 코드 리뷰 봇이 7라운드 동안 문서의 단정을 하나씩 무너뜨렸다.

“5분”이라는 단정부터 틀렸다. 핸들러마다 다르다. “다른 레코드에 시드하면 된다”도 반만 맞았다. 캐시 키의 엔티티 축이 정한다. 법안 캐시의 키는 의원이라 같은 의원의 다른 법안에 시드하면 그대로 적중한다. “기다리면 반드시 풀린다”는 화면에선 성립하지 않았다. SSR fetch에 revalidate가 걸려 있어 캐시가 두 겹이다. 실측했다. 콜드 법안에 값 A를 심고 B로 바꾼 뒤 백엔드 TTL이 만료된 시점에 백엔드는 B, 프론트는 A였다. 재요청하니 B. Next의 데이터 캐시는 stale-while-revalidate로 동작한다.

정책 문서의 “BE TTL” 열이 실은 브라우저 max-age였다는 것도 나왔다. 전수 대조하니 17개 라우트 중 규칙이 성립하는 게 7개, 이탈이 10개였다. 규칙이 있는데 이탈한 게 아니라 애초에 규칙이 없었다. 문서가 스스로 지어낸 인과 서사를 지웠다. 커밋 메시지에 “같은 문단에서 네 번째 과잉 단정”이라고 적혀 있다.

곁가지 반전이 하나 더 있다. 함정 1의 판별법으로 문서에 넣은 grep 스니펫이 오히려 오진을 만들었다. dev의 CSS는 미니파이가 안 돼 규칙이 여러 줄인데 grep은 줄 단위라, 규칙이 있어도 0건이 나온다. 테스트 스킬로 스니펫을 실제로 돌려 보다가 잡았다. 고유 조각 하나만 grep하고 다중행은 파이썬으로 본다.

배운 점