기술

화면을 먼저 배포했더니 오류 대신 '할 일이 없습니다'가 떴습니다

2026.09.298분 읽기

프랜차이즈 ERP 에 담당자별 할 일 화면이 있습니다. 여기에 축을 하나 더 붙였습니다. 연속 2회 이상 로열티가 밀린 매장을 담당자별로 모아 보여주는 구역입니다. 담당자가 아침에 이 화면을 열고 오늘 무엇을 챙길지 정하는 자리라, 이 구역이 비어 있으면 '챙길 게 없다'는 뜻으로 읽힙니다.

이 제품은 백엔드와 관리자 웹이 서로 다른 저장소입니다. 한 기능이 두 저장소에 걸쳐 있으면 배포도 두 번 나갑니다. 그날 화면 쪽이 먼저 원격에 올라갔고 백엔드는 승인을 받아 조금 뒤에 나갔습니다. 그 사이 구간이 문제였습니다.


오류가 아니라 멀쩡한 화면으로 보였습니다

새 축은 기존 할 일 축들과 응답을 합치지 않고 자기 API 를 따로 부르도록 설계했습니다. 구역이 독립적이라 하나가 느려도 화면 전체가 멈추지 않는다는 장점이 있습니다. 대신 화면만 먼저 올라간 구간에서는 그 구역이 정상적으로 그려진 채 부를 API 만 없는 상태가 됩니다.

화면에는 '할 일이 없습니다'만 떴습니다. 빨간 경고도 토스트도 없고, 콘솔에도 이 축이 실패했다는 말이 없습니다. 담당자 입장에서 이 화면은 밀린 로열티가 한 건도 없다는 뜻이고, 그건 그대로 업무 판단 재료가 됩니다. 실패한 화면이 아니라 틀린 사실을 말하는 화면이었습니다.


왜 아무도 눈치채지 못했나

두 가지가 겹쳤습니다. 하나는 데이터 훅의 기본값입니다. 쿼리 결과가 없으면 `data ?? []`(값이 없을 때 빈 배열로 대신하는 표현)로 빈 배열이 되고, 화면은 그것을 그대로 '0건'으로 그립니다. 이 한 줄은 화면이 깨지지 않게 해 주는 편의 장치이면서 동시에 실패를 삼키는 은폐 장치입니다.

다른 하나는 서버의 응답입니다. 이 백엔드는 존재하지 않는 경로에도 500 을 돌려줍니다. 404 였다면 프론트는 '아직 배포되지 않은 API'와 '장애가 난 API'를 갈라 볼 수 있습니다. 500 하나로 뭉뚱그려지면 프론트에 원인을 구분할 재료가 없습니다. 브라우저 네트워크 탭을 열어야만 그 구역이 실패하고 있다는 걸 알 수 있었습니다.

정리하면 이렇습니다. 목록 화면의 '0건'은 뜻이 두 개입니다. 진짜 없는 것과 못 가져온 것입니다. 둘을 같은 화면으로 그리는 순간 후자는 신고되지 않습니다. 사용자는 아무 이상을 못 느끼므로 문의조차 오지 않습니다.

목록의 0건이 진짜 없음과 못 가져옴 두 가지 뜻으로 갈리는 구조를 그린 화이트보드 도식


배포 순서는 대칭이 아닙니다

여기서 규칙 하나를 남겼습니다. 한 기능이 두 저장소에 걸치면 백엔드를 먼저 올립니다. 이유는 두 순서가 서로 대칭이 아니기 때문입니다.

먼저 올린 쪽

그 사이 구간에 일어나는 일

백엔드

새 API 가 열려 있지만 아무도 부르지 않습니다. 아무 일도 일어나지 않습니다

화면

구역이 이미 그려지고 사용자에게 틀린 사실을 말합니다

위 표에서 백엔드를 먼저 올린 경우의 비용은 0 입니다. 화면을 먼저 올린 경우의 비용은 잘못된 업무 판단입니다. 값이 같지 않은데 순서를 그때그때 정하면, 절반의 확률로 비싼 쪽을 고르게 됩니다.

일반화하면 소비자보다 제공자를 먼저 올립니다. 앱과 API, 배치와 스키마, 리포트와 집계 테이블이 전부 같은 축입니다. 제공자가 먼저 서 있으면 소비자가 도착할 때 이미 답할 준비가 돼 있습니다. 반대 순서는 소비자가 빈손으로 사용자 앞에 서는 구간을 만듭니다.

백엔드 먼저 배포한 경우와 화면 먼저 배포한 경우를 좌우로 비교한 화이트보드 도식


같은 숫자를 두 화면이 보여준다면 정본을 공유합니다

이 축을 만들면서 미납 회차와 금액을 새로 계산하지 않았습니다. 기존 미납 조회 서비스를 그대로 불러 썼습니다. 그래서 새 구역과 기존 로열티 미납관리 화면은 언제나 같은 값을 봅니다.

정산이 선입선출이라 남아 있는 미납 버킷 자체가 곧 연속 구간이 됩니다. 연속 몇 회인지를 따로 구현할 필요가 없었습니다. 만약 새 화면이 자기 계산을 따로 가졌다면 두 화면이 서로 다른 회차를 말하는 사고로 이어졌을 겁니다. 그리고 그 사고는 빈 화면보다 훨씬 늦게 발견됩니다. 사용자가 두 화면을 나란히 놓고 대조하기 전까지는 아무 증상이 없습니다.

배포 순서가 잘못됐을 때 생긴 빈 화면은 몇 시간짜리였습니다. 계산을 복제했을 때 생기는 숫자 갈림은 몇 달을 갑니다. 새 화면을 붙일 때 가장 먼저 확인할 것은 이 숫자의 정본이 어디인가입니다.


임계값을 화면 토글로 만들지 않았습니다

연속 2회라는 기준은 서버 상수로 뒀습니다. 관리자가 화면에서 조절할 수 있게 하자는 선택지가 있었고, 만들지 않는 쪽으로 정했습니다. 되돌리기 쉬운 완화 장치는 한 방향으로만 움직이기 때문입니다.

할 일이 많아 보이는 날에 누군가 기준을 한 번 느슨하게 내립니다. 그리고 아무도 다시 올리지 않습니다. 목록이 짧아진 화면은 불편하지 않으므로 되돌릴 동기가 생기지 않습니다. 기준을 바꾸려면 코드를 고치고 배포하도록 남겨 두면, 바꾸는 일이 논의를 거치게 됩니다.

이 판단이 항상 옳지는 않습니다. 기준이 자주 바뀌는 도메인이라면 상수로 박는 선택은 그대로 배포 병목이 됩니다. 여기서 성립한 이유는 이 값이 완화 방향으로만 눌리는 값이기 때문입니다. 양방향으로 오가는 값이라면 화면에 두는 편이 낫습니다.


이 규칙이 성립하지 않는 자리

같은 저장소라면 이 문제가 생기지 않습니다. 한 번에 나가므로 순서를 정할 자리 자체가 없습니다

호환성을 깨는 변경이면 순서가 반대일 수 있습니다. 기존 응답 형식을 바꾸는 배포는 백엔드를 먼저 올리는 순간 옛 화면이 멈춥니다. 이 규칙은 추가만 하는 변경에서 성립하고, 깨는 변경은 애초에 두 단계로 나눠 올려야 합니다

빈 상태와 실패를 항상 갈라 그릴 필요는 없습니다. 실패해도 업무에 지장이 없는 보조 위젯이라면 조용히 비워 두는 편이 낫습니다. 갈라야 하는 건 그 0건이 판단 재료가 될 때입니다

덧붙이면, 이번에는 화면이 먼저 올라간 구간이 짧았고 배포한 사람이 직접 그 화면을 봤습니다. 구간이 조금만 더 길었거나 다른 일을 하고 있었다면 아무도 못 보고 지나갔을 겁니다. 사고가 나지 않았다는 사실이 규칙이 필요 없다는 뜻은 아닙니다.


배포 전에 확인하는 것

이 기능이 두 저장소에 걸쳐 있는가. 걸쳐 있으면 제공자(백엔드)를 먼저 올린다

추가만 하는 변경인가, 기존 응답 형식을 바꾸는 변경인가. 바꾸는 변경이면 두 단계로 나눈다

새 구역의 0건이 사용자의 업무 판단 재료인가. 그렇다면 성공한 0건과 실패한 0건을 화면에서 갈라 그린다

이 화면이 보여주는 수치를 다른 화면도 보여주는가. 그렇다면 계산을 복제하지 말고 정본을 공유한다

새로 만든 기준값이 한 방향으로만 눌리는 값인가. 그렇다면 화면 토글로 두지 않는다

두 저장소에 걸친 기능을 배포하기 전에 밟는 네 단계 점검 순서를 위에서 아래로 그린 도식


마무리

검증은 할 일 서비스 테스트 28건으로 했고 실패는 없었습니다. 그중 이번 축이 8건입니다. 폐점 매장을 제외하는 조건을 일부러 지워서 해당 테스트가 실제로 깨지는 것까지 확인하고 되돌렸습니다. 통과하는 테스트만 보면 그 테스트가 무엇을 지키고 있는지 알 수 없습니다.

배포 후에 확인할 것으로는 응답 속도를 적어 뒀습니다. 이 축은 외부 회계 SaaS 에서 가져온 원장을 통째로 읽어 선입선출을 돌리므로 다른 축보다 느릴 수 있습니다. 축마다 따로 부르는 구조라 화면 전체가 멈추지는 않지만, 체감이 나쁘면 캐시나 회차 사전계산을 검토하기로 했습니다.

이어진 2차 작업에서는 실제로 백엔드를 먼저 올렸습니다. 규칙 한 줄이 남았고, 그 한 줄을 만든 값은 몇 시간짜리 빈 화면이었습니다. 저장소가 갈린 기능의 배포 순서는 어느 쪽이 먼저 틀린 말을 하느냐로 정하면 매번 다시 고민하지 않아도 됩니다.

#배포#배포순서#빈상태#조용한실패#프론트백엔드분리#정본공유