애플리케이션 구성 관리: 프로덕션 환경에서 반드시 피해야 할 10가지 실수
타임아웃, 인증 정보, 리소스 제한 설정 오류는 로직 버그보다 훨씬 빠르게 시스템 장애를 유발합니다. 코드와 달리 구성(config)은 종종 코드 리뷰, 자동화된 테스트, CI 파이프라인을 우회하기 때문에 ‘조용한 위험’이 생깁니다: 잘못된 데이터베이스에 쓰기, 커넥션 풀 고갈, 인증 정보 유출 등이 대표적입니다. 실제 사고 분석 결과, 실패가 발생할 때까지 드러나지 않던 10가지 미묘한 구성 오류가 확인됐습니다.
하드코딩된 타임아웃: 코드 전반에 산재한 30건 이상
소스코드 내 타임아웃 값을 하드코딩하면 조정을 위해 전체 빌드가 필요합니다. Duration.ofSeconds를 검색해보니, NotificationService는 10초, SearchService는 30초, ApiRoutes는 15초 등 불일치하는 값이 30건 이상 산재해 있었습니다.
private static final Duration TIMEOUT = Duration.ofSeconds(10); // NotificationService
private static final Duration TIMEOUT = Duration.ofSeconds(30); // SearchService
private static final Duration TIMEOUT = Duration.ofSeconds(10); // AuthFilter
private static final Duration TIMEOUT = Duration.ofSeconds(15); // ApiRoutes
부하 상황에서는 타임아웃 조정이 핵심적이지만, 하드코딩은 유연성을 완전히 차단합니다. 해결책: 환경 변수 오버라이드 기능을 갖춘 외부 구성 파일로 이관하세요.
누락된 검증: 실수로 프로덕션 DB 전체 삭제하기
695줄짜리 구성 클래스가 검증하는 파라미터는 단 하나뿐입니다. 나머지 모든 값은 무비판적으로 수용됩니다: 커넥션 풀 크기 = 0, 타임아웃 = -1, 빈 DB URL 등이 모두 유효한 입력으로 간주됩니다.
String authType = config.getString("authType");
if (!Set.of("kerberos", "jwt").contains(authType)) {
throw new IllegalArgumentException("잘못된 인증 방식입니다. 허용값: kerberos, jwt");
}
위험 사례로는 http.server.parsing.max-content-length = 10g: 단일 요청으로 10GB 메모리 소모, 또는 Liquibase의 dropAll = ${?DROP_ALL}—확인 없이 전체 스키마를 삭제하는 설정 등이 있습니다. 시작 시점에 즉시 실패(fail-fast)하도록 하면 사고를 막을 수 있습니다.
실패를 가리는 기본값
기본값은 누락된 키를 숨겨 런타임까지 문제를 드러내지 않습니다. SSO 활성화 시 다음과 같은 코드가 있습니다:
Duration maxResponseDelay = ssoEnabled
? config.getDuration("sso.maxResponseDelay")
: Duration.ofMinutes(10);
long responseCacheSize = ssoEnabled
? config.getLong("sso.responseCacheSize")
: 10_000;
누락된 키는 프로덕션에서 바로 충돌을 일으킵니다. 마찬가지로 tokenTtl은 구성 파일에서는 1분, 코드에서는 3분으로 기본값이 달라 보안 정책을 위반합니다.
- 기본값 함정: 구성 오류는 기능 활성화 시점까지 감춰져 있습니다.
- 해결책: 시작 시 모든 필수 키를 검증하고, 기본값 사용 시 경고 로그를 남기세요.
테스트와 프로덕션 간 구성 편차
테스트 구성은 프로덕션과 규모 면에서 극단적으로 다릅니다:
| 파라미터 | 프로덕션 | 테스트 |
|-----------|------------|------|
| api.responseLimit | 10MB | 1GB |
| api.contextLimit | 10MB | 1MB |
50MB 응답은 테스트에선 통과하지만 프로덕션에선 실패합니다. 반대로, 테스트 구성이 지나치게 엄격하면 잘못된 실패(false negative)가 발생합니다.
불일치하는 파라미터 명명법
하나의 application.conf 파일 안에서 다양한 명명 규칙이 혼재합니다:
fileStoragePath = "/opt/storage" # 카멜케이스
retention-days = 7 # 케밥케이스
createdump-interval-seconds = 240 # 혼합형
usage-threshold = 80 # 케밥케이스
compressHeapDump = true # 카멜케이스
불리언 값도 천차만별입니다: sslValidationEnabled, smtpSslEnable, saml.enabled, isUseSecureCookieJWT. 환경 변수 오버라이드도 실패합니다: smtpSslEnable → SMTP_SSL_ENABLED (하지만 Enable ≠ Enabled).
- 하나의 파일 내
카멜케이스와케밥케이스혼용. - HOCON 키와 환경 변수 이름 불일치.
- 불리언 플래그 표현 방식이 네 가지 이상 존재.
생명 주기 관리 없는 '좀비' 기능 플래그
구성 클래스에 16개의 기능 플래그가 있습니다: ws-cache-enabled=false는 전부 false, history-import-enabled=true는 항상 true. 생성일, 담당자, 폐기 이유가 전혀 없어 인지 부담이 급증합니다.
- 추가: 생성일, 담당자, 목적, 폐기 예정일을 반드시 기록하세요.
- 제거: 영원히
true/false로 고정되어 사용되지 않는 플래그는 과감히 삭제하세요.
잘못된 HOCON 오버라이드 순서
HOCON에서는 마지막에 선언된 값이 우선 적용됩니다. 올바른 예:
key = "기본값"
key = ${?ENV_OVERRIDE}
잘못된 예:
dump {
enabled = ${?DUMP_ENABLED}
enabled = false
directory = ${?DUMP_DIR}
directory = "opt/myapp/dumps/auto"
}
환경 변수 오버라이드(DUMP_ENABLED)는 false에 의해 덮어씌워져, 덤프 기능은 항상 비활성화됩니다.
문서화되지 않은 구성: 100개 이상의 파라미터, 설명은 제로
docker.md에는 EMAIL_ENABLED=false/true처럼 환경 변수만 나열되어 있고 설명은 없습니다. AppConfig.java는 695줄에 달하지만 Javadoc은 전무합니다. sync-request-timeout-seconds=600은 무엇을 제어하나요? workflowStepLimit=200이 0이나 100000이 되면 어떤 일이 벌어질까요?
하드코딩된 외부 URL 및 로그 레벨
API 엔드포인트를 코드에 직접 삽입하면 엔드포인트 변경 시 재빌드가 필요합니다. 로그 레벨을 도커 이미지에 하드코딩하면 DEBUG 활성화를 위해 볼륨 마운트나 이미지 재빌드가 불가피합니다.
핵심 요약:
- 모든 설정을 외부화하고, 시작 시 엄격하게 검증하세요.
- HOCON과 환경 변수 전반에 걸쳐 단일 명명 규칙을 강제하세요.
- 각 파라미터에 대해 타입, 유효 범위, 기본값, 비즈니스 영향을 문서화하세요.
- 조용한 대체(fallback)보다는 즉시 실패(fail-fast) 원칙을 따르세요.
- 기능 플래그는 생명 주기 관리가 필수: 담당자, 목적, 폐기 예정일을 명시하세요.
— Editorial Team
아직 댓글이 없습니다.