← Ascendy EN

backend

그럴듯한 가짜 기본값이 prod를 조용히 삼킨다 — 검증을 '환경 신호'에 묶어라

· Ascendy Engineering


TL;DR

소스 노트. 이 글은 백엔드 팀의 인테이크(docs/intake/from-backend/2026-06-04-placeholder-defaults-mask-config.md)를 정제한 것이다. 시크릿 소스·k8s Secret·추론 엔드포인트 호스트 등 내부 식별자는 일반화했다. 같은 “조용한 실패” 가족의 형제 글이 둘 있다 — ERROR는 보이는데 INFO만 사라졌다(로그가 사라짐), 초록불이 거짓말을 하고 있었다(성공 판정이 거짓말). 이 글은 기본값이 누락을 마스킹하는 세 번째 형태다.

알림이 안 왔다 — 그리고 그건 운이 좋은 쪽이었다

운영 알림이 안 와서 조사를 시작했다. 원인은 시크릿 소스의 drift였다 — 재생성 과정에서 손으로 추가했던 여러 env 키가 한꺼번에 증발해 있었다. 알림 키도 그중 하나였다.

그런데 알림 쪽은 운이 좋았다. 키가 없을 때 코드가 credentials not configured; skipping을 로그로 남겼기 때문이다. “알림이 안 온다”는 증상과 “자격증명이 없다”는 로그가 연결되니, 추적이 가능했다.

진짜 위험은 같은 사고에서 함께 증발한 다른 키였다. AI 추론 엔드포인트 URL. 이 값의 기본값은 placeholder였는데 — 하필 그럴듯한 가짜였다.

INFERENCE_URL = os.getenv("INFERENCE_URL", "https://service.example.com")  # 빠져도 조용히 가짜로 감

키가 빠지면 코드는 멈추지 않는다. 기본값인 https://...example.com진짜 엔드포인트인 양 요청을 보낸다. 그 호스트는 우리 것이 아니니 응답이 의미 없고, 코드는 에러도 없이 조용히 실패한다. 알림 키처럼 skipping 로그조차 남지 않는다. 같은 사고에서 두 키의 운명이 갈렸다 — 한쪽은 로그를 남겼고, 한쪽은 가짜로 폴백했다. 후자가 훨씬 음험하다.

함정 — “그럴듯한 가짜”가 누락을 마스킹한다

https://service.example.com, service-host 같은 그럴듯한 가짜 기본값은 dev에서는 편하다. 아무것도 설정 안 해도 앱이 일단 뜨니까. 그런데 바로 그 편의가 prod에서는 누락을 마스킹한다. 값이 빠진 것과 값이 가짜인 것이 코드에서 구분되지 않으니, 누락이 “정상 동작처럼 보이는 실패”로 둔갑한다.

차라리 명백한 placeholder가 낫다. __SET_ME__ 같은 값은 최소한 “이건 진짜가 아니다”를 코드가 알아챌 수 있다. 진짜 위험은 가짜가 진짜처럼 생겼을 때다.

해법 — 검증을 ‘환경 신호’에 묶는다

그렇다고 “이 값이 없으면 무조건 죽어라”로 가면 dev와 CI가 다 깨진다. mock 모드나 CI에서는 추론 엔드포인트를 애초에 안 쓰니까. 반대로 무조건 통과시키면 prod가 조용히 깨진다. 그 사이를 가르는 신호가 핵심이다.

그 신호는 *“이 config를 실제로 쓰는 모드인가”*다. 추론이 원격 백엔드를 호출하는 모드일 때만, 그 URL을 startup에서 검증하고 없거나 placeholder면 fail-fast한다.

def validate_config(settings):
    if settings.inference_mode == "remote":          # ← 환경 신호: 이 값을 실제로 쓰는가
        if not settings.inference_url or is_placeholder(settings.inference_url):
            raise SystemExit("INFERENCE_URL required in remote mode (got placeholder/empty)")
    # mock/CI 모드는 통과 — dev를 깨지 않는다.

효과는 단순하다. “조용히 가짜로 실패”가 “시끄럽게 기동 실패”로 바뀐다. 누락은 배포 직후 1초 만에, 가짜 엔드포인트로 첫 요청이 나가기도 전에 드러난다. 가시성의 차이가 곧 평균 복구시간의 차이다.

레이어를 나눠 막는다

한 가지 더. 이 fail-fast는 코드 측 2차 방어다. 시크릿이 startup에서 빠졌음을 기동 시점에 잡는 그물이다. 하지만 애초에 시크릿 소스가 drift나서 키가 증발하는 일 자체를 막는 1차 방어는 인프라 레이어의 몫이다. 둘은 다른 층이고, 어느 하나로 다른 하나를 대신할 수 없다 — 레이어별로 방어를 나눠 거는 게 맞다. 이 글은 그중 코드 측 그물 한 겹을 다룬다.

가져갈 것


저작·인용: 이 글은 Ascendy Engineering이 작성했으며 출처 표기 시 재인용 가능합니다. 잘못된 정보를 발견하면 GitHub 이슈로 알려주세요.


Tags: configuration, fail-fast, silent-failure, observability, twelve-factor, defense-in-depth