들어가면서
동료가 말했다.
“이 페이지 접근이 안되는데요?”
아, 하고 바로 떠올랐다.
봐둔 코드가 있었다.
봐두고도 까먹었던 코드.
이 글은 그 얘기 이고,
동시에 왜 이런 일이 생기는가 에 대한 얘기 이기도 하다.
구조 먼저 (추상)
어떤 한 영역은 프레임워크 A 와 외부 엔진 B 를 같은 호스트 에서 같이 서빙 한다.
그 사이에 미들웨어 가 서 있다.
기본 동작은 모르는 경로 는 B 로 보낸다 이다.
A 에 새 페이지를 만들면
“나 여기 살아있어요” 를 미들웨어에 알려 줘야 한다.
알려주는 곳이 예외 경로 목록 (상수) 이다.
const RESERVED_PATHNAMES: readonly string[] = [
'showcase',
'post',
'collection',
// ...
'new-page',
];
여기에 내 pathname 이 없으면
미들웨어는 내 페이지를 B 로 먼저 물어보러 간다.
B 에는 당연히 그 문서가 없으니 404 가 돌아온다.
그 다음 이 핵심 이다.
조용히 바뀐 동작
예전에는 B 가 404 를 주면 미들웨어가 그냥 빠졌다.
if (!html) {
return;
}
return; 은 “나는 이 요청 손 안 댈게” 라는 뜻 이다.
그러면 프레임워크 A 가 원래 대로 내 페이지를 렌더 한다.
그래서 예외 경로 목록 에 이름을 안 넣어도
B 를 한 번 쓸데없이 때리고 실패 한 뒤
내 페이지가 결국 잘 나왔다.
비효율이긴 하지만 사용자 입장 에서는 아무 문제 없다.
최근 main 에 이 동작을 바꾸는 변경이 들어왔다.
B 에서 4xx/5xx 가 나오면 우리 가 정의한 표준 에러 페이지로 일원화 하도록.
const rewriteToError = (req, status) => {
const path = status >= 500 ? '/_error' : '/404';
return NextResponse.rewrite(new URL(path, req.url));
};
if (response.isError) {
return rewriteToError(req, response.status);
}
취지는 좋다.
모니터링 도, 사용자가 보는 화면도 정리된다.
그런데 이 변경과 동시에
예외 경로 목록 의 의미 도 조용히 바뀌었다.
이전
- 등록 안 하면 → B 갔다 실패 → A 가 받아줌 → 페이지 나옴
이후
- 등록 안 하면 → B 갔다 실패 → 미들웨어 가 404 로 rewrite → 페이지 안 나옴
같은 상수, 같은 이름, 같은 파일.
동작은 반대가 됐다.
아무도 이걸 “브레이킹 체인지” 라고 부르지 않는다.
정상 경로는 아무 것도 안 바뀌니까.
바뀐 건 조용한 실패의 형태 뿐 이니까.
나는 그 변경을 봤었다
봤다.
보고 “아 내 페이지 올릴 때 여기에도 이름 넣어야 되네” 라고 생각했다.
그리고 까먹었다.
왜 까먹었냐면
내 PR 에는 이 미들웨어 파일이 없으니까.
내 PR 은 페이지를 추가하는 PR 이었다.
미들웨어 파일은 내 변경 범위 바깥 이었다.
PR 올리고 리뷰 받고 머지 할 때까지 한 번도 그 상수를 다시 읽을 일이 없었다.
경험 있으면 기억하고,
경험 없으면 모른다.
그리고 경험 있어도 까먹는다.
세 번째가 나였다.
검증환경 에서 살았다
그래도 이번에 하나 잘한 게 있다.
내 기능 브랜치를 검증환경에 올릴 때
최신 main 을 먼저 반영 하고 그 위에 내 기능을 얹었다.
배포 후에 동료가 말했다.
“이 페이지 접근이 안되는데요?”
아, 하고 바로 떠올랐다.
예외 경로 목록에 한 줄 추가 하고 끝.
최신 main 을 안 반영 했다면
검증환경에 예전 main 이 그대로 있었다면
거기에는 여전히 이전 동작의 미들웨어가 살아 있다.
이전 동작 에서는 이름을 안 넣어도 페이지가 나왔다.
그래서 검증환경에서는 내 페이지가 잘 나온다.
QA 도 통과 한다.
운영에 배포 한다.
운영에는 동작이 바뀐 변경이 이미 있다.
이전 동작은 없다.
운영에서야 페이지가 404 로 덮인다.
동료들이 접근 못 한다고 얘기 한다.
모니터링 에 404 가 쌓인다.
내 PR 을 다시 본다.
내 PR 에는 그 파일이 없다.
“제 변경이 아닌 것 같은데요” 라고 말하고 싶어진다.
틀린 말은 아닌데 맞는 말도 아니다.
내 변경과 최근 main 의 변경이 만나서 생긴 문제 라서.
책임은 공중에 뜨고 시간은 간다.
끔찍 하다.
이 사건 이 말하는 것 하나 — 검증환경 의 역할
검증환경에 최신 main 을 반영 하는 건
내 기능 을 검증 하기 위한 것이 아니다.
내 기능 만 검증 하려면 오히려 다른 변경이 없는 환경이 더 깨끗 하다.
그게 아니라 검증환경 의 역할은
운영 직전의 마지막 합류 지점 을 흉내 내는 것이다.
운영은 내 기능이 혼자 사는 곳이 아니다.
지난 몇 주 팀이 쌓아 올린 main 위에 내 기능이 얹어지는 곳 이다.
그 합류 지점을 검증환경에서 미리 만나면 한 줄 추가 하면 끝나고
운영 에서 처음 만나면 롤백 과 핫픽스 와 재배포 가 된다.
특히 이번 같은 동작의 기본값이 조용히 바뀌는 변경 은
리뷰로 막기도 어렵고 문서로 막기도 어렵다.
“봤고 까먹는다” 라는 사실 자체를 전제 로 깔고
환경이 대신 기억 하게 하는 것 외에 방법이 별로 없다.
이 사건 이 말하는 것 둘 — 작업 을 짧게 가져 가자
여기 까지가 검증환경 얘기 고
사실 더 근본적인 얘기가 하나 남아 있다.
내가 이걸 까먹은 이유,
그리고 내 브랜치와 main 의 거리가 벌어진 이유,
둘 다 같은 원인을 공유 한다.
작업이 길었다.
작업이 길어지면 거리 는 자동으로 벌어진다.
내가 이 기능에 붙어 있는 동안 main 은 계속 움직인다.
오늘 main 에 들어온 변경을 내가 보고
“아 나도 이거 해야지” 라고 생각해도
내 작업이 2주 를 더 가면
그 사이에 main 에는 또 다른 변경이 열 몇 개 들어 오고
처음 봤던 그 변경은 기억 저 편으로 밀린다.
거기다 작업 중에 기능이 계속 바뀌었다.
처음 만들던 화면과 지금 만드는 화면이 다르다.
요구 사항이 중간에 바뀌어서 어제 만든 걸 오늘 지운다.
PR 은 거대해지고 리뷰는 표면만 훑게 된다.
내 머릿속도 지쳐 있어서
“한 줄 짜리 예외 경로 등록” 같은 디테일 은 당연히 우선 순위 뒤로 밀린다.
이런 작업 은 정신 건강에도 나쁘다.
그냥 기분 문제가 아니라
판단력이 떨어진다 는 점에서 나쁘다.
길어진 작업의 끝 무렵 에는
평소 였으면 기억 했을 것들도 못 기억 하고
평소 였으면 의심 했을 가정들도 의심하지 않는다.
이런 건 안 하는 게 좋다.
정말로 안 하는 게 좋다.
그럼 어쩌라는 거냐
정답 을 말하고 싶지는 않다.
태도 세 가지 정도만 말할 수 있을 것 같다.
하나, 검증환경을 내 기능 만의 전용 무대 로 쓰지 말자.
거기는 운영 전의 미리 보기 다.
다른 사람의 변경과 내 변경이 처음 만나는 곳이다.
그 만남을 운영 에서 하지 말고 거기서 하자.
둘, 전역에 영향을 주는 파일 은 특별히 긴장하자.
라우팅, 미들웨어, 공용 상수, 공용 설정 같은 것들.
내가 건드리지 않아도 나한테 영향을 준다.
내가 다 읽어 둘 수도 없고 읽어 둬도 까먹는다.
그래서 이 파일들을 건드리는 최근 PR 이 들어 오면
내 작업에 영향이 있는지 한 번 더 의식적 으로 점검 하는 습관이 필요 하다.
단, 인간의 주의력을 믿지 말고 환경이 잡아 주게 두는 게 더 안전 하다.
셋, 작업을 짧게 끊자.
이게 사실 제일 근본적이다.
작업이 짧으면
- 내 브랜치와 main 의 거리 가 짧다. 그래서 합류 충격 이 작다.
- 내 머리속의 컨텍스트 가 뜨겁다. 그래서 디테일 을 기억 한다.
- 요구 사항 변경 에 휩쓸리기 전에 머지 할 수 있다.
- 정신 건강에 좋다. 그래서 다음 판단 이 덜 흐려진다.
검증환경에 최신 main 을 반영 하는 것도 중요 하지만
애초에 내 브랜치가 main 과 가깝게 유지 되는 것 이 더 중요 하다.
반영 이라는 동사는 거리 가 있을 때 필요한 동사 다.
거리 가 없으면 반영 할 필요 도 없다.
마치며
이번엔 운이 좋았다.
검증환경에 최신 main 을 올려 뒀고
동료가 빨리 알려줬고
기억도 빨리 돌아 왔다.
운이 좋았다 는 말은
다음 번에는 운영 에서 터질 수도 있다는 말이다.
자동화로 잡을 수 있는 건 자동화 로,
못 잡는 건 검증환경 이 대신 기억 하게 하고,
그 전에 애초에 작업 을 길게 끌지 않도록 해서
내가 기억할 수 있는 범위 안에서 끝내는 것,
이게 내가 할 수 있는 전부 인 것 같다.
검증환경에 최신 main 을 반영 하자.
그리고 가능하면 그렇게 까지 반영 할 게 많지 않도록
작업 을 짧게 가져 가자.