--- id: feature-flags-in-practice title: 기능 플래그 실전 (Feature Flags in Practice) category: Coding status: draft canonical_id: feature-flags-in-practice aliases: [feature toggle, feature gate, kill switch, dark launch, 기능 플래그] duplicate_of: null source_trust_level: B confidence_score: 0.85 verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 last_reinforced: 2026-05-09 review_reason: "" merge_history: [] tags: [coding, deployment, release, experimentation, vibe-coding] raw_sources: ["P-Reinforce session 2026-05-09 — bulk Coding seed batch 1"] tech_stack: language: "TypeScript / 모든 언어" applicable_to: ["Backend", "Frontend", "Mobile"] applied_in: [] --- # 기능 플래그 실전 > **배포와 릴리즈를 분리**한다. 코드는 머지·배포되어도 사용자에겐 안 보일 수 있어야 한다. 플래그는 4종 — Release / Experiment / Ops / Permission — 으로 나누고 수명도 다르게. ## 📖 핵심 개념 기능 플래그는 "이 코드 경로를 누구에게 보일까?" 를 런타임에 결정하는 스위치. 단일 도구가 아니라 **수명·평가 주기·기록 정책이 다른 4종**으로 분류: | 종류 | 목적 | 수명 | 평가 빈도 | |---|---|---|---| | **Release Toggle** | 배포 ≠ 릴리즈. 롤백 가능 | 짧음 (< 30일) | 부팅 시 | | **Experiment Toggle** | A/B 테스트, 트래픽 분기 | 중간 (실험 기간) | 사용자별 | | **Ops Toggle** (kill switch) | 사고 시 즉시 차단 | 무기한 | 매 요청 | | **Permission Toggle** | 유료 / 베타 사용자 | 무기한 | 사용자별 | ## 💻 코드 패턴 ### 1. 평가 추상화 (구체 도구 격리) ```ts export interface FeatureFlags { isOn(flag: string, ctx?: { userId?: string }): boolean; variant(flag: string, ctx?: { userId?: string }): T | undefined; } ``` LaunchDarkly / Unleash / GrowthBook / 자체 DB — 모두 위 인터페이스 뒤에. 비즈니스 코드는 도구를 모름. ### 2. Release Toggle — 부팅 시 평가 + 캐시 ```ts const newCheckoutEnabled = flags.isOn('release.new-checkout'); app.post('/api/checkout', async (req, res) => { if (newCheckoutEnabled) return newCheckoutHandler(req, res); return legacyCheckoutHandler(req, res); }); ``` 부팅 시 한 번 읽는 게 일반적. 변경하려면 재배포 또는 reload. ### 3. Experiment Toggle — 사용자별 분기 ```ts app.get('/dashboard', async (req, res) => { const variant = flags.variant<'control' | 'B'>('exp.dashboard-redesign', { userId: req.user.id }); if (variant === 'B') return renderDashboardB(res); return renderDashboardA(res); }); ``` userId hash 로 sticky 분기. 같은 사용자는 같은 variant. ### 4. Ops Toggle (Kill Switch) — 매 요청 평가 ```ts async function callExternalProvider(input: Input) { if (flags.isOn('ops.disable-provider-x')) { return { ok: false, reason: 'TEMPORARILY_DISABLED' }; } return providerX.call(input); } ``` 장애 발생 시 1분 내 차단. **매 호출 평가 필수** — 부팅 캐시는 여기에 부적합. ### 5. Permission Toggle ```ts function canAccessAdvancedAnalytics(user: User): boolean { if (user.plan === 'enterprise') return true; if (flags.isOn('beta.analytics', { userId: user.id })) return true; return false; } ``` ## 🤔 의사결정 기준 | 상황 | 플래그 종류 | 수명 | 비고 | |---|---|---|---| | 큰 리팩터 / DB 마이그레이션 | Release | 머지 후 1~2주 | dual-write, dual-read 패턴 | | 새 UI vs 기존 UI 비교 | Experiment | 실험 기간 | 통계 유의성 도달 후 종료 | | 외부 API 사고 격리 | Ops | 무기한 | 매 요청 평가 | | 유료 플랜 기능 | Permission | 무기한 | 보통 DB 기반, 플래그 시스템과 통합 가능 | | Frontend 기능 점진 노출 | Release / Experiment | 짧음 | client-side 평가 시 보안 주의 | ## ❌ 안티패턴 - **플래그 무덤**: 다 끝난 Release Toggle 이 1년 넘게 코드에 남아 있음. **만료일 설정 + 정기 청소**. CI 에서 expired flag 검증 도구. - **분기마다 새 함수 만들지 않음**: `if (flag) { ... 100 lines ... } else { ... 100 lines ... }` 형태로 한 함수에 두 분기. 두 함수로 분리. - **테스트가 한 분기만 검증**: 양 분기 모두 테스트. 끄고 켜고 둘 다. - **사용자 ID 없이 hash**: 같은 사용자에게 매번 다른 variant. sticky 보장 안 됨. - **Ops Toggle 을 부팅 캐시**: 사고 시 차단이 안 됨. ops 는 매 요청 평가. - **민감 분기를 client-side 평가**: 사용자가 토글 가능. 권한 / 보안 게이트는 server-side. - **flag dependency hell**: A 가 B 에 의존, B 가 C 에 의존 — 어떤 조합이 유효한지 모름. 평가 매트릭스 문서화. - **flag 전파에 retry 없음**: flag 시스템 장애 시 default 동작 명확히 (보통 "off" 가 안전). ## 🤖 LLM 활용 힌트 - LLM에게 새 기능 코드 작성: "**flag 뒤에 두고, 두 분기를 별도 함수로 분리**" 명시. - 큰 리팩터: "**release toggle + dual-read/write 단계로 점진적 전환**" 패턴 요청. - 사고 대응 코드: "**외부 의존성마다 ops kill switch 추가**" 명시. - 청소: 코드에 `flag.isOn('release.X')` 검색 → 만료일 30일 지난 것 보고. ## 🧪 검증 상태 - verification_status: `conceptual` - Martin Fowler "Feature Toggles" 분류, Pete Hodgson 의 4종 분류가 표준. - 적용 사례 발견 시 `applied_in` 추가. ## 🔗 관련 문서 - [[Idempotent_Operations]] - [[Optimistic_Concurrency_Control]] - [[Backpressure_Patterns]]