프로그래밍/디자인패턴

피처 토글 패턴(Feature Toggle Pattern) 완벽 정리

Jinwookoh 2025. 12. 7. 21:22

"새로운 결제 기능을 배포했는데 치명적인 버그가 발견되었습니다. 서버를 내리고, 코드를 수정하고, 다시 빌드해서 배포하려면 최소 30분이 걸립니다. 그동안 결제는 멈춰있습니다."

만약 코드 안에 **"이 기능을 켤까요?"**를 확인하는 if 문이 하나 있고, 그 값을 외부에서 즉시 바꿀 수 있다면 어떨까요? 서버 재배포 없이 스위치만 내리면(Off) 구버전으로 돌아갈 수 있습니다.

이 스위치를 "설정 파일에 박아둘 것인가" 아니면 **"실시간으로 제어할 것인가"**에 따라 활용도가 달라집니다.


1. 방식 1: 정적 토글 (Static Toggle / Config-based) - "단순한 스위치"

애플리케이션의 설정 파일(application.yml, .env)에 기능의 ON/OFF 여부를 적어두는 방식입니다. 가장 구현하기 쉽습니다.

특징

  • 배포 의존: 값을 바꾸려면 애플리케이션을 **재시작(Restart)**하거나 설정을 리로드해야 합니다.
  • 유지보수 모드: 전체 시스템의 특정 기능을 아예 막아둘 때 유용합니다.

코드 예시 (Spring Boot)

YAML
 
# application.yml
features:
  new-billing-system: false # 아직 개발 중이라 꺼둠
Java
 
@Service
public class BillingService {

    @Value("${features.new-billing-system}")
    private boolean isNewSystemEnabled;

    public void processPayment() {
        if (isNewSystemEnabled) {
            // 새로운 결제 로직 (개발 중)
            processNewPayment();
        } else {
            // 기존 결제 로직 (안전함)
            processOldPayment();
        }
    }
}

장단점

  • 장점:
    • 구현 용이: 별도의 DB나 서버가 필요 없습니다. if 문 하나면 끝납니다.
    • 트렁크 기반 개발: 완성되지 않은 코드도 false로 설정해두면 메인 브랜치(Trunk)에 병합할 수 있어, 머지 지옥(Merge Hell)을 피할 수 있습니다.
  • 단점:
    • 대응 속도: 값을 바꾸려면 서버를 재시작해야 하므로, 긴급 상황(Kill Switch)에서 반응이 느립니다.
    • 타겟팅 불가: "사내 직원에게만 ON", "1% 유저에게만 ON" 같은 정교한 제어가 불가능합니다. 무조건 전체 ON 아니면 전체 OFF입니다.

2. 방식 2: 동적 토글 (Dynamic Toggle / Service-based) - "스마트 컨트롤러"

DB나 별도의 토글 관리 서버(Unleash, LaunchDarkly 등)에서 실시간으로 상태 값을 가져오는 방식입니다. A/B 테스트나 **카나리 배포(Canary Release)**의 핵심 기술입니다.

특징

  • 실시간 제어: 서버 재배포 없이 런타임에 기능을 켜고 끌 수 있습니다.
  • 컨텍스트 인식: "User ID가 짝수인 사람만", "한국 IP만" 등 요청 정보(Context)에 따라 다르게 동작합니다.

코드 예시

Java
 
@Service
public class ProductService {
    private final FeatureManager featureManager; // 토글 관리자 (DB or 외부 서비스 연동)

    public ProductDto getProduct(User user) {
        // "new-ui" 기능을 이 유저(user)에게 보여줄지 물어봄
        if (featureManager.isActive("new-ui-feature", user)) {
            return getNewProductUi(); // A그룹 (실험군)
        } else {
            return getOldProductUi(); // B그룹 (대조군)
        }
    }
}

활용 시나리오 (Canary Release)

  1. 새 기능을 배포하되, 토글은 OFF 상태로 둠. (사용자는 모름)
  2. 동적 설정에서 "사내 IP 대역"만 ON으로 변경 -> 직원들만 테스트.
  3. "전체 유저의 1%"만 ON으로 변경 -> 에러 로그 모니터링.
  4. 문제없으면 10% -> 50% -> 100%로 점진적 확대.

장단점

  • 장점:
    • Kill Switch: 치명적 버그 발견 시, 관리자 페이지에서 버튼 하나만 누르면 1초 만에 롤백 됩니다.
    • 데이터 기반 의사결정: A/B 테스트를 통해 어떤 UI가 매출이 더 좋은지 실험할 수 있습니다.
  • 단점:
    • 복잡도: 외부 서비스 호출로 인한 지연(Latency)이 발생할 수 있어 캐싱 전략이 필요합니다.
    • 기술 부채: 실험이 끝난 토글(if-else)을 제때 지우지 않으면, 코드가 스파게티가 됩니다. (Flag Cleanup 필수)

3. 실무 비교: 언제 무엇을 쓰는가?

구분 Static Toggle (Config) Dynamic Toggle (Service/DB)
제어 위치 설정 파일 (yml, env) DB, Redis, 외부 SaaS
변경 적용 재시작/재배포 필요 즉시 반영 (Runtime)
적용 범위 시스템 전체 일괄 적용 사용자별/조건별 타겟팅 가능
주 목적 개발 중인 코드 숨기기, 유지보수 A/B 테스트, 긴급 차단(Kill Switch)
구현 난이도 상 (인프라 구축 필요)

결론

"아직 덜 만든 기능을 메인 브랜치에 합치고 싶다"면 간단한 **정적 토글(Config)**을 사용하세요. 이것만으로도 장기 실행 브랜치(Long-lived branch)의 고통에서 해방됩니다.

하지만 **"장애 발생 시 1초 만에 끄고 싶다"**거나 **"특정 사용자층을 대상으로 신기능을 실험하고 싶다"**면 반드시 동적 토글(Dynamic) 시스템을 구축해야 합니다.

Tip: 피처 토글은 영원히 두는 게 아닙니다. 기능이 안정화되면 반드시 if 문을 지우고 코드를 정리해야 기술 부채가 쌓이지 않습니다.