같은 목록을 세어봤더니 백엔드 19개, 프론트 18개였습니다
배치 스케줄 화면을 다루던 중이었습니다. "매장 동기화 잡은 기간이 무관한데, 화면엔 왜 날짜 범위가 뜨죠?"라는 지적이 들어왔습니다. 처음엔 표시 조건 하나 고치면 끝나는 사소한 UI 이슈라고 생각했습니다.
그런데 "화면이 어떻게 기간 무관인 줄 아는가"를 따라가면서 이야기가 달라졌습니다. 표시 조건을 고치려면 판정 기준이 있어야 하는데, 그 기준이 화면 어디에도 없었습니다.
판정 기준이 서버 안에만 있었습니다
"날짜 무관" 잡의 목록은 백엔드 서비스의 내부 상수였습니다. 화면이 알 방법이 없으니, 프론트는 "시스템 타입이면 기간 무관"이라는 어림짐작 분기를 하고 있었습니다. 문제는 날짜가 무관한 잡이 존재한다는 점입니다. 이 잡들은 화면에서 의미 없는 가짜 기간을 달고 있었습니다.
여기서 멈추지 않고 지원 잡 목록 자체를 세어봤습니다. 백엔드가 19개, 프론트가 18개였습니다. 같은 목록을 양쪽에 각각 하드코딩해 둔 결과였습니다. 프론트에 빠진 1개 잡은 화면에서 태스크 추가가 아예 불가능한 상태였고, 그동안 아무도 눈치채지 못했습니다.

이중 하드코딩은 이미 어긋나 있습니다
이 사건의 첫 번째 결론입니다. 같은 목록을 두 곳에 하드코딩하면 "언젠가" 어긋나는 게 아니라, 발견하는 시점엔 이미 어긋나 있습니다. 드리프트는 조용히 진행되고, 발견 계기는 대개 "안 되는 기능"입니다. 목록은 한쪽이 소유하고 다른 쪽은 받아 쓰는 구조여야 어긋날 방법 자체가 사라집니다.
정의나 목록이 여러 곳에 흩어질 때 생기는 어긋남은 집계에서도 똑같이 겪었습니다. 브랜드별 매출을 다 더했더니 전체보다 커졌던 사건도 결국 같은 뿌리였습니다.
설정 컬럼으로 만들 뻔했습니다
처음 떠올린 해법은 단순했습니다. 잡 설정 테이블에 "기간 사용 여부" 컬럼을 추가하고, 화면은 그 값을 보고 날짜 입력 칸을 켜고 끄면 된다는 것이었습니다. 유연해 보였지만, 파고들수록 이 방향이 위험하다는 게 분명해졌습니다.
"기간을 쓰는가"는 운영자가 정하는 취향이 아니라 구현이 정하는 사실이기 때문입니다. 매장 동기화 잡의 코드는 애초에 날짜 파라미터를 받지 않습니다. 화면에서 설정을 켠다고 코드가 날짜를 쓰게 되지 않습니다. 반대로 잘못 켜면 더 큰 사고가 됩니다. 기간 잡에만 적용해야 할 월별 분할 디스패치가 전체 동기화 잡에 걸리면, 같은 전체 동기화가 개월 수만큼 중복 실행됩니다.
그래서 기준을 두 축으로 나눴습니다. "기간을 쓰는가"는 구현이 정하는 사실(capability)이라 설정이 아니라 선언 대상입니다. 반면 "며칠을 볼 것인가"는 다릅니다. 지연 적재를 흡수하려고 35일을 돌아보는 잡의 그 35일은 운영이 상황을 봐가며 조정하는 튜닝값이라, 이것은 설정이 맞습니다.
구분 | 기간을 쓰는가 | 며칠을 볼 것인가(35일) |
|---|---|---|
성격 | 구현이 정하는 사실(capability) | 운영 튜닝값 |
바꾸는 주체 | 코드 수정(구현 변경) | 운영자(화면 조정) |
두는 곳 | 카탈로그 선언 | 설정 |
설정으로 만들면 | 켜도 무시되거나 중복 실행 사고 | 정상 — 이게 설정의 자리 |

카탈로그 API 하나로 합쳤습니다
구현은 잡 카탈로그를 단일 모듈로 승격하는 것이었습니다. jobKey, 타입, 라벨과 함께 각 잡이 받는 날짜 파라미터를 dateFields로 선언합니다. 어떤 잡은 [lookback, toOffset, chunk]를 전부 받고, 어떤 잡은 [lookback]만 받고, 기간 무관 잡은 빈 배열입니다. 이 카탈로그를 API로 노출했습니다.
프론트는 하드코딩 미러를 삭제하고 카탈로그를 받아 잡이 선언한 칸만 렌더합니다. "기간 사용 여부" 같은 boolean이 아니라 필드 목록을 선언한 이유는 표현력입니다. 잡마다 받는 파라미터 조합이 다르면 켜고 끄기로는 부족합니다. 타입으로 때려잡던 프론트 분기도 함께 사라졌습니다.

35일이 1일로 조용히 줄어들 뻔했습니다
마지막에 하나 더 있었습니다. 구조를 바꾸면서 기존 잡 설정에 남은 잔재값 정리 DML을 함께 실행했는데, 이걸 건너뛰었다면 채널 재집계 잡의 조회 기간이 35일에서 1일로 조용히 줄어들 뻔했습니다. 새 코드의 기본값이 1일이었기 때문입니다.
에러가 나는 것도 아니고 그냥 덜 도는 동작이라, 수치가 어긋난 한참 뒤에야 발견됐을 것입니다. 메타 구조 정리에는 데이터 정리가 한 세트로 따라온다는 걸 이 대목에서 확인했습니다.
비슷한 구조를 점검할 때 보는 것들
같은 목록이 백엔드와 프론트에 각각 하드코딩되어 있는가 — 있다면 이미 어긋나 있다고 가정하고 개수부터 센다
프론트가 서버 내부 상수를 타입 같은 걸로 어림짐작하는 분기가 있는가 — 선언을 내려보내면 분기가 사라진다
설정 테이블의 각 컬럼이 운영이 정하는 값인가, 구현이 정하는 사실인가 — 사실이면 카탈로그 선언으로 옮긴다
capability를 boolean으로 표현하기에 부족하지 않은가 — 파라미터 조합이 항목마다 다르면 필드 목록을 선언한다
메타 구조를 바꿀 때 기존 데이터의 잔재값 정리 계획이 있는가 — 새 코드의 기본값과 만나면 조용한 동작 변경이 된다
새 잡을 추가할 때는 이제 같은 파일의 이웃한 두 곳만 수정하면 화면에 자동 반영됩니다. 그동안 추가가 불가능했던 잡도 드롭다운에 나타났습니다. 시작은 "왜 날짜가 뜨죠?"라는 한 줄 지적이었지만, 남은 것은 설정으로 만들 것과 선언으로 내릴 것의 경계였습니다.