✍️ 필사 모드: Feature Flag와 Progressive Delivery 완전 해부 — Dark Launch, Canary, Rolling, A/B, OpenFeature까지
한국어들어가며 — "배포했는데 아직 기능은 없어요"
현대 소프트웨어의 이상한 원칙:
"배포 ≠ 릴리스"
새 코드를 운영 서버에 올리는 것(배포)과, 사용자에게 기능을 켜주는 것(릴리스)은 다르다. 둘을 섞으면 배포가 곧 공포다. 코드 한 줄 바꿀 때마다 "장애 나면 어쩌지"라는 마음으로 롤백 준비.
분리하면? 배포는 안전한 일상이 되고, 릴리스는 비즈니스 결정이 된다. 이걸 가능케 하는 기술이 Feature Flag다. 이 글에서는:
- Feature Flag의 역사 — Facebook의 "Gatekeeper" 비밀
- 4가지 플래그 타입 — release, experiment, ops, permission
- Progressive Delivery — Canary, Rolling, Blue/Green
- A/B 테스팅과 Feature Flag의 차이
- Trunk-based Development — 브랜치 지옥 탈출
- 주요 도구 비교 (LaunchDarkly, Unleash, GrowthBook, Flagsmith)
- Open Feature — CNCF 표준화 움직임
- 플래그 부채 관리 — "좀비 플래그"의 문제
- 실전 롤아웃 전략 10개
이전 글 Chaos Engineering 완전 해부에서 "프로덕션에서 실패를 탐색한다"고 했다. Feature Flag는 "프로덕션에서 안전하게 신기능을 탐색"하는 반대편 짝이다.
1. 기원 — Facebook의 Gatekeeper
2004~2008 Facebook의 문제
Facebook이 기능을 매주 하나씩 내놓고 싶은데:
- 버그 나면 수백만 사용자 영향
- 롤백은 코드 revert + 재배포 → 수 시간
- QA 환경이 프로덕션과 다름
내부 도구 "Gatekeeper"
엔지니어가 코드에 간단한 체크를 넣음:
if (Gatekeeper::check("new_newsfeed", user)) {
return renderNewFeed();
} else {
return renderOldFeed();
}
관리자가 중앙 서버에서 "new_newsfeed를 10% 사용자에게"로 바꾸면 즉시 반영. 재배포 없음.
Dark Launch
더 나아가 "Dark Launch" — 기능을 UI에 노출하지 않고 백엔드만 동작시켜 부하를 먼저 측정. 2008년 Facebook Chat 출시 때 수 주간 Dark Launch로 서버 용량 검증. 덕분에 기능 공개 첫날 문제 없음.
이 패턴이 2010년대 Etsy, Netflix, Google로 퍼지고 LaunchDarkly (2014) 같은 SaaS의 탄생으로 대중화.
2. Feature Flag의 4가지 타입 — 수명과 목적으로 분류
Pete Hodgson의 분류 (martinfowler.com 2017)가 업계 표준:
1. Release Toggle (배포 토글)
- 수명: 단기 (며칠~몇 주)
- 목적: 미완성 기능을 main 브랜치에 머지하되 사용자에겐 숨김
- 예: 결제 시스템 개편, UI 리뉴얼
2. Experiment Toggle (실험 토글)
- 수명: 중기 (실험 기간)
- 목적: A/B 테스트 — 어떤 변형이 더 나은 전환율?
- 예: CTA 버튼 색, 가격 표시 방식
3. Ops Toggle (운영 토글)
- 수명: 장기 또는 영구
- 목적: 장애 대응 — 특정 기능 긴급 비활성화
- 예: 외부 API 의존 기능, 고부하 리포트
4. Permission Toggle (권한 토글)
- 수명: 영구 (제품의 일부)
- 목적: 플랜/역할 기반 기능 제어
- 예: Premium 기능, 관리자 권한
분류가 중요한 이유
수명 관리가 다름:
- Release Toggle은 출시 후 즉시 제거해야 함 (안 하면 부채)
- Experiment Toggle은 결과 확정 후 한쪽 유지, 다른 쪽 제거
- Ops Toggle은 영구 유지 (코드로 인프라 제어)
- Permission Toggle은 영구 유지 (비즈니스 로직)
혼동하면 "무슨 플래그가 몇 개인지 모르는 정글" 탄생.
3. Progressive Delivery — 점진적 공개 전략
Big Bang Release의 위험
예전엔 배포 = "모든 사용자에게 한 번에". 버그 나면 수백만 영향.
Canary Deployment
일부 서버나 일부 사용자에게 먼저 새 버전:
100% 트래픽
├── 95% → 구버전 v1.0
└── 5% → 신버전 v1.1 (카나리)
광산의 카나리처럼 소수가 먼저 위험을 감지. 1% → 5% → 25% → 100%로 점진.
Rolling Deployment
서버 인스턴스를 한 번에 몇 대씩 교체. Kubernetes의 기본 전략.
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
장점: 단순, 추가 자원 적음. 단점: 롤백이 느림 (다시 롤링).
Blue/Green Deployment
완전히 같은 구성 두 세트(blue=현재, green=새).
- Green에 새 버전 배포
- 테스트
- 트래픽을 한 번에 blue → green 전환
- 문제 없으면 blue 폐기, 문제 있으면 blue로 복귀
장점: 즉각 롤백. 단점: 자원 2배, DB 스키마 호환성 관리.
Feature Flag 기반 배포
위의 "서버 수준" 전략과 달리 사용자 수준 제어:
같은 코드 배포 후
→ Flag: new_checkout
→ 사용자 IDs 1~1000에게만 켬
→ 전환율 측정
→ 좋으면 100%로 확장
장점:
- 서버 재배포 없이 제어
- 사용자 단위 정교한 타겟팅
- 순간 롤백 (플래그 OFF)
4. A/B 테스트 vs Feature Flag
겹치는 부분
둘 다 "일부 사용자에게만 다른 동작 보여주기".
다른 부분
| 측면 | Feature Flag | A/B 테스트 |
|---|---|---|
| 목적 | 배포 안전성, 권한 제어 | 통계적 실험 |
| 측정 | 기본 오류율 | 비즈니스 지표 (전환율, LTV) |
| 기간 | 가변 | 유의성 도달까지 |
| 설계 | 단순 ON/OFF | 통제된 실험 (random, 표본 수) |
| 도구 | LaunchDarkly, Unleash | Optimizely, VWO, GrowthBook |
현실 — 통합 플랫폼
LaunchDarkly, GrowthBook은 Feature Flag + A/B를 하나의 플랫폼에서 제공. 같은 "규칙 엔진" 기반이라 자연스러움.
A/B 테스트의 통계 함정
- P-hacking — 지표 여러 개 놓고 우연히 유의한 것만 보고
- Peeking — 실험 중간에 결과 보고 "통계적 유의성 달성 시 즉시 종료" → 실제론 유의 X
- Sample Ratio Mismatch (SRM) — A:B가 50:50으로 나누려 했는데 48:52면 랜덤화 버그 의심
- Novelty Effect — 새 UI가 처음엔 호기심으로 클릭, 2주 후엔 평범
통계학자 협업 권장. Sequential Testing (CUPED, mSPRT) 같은 기법이 peeking 문제 해결.
5. Trunk-based Development — 긴 브랜치의 종말
Git Flow의 한계
2010년 Vincent Driessen의 Git Flow:
- feature/xxx 브랜치
- develop 브랜치
- release/xxx 브랜치
- hotfix 브랜치
- main 브랜치
이론상 깨끗. 현실은:
- 머지 지옥 — 브랜치 오래 살면 충돌 폭증
- 통합 지연 — 기능이 통합되지 않은 채 2주 이상
- "크리스마스 트리" 배포 — 일주일치 변경을 한 번에 올림, 장애 나면 원인 불명
Trunk-based의 제안
- 모든 개발자가 main(trunk)에 직접(또는 1일 수명 브랜치에서) 커밋
- 배포는 매일 또는 매시간
- 미완성 기능은 Feature Flag로 숨김
- 주요 기업: Google, Facebook, Netflix, Amazon
장점
- 통합이 매일 → 충돌 최소
- 린 딜리버리
- 모든 커밋이 잠재적 배포
단점 / 필수 조건
- 강력한 CI 파이프라인
- 자동화된 테스트
- Feature Flag 인프라
- 팀의 숙련도
"우리 팀에서는 안 통해요"는 대부분 CI/CD 성숙도가 원인.
6. 도구 생태계 비교
LaunchDarkly (2014)
- 업계 리더, 엔터프라이즈 중심
- 거의 모든 언어 SDK (20+)
- 강력한 experimentation
- 가격: 사용자 수 기반, 비싼 편
Unleash (2014, Open Source)
- 오픈소스 (Apache 2.0)
- 셀프호스팅 가능 (데이터 주권)
- Slack/GitHub/Jira 통합
- Enterprise 버전 유료
GrowthBook (2020)
- 오픈소스
- Experimentation first (A/B 중심)
- BigQuery/Snowflake 직접 연결해서 지표 계산
- Data team 친화적
Flagsmith (2021)
- 오픈소스
- Remote config 기능 강조
- 모바일 앱 친화적
ConfigCat
- 중소 SaaS 친화 가격
- 다국가 엣지 배포
자체 구현
구글/메타/넷플릭스는 자체 인프라. 수천 플래그 관리에 SaaS로는 부족. 하지만 초기부터 자체 구현은 99% 낭비 — 초기엔 Unleash 자체호스팅 또는 Flagsmith 권장.
7. Open Feature — 표준화의 시대
문제
앱 하나가 LaunchDarkly 쓰다 Unleash로 옮기려면? 전체 코드에서 SDK 교체. 테스트 재작성. 이주 프로젝트.
Open Feature (2022, CNCF)
표준 API를 정의하고, 각 벤더가 Provider 구현을 제공. 애플리케이션은 Open Feature API만 호출. 벤더 교체 = Provider만 교체.
import { OpenFeature } from '@openfeature/server-sdk';
import { LaunchDarklyProvider } from '@openfeature/launchdarkly-provider';
OpenFeature.setProvider(new LaunchDarklyProvider({ sdkKey: '...' }));
const client = OpenFeature.getClient();
const showNewUi = await client.getBooleanValue('new-ui', false, context);
나중에 Unleash로 바꾸려면 setProvider만 교체. 나머지 코드 그대로.
2025 생태계
- 공식 Provider: LaunchDarkly, Flagsmith, Split, ConfigCat, GoFeatureFlag
- SDK: Node, Java, Go, Python, .NET, Ruby, PHP, C++, Swift, Kotlin
CNCF Incubating 상태. 빠르게 표준으로 자리잡는 중.
8. 플래그 부채 관리 — 좀비 플래그의 공포
부채가 어떻게 쌓이는가
- 엔지니어 A가 Release Toggle 만듦
- 기능 100% 출시 완료
- A는 다른 일로 바쁨 → 플래그 코드 제거 잊음
- 몇 달 뒤 코드에 "만약 이거 꺼져 있으면..." 분기 남음
결과:
- 조건 분기 폭증 — 코드 복잡도
- 테스트 행렬 폭발 — 2^N 가지 조합
- 이해 불가 — "이 플래그 OFF면 무슨 일이?" 아는 사람 없음
대응 전략
1. 플래그 Naming Convention
[타입]_[팀]_[기능]_[만료일]
release_checkout_new-ui_2026-06-01
2. Expiry Dates + 알림
- 각 플래그에 만료일. 지나면 Slack 알림.
- LaunchDarkly는 "Flag Expiration" 기능 내장.
3. 정기 Flag Audit
- 매 분기 미팅에서 "이 플래그 아직 필요?" 점검
- Cleanup PR 전담자 지정
4. Static Analysis
- IDE 린트로 "사용 안 되는 플래그" 탐지
- ESLint, Sonar 플러그인 활용
5. "플래그 예산" 개념
- 팀당 활성 플래그 50개 이하 유지 같은 정책
유명한 실패 사례
Knight Capital 2012년 4.5억 달러 손실:
- 8년 된 사용하지 않는 플래그를 실수로 재활성화
- 옛 트레이딩 로직이 다시 켜지며 45분만에 회사 파산
- 교훈: 부채 방치는 직접 비용
9. 실전 롤아웃 전략 10가지
전략 1: Percentage-based Rollout
1% → 5% → 25% → 50% → 100%
각 단계 24시간 관찰 후 진행
전략 2: Ring-based Deployment
사용자를 링으로 분류:
Ring 0: 내부 직원 (도그푸딩)
Ring 1: 얼리 어답터 (베타 참여자)
Ring 2: 무료 사용자
Ring 3: 유료 사용자
Ring 4: 엔터프라이즈
Ring 0→4로 점진 공개. Microsoft가 Windows 업데이트에 이 방식 사용.
전략 3: 지리적 롤아웃
Week 1: 뉴질랜드, 호주 (작은 시장)
Week 2: 유럽 일부
Week 3: 미국
Week 4: 전 세계
장점: 작은 시장에서 먼저 검증, 시차로 24시간 모니터링.
전략 4: 세그먼트 기반
IF user.is_premium AND user.country = 'KR'
THEN flag = ON
전략 5: Sticky Bucketing
같은 사용자는 항상 같은 결과. hash(user_id + flag_name) % 100 < rollout_percent.
전략 6: Holdout Group
장기 실험에서 소수 사용자는 영구 신기능 제외. 실제 비즈니스 임팩트 측정용.
전략 7: Automated Rollback
if error_rate > 1% OR p99 > 500ms:
rollback_flag(immediate)
관측성과 통합된 자동화가 SRE 시대의 기본.
전략 8: Shadow Traffic
프로덕션 요청을 복제해서 새 버전에도 보냄. 응답은 버림. 실제 부하 영향 평가.
전략 9: Feature Flag + Database Migration
- 플래그 OFF: 기존 스키마 사용
- 플래그 50%: 이중 쓰기 — 새/옛 스키마 모두
- 플래그 100%: 새 스키마만
전략 10: Kill Switch
외부 API 고장 시 기능 자체를 비활성화. Ops Toggle의 전형.
if (externalAPI.isDown()) {
flag.force("use-fallback", ON);
}
10. 플래그 평가의 아키텍처
Client-side vs Server-side
Client-side (브라우저/모바일)
- 장점: 서버 왕복 없음, 빠름
- 단점: 규칙 엔진이 클라이언트에 노출 (보안), SDK 키 유출 위험
- 민감 플래그 X
Server-side
- 장점: 규칙 보호, 개인정보 서버에 머무름
- 단점: 매 요청 또는 주기적 동기화 필요
평가 지연 최적화
Streaming Updates: SDK가 SSE/Polling으로 실시간 업데이트. 몇 초 이내 전파.
Local Evaluation: 규칙 전체를 SDK에 다운로드 → 로컬 평가. 네트워크 왕복 0.
Edge Evaluation: Cloudflare Workers 같은 엣지에서 평가. 글로벌 저지연.
11. 실무 함정과 안티패턴
함정 1: 플래그가 비즈니스 로직에 스며듦
// 나쁜 예
if (flag("feature-a")) {
if (flag("feature-b")) {
if (user.country === 'KR' && flag("feature-c")) {
// ...
}
}
}
플래그 트리 × 사용자 속성 × 여러 언어 = 유지보수 재앙.
해결: 플래그는 "기능 추가/제거" 수준으로 얇게. 복잡한 분기는 상위 설계로.
함정 2: 실험 결과를 제대로 분석 안 함
"플래그 켰더니 지표 좋아진 것 같음" → 과학이 아닌 추측. 통계 엄격함 필수.
함정 3: 테스트 환경이 모든 플래그 조합 커버 X
"플래그 A=ON, B=OFF, C=?" 조합마다 다른 동작. 모든 조합 테스트는 불가능 → 최상위 경로 통합 테스트로 한정.
함정 4: 플래그가 캐시됨
CDN이 응답을 캐시했는데 플래그가 바뀌면? 사용자 단위로 캐시 키 포함 또는 짧은 TTL.
함정 5: DB/외부 API에 의존하는 플래그
플래그 평가에 DB 쿼리가 붙으면? 매 요청마다 DB 접근 → 느림. 플래그는 반드시 빠른 로컬 평가.
함정 6: 플래그로 롤백 한 뒤 잊어버림
장애 대응으로 플래그 OFF → 근본 원인 미해결 → 다음에 똑같이 터짐. Ops Toggle은 반드시 후속 포스트모템.
12. 실전 체크리스트 12가지
- 플래그 타입을 명시 — Release/Experiment/Ops/Permission
- 만료일 설정 + 알림 — 좀비 플래그 방지
- Open Feature API 채택 — 벤더 종속 회피
- 플래그를 얇게 — 비즈니스 로직에 깊이 끼지 않게
- 관측성과 통합 — 플래그별 오류율/지연 자동 측정
- 자동 롤백 트리거 — 지표 이탈 시 OFF
- Sticky Bucketing — 같은 사용자 일관 경험
- Holdout Group — 장기 비즈니스 영향 측정
- 정기 Flag Audit — 분기 1회 최소
- Naming Convention — 검색성 확보
- SDK 평가는 로컬 — 네트워크 지연 피하기
- Trunk-based 전환 시 플래그가 기반 — CI/CD와 세트로
다음 글 예고 — Database Migration과 Zero-Downtime 전환
Feature Flag가 "기능 롤아웃"의 기본기라면, Zero-downtime DB Migration은 "스키마 롤아웃"의 기본기다. 둘은 닮았다. 다음 글에서는:
- Expand-Contract 패턴 — 스키마 변경의 정석
- Blue/Green 데이터베이스의 현실
- Dual-Write 함정과 CDC 기반 이주
- 외부 키 제약, 인덱스 추가의 Online 기법
- pt-online-schema-change, gh-ost (GitHub)의 원리
- PostgreSQL CONCURRENTLY 패턴
- Logical Replication으로 메이저 버전 업그레이드
- NoSQL 마이그레이션 전략 (MongoDB, DynamoDB)
- 수십억 행 테이블 실전 사례
"스키마 바꾸면 테이블 락 걸려요 어떻게 해요?" "메이저 업그레이드 다운타임 없이 가능한가요?" 같은 질문의 답이 다음 글에 있다.
"데이터는 코드보다 무겁다. 하지만 그 무게를 분산시킬 방법은 있다."
현재 단락 (1/241)
현대 소프트웨어의 이상한 원칙: