5.5 KiB
5.5 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, created_at, updated_at, last_reinforced, review_reason, merge_history, tags, raw_sources, tech_stack, applied_in
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | created_at | updated_at | last_reinforced | review_reason | merge_history | tags | raw_sources | tech_stack | applied_in | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| feature-flags-in-practice | 기능 플래그 실전 (Feature Flags in Practice) | Coding | draft | feature-flags-in-practice |
|
null | B | 0.85 | conceptual | 2026-05-09 | 2026-05-09 | 2026-05-09 |
|
|
|
기능 플래그 실전
배포와 릴리즈를 분리한다. 코드는 머지·배포되어도 사용자에겐 안 보일 수 있어야 한다. 플래그는 4종 — Release / Experiment / Ops / Permission — 으로 나누고 수명도 다르게.
📖 핵심 개념
기능 플래그는 "이 코드 경로를 누구에게 보일까?" 를 런타임에 결정하는 스위치. 단일 도구가 아니라 수명·평가 주기·기록 정책이 다른 4종으로 분류:
| 종류 | 목적 | 수명 | 평가 빈도 |
|---|---|---|---|
| Release Toggle | 배포 ≠ 릴리즈. 롤백 가능 | 짧음 (< 30일) | 부팅 시 |
| Experiment Toggle | A/B 테스트, 트래픽 분기 | 중간 (실험 기간) | 사용자별 |
| Ops Toggle (kill switch) | 사고 시 즉시 차단 | 무기한 | 매 요청 |
| Permission Toggle | 유료 / 베타 사용자 | 무기한 | 사용자별 |
💻 코드 패턴
1. 평가 추상화 (구체 도구 격리)
export interface FeatureFlags {
isOn(flag: string, ctx?: { userId?: string }): boolean;
variant<T extends string>(flag: string, ctx?: { userId?: string }): T | undefined;
}
LaunchDarkly / Unleash / GrowthBook / 자체 DB — 모두 위 인터페이스 뒤에. 비즈니스 코드는 도구를 모름.
2. Release Toggle — 부팅 시 평가 + 캐시
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 — 사용자별 분기
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) — 매 요청 평가
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
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추가.