금요일 오후에 새 기능을 배포했는데 일부 사용자에게만 결제 오류가 발생한다고 생각해 보겠습니다. 코드 전체를 이전 버전으로 되돌리면 새 기능과 무관한 버그 수정까지 사라집니다. 그대로 두고 원인을 찾자니 피해가 커질 수 있습니다. 이때 문제가 된 기능만 즉시 끌 수 있다면 대응 방식이 완전히 달라집니다.
기능 플래그(Feature Flag)는 실행 중인 애플리케이션이 조건에 따라 특정 코드 경로를 선택하게 만드는 제어 장치입니다. 코드는 운영 환경에 먼저 배포하되 내부 사용자에게만 열고, 지표가 안정적이면 1%, 10%, 50%로 대상을 늘린 뒤 전체에 공개할 수 있습니다. 핵심은 배포(deployment)와 출시(release)를 분리하는 것입니다.
하지만 기능 플래그는 단순한 if 문이 아닙니다. 평가 결과가 사용자마다 흔들리거나, 설정 서버 장애로 애플리케이션이 멈추거나, 오래된 플래그가 코드 곳곳에 남으면 오히려 위험이 증가합니다. 안전한 릴리스를 위해 어떤 설계와 운영 규칙이 필요한지 차례대로 살펴보겠습니다.
코드를 배포하는 것과 기능을 공개하는 것은 다르다
전통적인 배포에서는 새 코드가 서버에 올라가는 순간 모든 사용자가 새 기능을 경험합니다. 검증 단위가 스테이징에서 전체 운영 사용자로 한 번에 점프합니다. 반면 기능 플래그를 사용하면 코드는 비활성 상태로 배포한 뒤 운영 데이터와 인프라에서 내부 검증을 진행할 수 있습니다.
이 분리는 배포 빈도를 높이는 데도 도움이 됩니다. 완성되지 않은 큰 기능을 장기간 별도 브랜치에 쌓아 두지 않고, 작은 변경을 기본 브랜치에 합치면서 노출만 막을 수 있습니다. 통합 충돌은 줄고 실제 운영 환경에서의 호환성 문제를 더 일찍 발견할 수 있습니다.
그렇다고 플래그가 잘못된 배포를 모두 구해 주는 것은 아닙니다. 애플리케이션 자체가 시작되지 않거나 공통 코드가 깨진 경우 플래그를 평가할 기회도 없습니다. 버전 롤백, 블루·그린 배포, 카나리 인프라와 기능 플래그는 서로 대체 관계가 아니라 실패 범위를 다르게 줄이는 보완 수단입니다.
플래그는 목적에 따라 수명이 다르다
모든 플래그를 같은 방식으로 관리하면 정리 시점을 놓치기 쉽습니다. 먼저 목적을 구분해야 합니다.
- Release flag: 개발 중인 기능을 숨기고 점진적으로 공개합니다. 전체 출시가 안정화되면 제거해야 하는 단기 플래그입니다.
- Experiment flag: 여러 경험을 비교해 제품 가설을 검증합니다. 실험 종료와 의사결정 뒤 제거합니다.
- Operational flag: 외부 API 호출, 고비용 작업, 새 알고리즘을 장애 시 차단하는 Kill Switch입니다. 장기간 유지할 수 있습니다.
- Permission flag: 요금제나 계약에 따라 기능을 제공하지만, 이것만으로 보안을 처리해서는 안 됩니다. 서버의 권한 검사가 별도로 필요합니다.
생성할 때부터 유형, 소유 팀, 생성 이유, 기본값, 만료 예정일을 기록하면 플래그가 임시 장치인지 영구 운영 제어인지 알 수 있습니다. 이름도 new_feature처럼 모호하게 짓기보다 checkout_v2_release처럼 기능과 목적이 드러나게 정하는 편이 좋습니다.
플래그 평가는 어디에서 해야 할까
클라이언트에서 플래그를 평가하면 화면을 빠르게 바꿀 수 있지만 설정과 숨겨진 기능 이름이 사용자에게 노출될 수 있습니다. 개발자 도구로 값을 조작할 가능성도 있습니다. 할인 적용, 데이터 접근, 관리자 기능처럼 보안과 금전이 걸린 결정은 반드시 서버에서 다시 검증해야 합니다.
서버 평가는 중앙에서 일관된 결정을 내리기 쉽지만 모든 요청마다 원격 플래그 서비스에 접속하면 지연과 장애 의존성이 생깁니다. 일반적으로 SDK가 설정을 메모리에 캐시하고 스트리밍이나 주기적 폴링으로 갱신하며, 요청 경로에서는 로컬 평가를 수행합니다. 중앙 서비스가 잠시 끊겨도 마지막으로 확인한 설정을 사용할 수 있어야 합니다.
프런트엔드와 백엔드가 같은 기능을 판단한다면 사용자에게 전달할 결과를 서버가 결정하는 것이 안전합니다. 화면은 새 결제 UI를 보여주는데 API는 구버전만 처리하는 불일치를 피하려면 플래그 계약과 배포 순서를 함께 관리해야 합니다.
비율 배포는 매 요청마다 주사위를 던지는 것이 아니다
사용자의 10%에게 기능을 공개할 때 요청마다 무작위 숫자를 뽑으면 같은 사람이 새 화면과 옛 화면을 번갈아 보게 됩니다. 장바구니나 작업 흐름이 서로 다른 코드 경로를 오가면 데이터 오류도 만들 수 있습니다. 필요한 것은 무작위성이 아니라 결정적이고 안정적인 분배입니다.
보통 hash(flag_key + user_id)를 일정한 버킷 범위로 변환하고, 지정한 비율 안에 들어오는 사용자를 활성화합니다. 같은 플래그와 사용자는 언제 평가해도 같은 결과를 얻습니다. 비로그인 사용자는 안정적으로 보존되는 익명 식별자를 사용할 수 있지만 쿠키 삭제와 여러 기기 사용을 고려해야 합니다.
플래그별 salt를 사용하지 않으면 여러 실험에서 항상 같은 10%가 선택될 수 있습니다. 반대로 해시 방식이나 버킷 수를 중간에 바꾸면 기존 사용자가 다른 그룹으로 이동합니다. 평가 규칙은 단순한 구현 상세가 아니라 사용자 경험의 일관성을 보장하는 프로토콜로 봐야 합니다.
단계적 롤아웃은 지표를 확인하는 반복 과정이다
새 기능은 먼저 개발팀과 사내 사용자에게 공개하고, 그다음 특정 테스트 고객이나 지역을 거쳐 작은 비율의 실제 사용자로 확장합니다. 각 단계 사이에 충분한 관찰 시간을 두고 기술 지표와 제품 지표를 함께 확인합니다.

기술 지표에는 오류율, 응답 시간, CPU와 메모리, 데이터베이스 부하, 큐 적체가 포함됩니다. 제품 지표로는 결제 완료율, 가입 완료율, 이탈률처럼 기능이 실제 목적을 달성하는지 봅니다. 새 기능이 서버 오류를 만들지 않아도 사용자가 작업을 완료하지 못한다면 성공한 배포가 아닙니다.
확대와 중단 기준을 배포 전에 정하는 것이 중요합니다. 예를 들어 오류율이 기존 대비 0.3%포인트 이상 증가하거나 p95 지연이 20% 나빠지면 자동 중단하도록 설정할 수 있습니다. 사고가 난 뒤 대시보드를 보며 임의로 판단하면 대응이 늦고 팀마다 결론이 달라집니다.
Kill Switch는 정말 빠르게 꺼져야 한다
장애 대응용 플래그는 관리 화면에서 값을 바꾸는 것만으로 충분하지 않습니다. 변경이 모든 인스턴스에 전파되는 시간, 캐시 갱신 주기, 연결이 끊긴 인스턴스의 행동을 알아야 합니다. “즉시 비활성화”라고 생각했는데 5분 캐시 때문에 일부 서버가 계속 실행한다면 Kill Switch 역할을 하지 못합니다.
반대로 플래그 서비스가 불안정할 때 무조건 새 기능을 끄는 것이 안전한지도 업무별로 다릅니다. 검색 추천은 꺼도 되지만 결제 처리 도중 코드 경로가 바뀌면 더 위험할 수 있습니다. 각 플래그에 장애 시 사용할 기본값을 명시하고, 한 요청이나 한 업무 트랜잭션 안에서는 평가 결과를 고정해야 합니다.
긴급 플래그 변경에는 권한과 감사 기록도 필요합니다. 누가 언제 어떤 이유로 값을 바꿨는지, 이전 값이 무엇이었는지 남기고 복구 절차를 제공해야 합니다. 운영자 한 명의 실수로 전체 기능이 꺼질 수 있으므로 중요한 플래그에는 승인이나 변경 범위 제한을 적용할 수 있습니다.
데이터베이스 변경은 플래그보다 천천히 움직인다
애플리케이션 코드는 플래그로 즉시 전환할 수 있지만 스키마와 데이터는 그렇지 않습니다. 새 코드가 새 컬럼만 쓰기 시작한 뒤 플래그를 끄면 구버전 코드가 필요한 데이터를 읽지 못할 수 있습니다. 안전한 변경에는 확장 후 축소하는 단계가 필요합니다.
- 기존 코드와 호환되는 nullable 컬럼이나 새 테이블을 먼저 추가합니다.
- 새 코드가 구·신 구조에 함께 기록하도록 배포합니다.
- 기존 데이터를 백필하고 누락 여부를 검증합니다.
- 플래그로 읽기 경로를 새 구조로 조금씩 전환합니다.
- 안정화 뒤 플래그와 이중 쓰기를 제거합니다.
- 마지막에 사용하지 않는 컬럼과 제약을 정리합니다.
메시지 이벤트와 공개 API도 같은 원칙이 필요합니다. 새 소비자만 이해하는 필드를 필수로 만들기보다 하위 호환 가능한 형태로 먼저 추가해야 합니다. 플래그는 코드 실행을 제어할 뿐 이미 발행된 이벤트와 삭제된 데이터까지 되돌려 주지 않습니다.
플래그가 있는 코드는 어떻게 테스트할까
플래그 하나는 켜짐과 꺼짐 두 경로를 만듭니다. 플래그가 열 개라면 이론상 조합은 1,024개지만 모든 조합을 테스트할 수는 없습니다. 각 플래그의 기본값 경로, 활성 경로, 중요한 플래그 간 상호작용을 위험 기반으로 선택해야 합니다.
단위 테스트에서는 평가 서비스를 직접 호출하기보다 인터페이스로 감싸 결과를 명시적으로 주입하는 편이 안정적입니다. 통합 테스트에서는 플래그가 꺼진 현재 운영 상태와 앞으로 기본이 될 활성 상태를 모두 실행합니다. 테스트 환경의 전역 플래그 값을 여러 테스트가 공유하면 순서에 따라 실패할 수 있으므로 테스트별 컨텍스트를 격리해야 합니다.
플래그 설정 자체도 코드처럼 검증할 수 있습니다. 존재하지 않는 대상 속성, 100%를 넘는 분배, 소유자 없는 플래그, 지난 만료일을 CI에서 탐지합니다. 운영 변경은 작은 설정 파일이라도 제품 행동을 바꾸므로 리뷰와 이력을 남길 가치가 있습니다.
관측 데이터에는 플래그 결과가 포함되어야 한다
오류 로그에 어떤 플래그 조합이 적용됐는지 없으면 새 기능 사용자에게만 발생한 장애를 찾기 어렵습니다. 요청 추적과 주요 메트릭에 플래그 버전이나 변형 값을 제한적으로 포함하면 비교가 쉬워집니다. 다만 사용자 ID나 수많은 플래그 값을 메트릭 라벨에 그대로 넣으면 카디널리티가 폭발하므로 주의해야 합니다.
핵심 플래그별로 활성·비활성 그룹의 오류율과 지연 시간을 나눠 보고, 변경 시각을 배포 타임라인에 표시합니다. 플래그 평가 실패율, 설정의 마지막 갱신 시각, 기본값으로 대체된 횟수도 운영 지표가 됩니다. 기능을 끈 뒤 장애가 실제로 줄었는지까지 확인해야 대응이 끝납니다.
가장 위험한 것은 끝나지 않은 플래그다
출시가 끝난 Release flag를 남겨 두면 코드에는 영원히 두 갈래 경로가 존재합니다. 개발자는 현재 어느 쪽이 정상인지 알기 어려워지고, 오래된 경로는 테스트되지 않은 채 보안 패치와 데이터 모델 변경에서 빠질 수 있습니다. 이를 기능 플래그 부채라고 부릅니다.

좋은 운영 방식은 생성 시 정리 티켓까지 만들고 만료일이 가까워지면 소유자에게 알리는 것입니다. 전체 공개가 안정화되면 활성 경로를 기본 코드로 남기고 비활성 경로, 테스트, 설정을 함께 삭제합니다. 제거 자체도 코드 변경이므로 단계적으로 배포하고 관측합니다.
운영용 Kill Switch처럼 오래 유지할 플래그는 주기적으로 실제 작동 여부를 훈련해야 합니다. 수년간 한 번도 꺼 보지 않은 스위치는 막상 장애 때 오래된 코드 경로를 실행해 더 큰 문제를 만들 수 있습니다.
직접 만들지 서비스를 도입할지 판단하는 기준
플래그 몇 개를 환경 변수로 관리하는 것은 쉽지만, 다수 인스턴스에 실시간 전파하고 사용자 속성별 규칙, 감사 로그, 승인, SDK 캐시, 통계까지 제공하려면 독립 시스템이 됩니다. 팀과 서비스가 작고 서버 측 단순 On/Off만 필요하다면 데이터베이스와 캐시로 시작할 수 있습니다.
여러 플랫폼에서 같은 규칙을 평가하고 비개발자도 안전하게 대상을 조정해야 한다면 전용 플랫폼을 검토할 만합니다. 이때 기능 목록보다 설정 서비스 장애 시 SDK 동작, 데이터 저장 지역, 개인정보 전송 범위, 내보내기와 공급자 종속성을 먼저 봐야 합니다. 평가 컨텍스트에 이메일 같은 개인정보를 무심코 전달하지 않는 것도 중요합니다.
실무 도입 체크리스트
- 플래그마다 유형, 소유자, 기본값, 만료일과 비상 연락처가 있는가?
- 같은 사용자가 항상 같은 그룹에 배정되는가?
- 설정 서비스 장애 시 마지막 값과 안전 기본값이 정의되어 있는가?
- 확대·중단 기준과 관찰 시간이 배포 전에 합의되어 있는가?
- 보안·결제 결정은 서버에서 별도로 검증하는가?
- 한 요청과 업무 트랜잭션 안에서 평가 결과가 변하지 않는가?
- 로그와 지표로 활성·비활성 그룹을 비교할 수 있는가?
- DB·이벤트 변경이 구버전과 호환되고 되돌릴 수 있는가?
- 전체 출시 후 플래그와 죽은 코드 경로를 제거하는 절차가 있는가?
안전한 배포는 작은 결정의 연속이다
기능 플래그의 진짜 가치는 버튼 하나로 기능을 켜고 끄는 데만 있지 않습니다. 큰 출시를 내부 사용자, 작은 고객군, 일부 트래픽, 전체 사용자라는 검증 가능한 단계로 나누고 각 단계에서 멈출 선택권을 제공하는 데 있습니다.
핵심 원칙은 간단합니다. 결정은 일관되게 평가하고, 확대는 지표를 근거로 진행하며, 비상 경로는 실제로 검증하고, 임시 플래그는 반드시 제거해야 합니다. 이 네 가지가 지켜질 때 기능 플래그는 복잡성을 늘리는 if 문이 아니라 배포 위험을 통제하는 제품 운영 도구가 됩니다.