금요일 오후 4시 배포 버튼 앞에서 손이 멈추는 팀은 드물지 않다. 한 B2B 구독 제품 팀은 기능을 메인 브랜치에 합친 뒤 전 사용자에게 즉시 노출하는 방식을 고수했다. 문제는 단순했다. 작은 회귀 하나가 주말 온콜과 긴급 핫픽스로 이어졌고, “안전한 배포”보다 “배포를 미루는 문화”가 자리 잡았다.
이 글은 그 팀이 오픈소스 피처 플래그 플랫폼 언리시(Unleash)를 도입해, 코드 배포와 기능 노출을 분리하기까지 약 6주간 겪은 과정을 문제·시도·결과 순으로 정리한 사례 기록이다. 특정 제품 홍보가 아니라, 도입 초반에 터진 실패와 운영 규칙을 다시 짠 지점에 초점을 둔다.
문제: 배포는 성공인데 기능 노출이 사고인 상태
팀은 Next.js 프론트엔드, Node.js API, PostgreSQL, 백그라운드 워커로 구성된 구독형 SaaS를 운영했다. 풀타임 개발은 다섯 명, 배포는 하루 1~3회, 온콜은 주 단위 로테이션이었다. 표면 지표만 보면 건강했다. CI는 통과하고, 컨테이너 배포도 정상이며, 헬스체크는 초록이었다.
사고가 나던 지점은 다른 층이었다. 신규 결제 플로우, 관리자 권한 화면, 실험용 대시보드 위젯처럼 “일부 사용자에게만 먼저 켜고 싶은” 기능이 메인 합류와 동시에 전 고객에게 열렸다. 스테이징에서 못 잡은 엣지 케이스가 유료 고객 계정에서 터지면, 팀은 전체 롤백과 핫픽스 중 하나를 골라야 했다. 롤백은 그날 합쳐진 다른 수정까지 되돌렸고, 핫픽스는 금요일 밤 코드 리뷰 없는 커밋을 양산했다.
결정적 계기는 권한 체크 누락 버그였다. 관리자 전용 리포트 API가 일반 사용자 토큰으로도 200을 반환했다. 배포 자체는 정상이었고, 에러율도 크게 안 올랐다. 다만 보안 이슈라 기능을 즉시 내려야 했는데, 코드 경로에 킬 스위치가 없어 긴급 배포 한 번이 더 필요했다. 회고 문서의 한 줄이 이후 프로젝트를 열었다. “우리는 배포를 되돌릴 수는 있어도, 기능을 끌 수는 없다.”
시도 1: 장기 브랜치와 환경 변수로 버틴 세 번의 실패
첫 대응은 도구 구매가 아니라 익숙한 우회로였다. 큰 기능은 장기 피처 브랜치에 모아 두고, 완성되면 한 번에 머지했다. 작은 실험은 ENABLE_NEW_CHECKOUT=true 같은 환경 변수로 스테이징과 프로덕션을 갈랐다.
장기 브랜치는 이틀 만에 고통이 됐다. 메인 브랜치의 API 스키마 변경이 피처 브랜치와 충돌했고, 머지 전 리베이스에 반나절이 사라졌다. 리뷰어도 “500줄짜리 한꺼번에 온 PR”을 기피했다. 환경 변수 방식은 더 미묘했다. 프로덕션 전체를 켜거나 끄는 스위치라, 특정 고객사·내부 직원·10% 트래픽만 열 수 없었다. 변수를 바꾸려면 재배포가 필요했고, 재배포 중에는 다른 설정도 같이 바뀌는 공포가 남았다.
세 번째 시도는 “관리자 이메일 화이트리스트”를 코드에 하드코딩하는 것이었다. 내부 테스트에는 쓸 만했지만, 고객사 파일럿 요청이 들어오자 목록 관리가 티켓 지옥이 됐다. 팀은 이 단계에서 한 가지를 합의했다. 필요한 것은 브랜치 전략이 아니라, 이미 배포된 코드 위에서 노출 범위를 런타임에 조절하는 계층이었다.
문제의 출발점 — 코드가 프로덕션에 도착하는 순간과 기능이 사용자에게 열리는 순간이 같았던 상태
시도 2: 자체 토글 테이블 대신 언리시를 고른 이유
후보는 네 갈래였다. 첫째, 플래그 테이블을 직접 만들고 관리 화면을 붙인다. 둘째, 상용 피처 플래그 서비스를 구독한다. 셋째, 오픈소스 언리시·플래그스미스·그로스북 중 하나를 셀프호스팅 또는 클라우드로 쓴다. 넷째, 클라우드 설정 서비스의 원격 설정만으로 버틴다.
자체 구축은 매력적이었지만 기각됐다. 백분율 롤아웃, 스틱키 버킷, 환경별 설정, 감사 로그, 클라이언트 캐시 무효화까지 만들면 본업보다 플래그 플랫폼 유지보수가 커질 가능성이 높았다. 상용 올인원은 권한·실험·분석이 풍부했지만, 초기 좌석·이벤트 과금과 데이터 반출 검토에 시간이 걸렸다. 팀은 “먼저 배포-노출 분리 습관을 만들고, 나중에 실험 플랫폼으로 확장” 쪽을 택했다.
언리시가 선택된 이유는 네 가지였다. (1) 서버·프론트 공식 SDK가 있을 것, (2) 점진 롤아웃·제약 조건(사용자 아이디, 속성)이 문서화되어 있을 것, (3) 오픈소스 코어로 셀프호스팅 선택지가 있을 것, (4) 킬 스위치 개념이 제품 모델의 중심에 있을 것. 초기에는 운영 부담을 줄이려고 호스팅 옵션으로 시작했고, 플래그 이름 규약과 수명 정책을 팀이 문서로 소유하기로 했다. 도구보다 규약이 먼저라는 원칙을 선정 회의 메모 맨 위에 적었다.
1~2주차: SDK를 켠 직후 생긴 세 가지 실수
1주차 목표는 좁았다. API 한 경로와 관리자 화면 한 곳에 플래그를 붙이고, 내부 계정에서만 켠 뒤 동작을 확인한다. 설치 자체는 하루면 끝났다. 문제는 “플래그를 어디에 두느냐”였다.
첫 실수는 프론트와 백엔드에 같은 이름의 플래그를 서로 다른 의미로 쓴 것이었다. 프론트는 UI 버튼을 숨기고, 백엔드는 권한 검사를 건너뛰는 쪽으로 해석했다. 플래그를 끄면 버튼은 사라지지만 API는 여전히 열려 있는 상태가 잠깐 생겼다. 보안 회고에서 바로 지적됐고, 권한·결제·데이터 노출은 서버 플래그만 진실 공급원으로 정했다. 프론트 플래그는 오직 표시·실험용으로 제한했다.
두 번째 실수는 기본값을 “켜짐”으로 둔 것이다. SDK 초기화 실패나 네트워크 지연 시 안전 쪽(꺼짐)으로 가야 하는데, 개발 편의로 기본 허용을 넣었다가 스테이징에서 의도치 않은 기능 노출이 났다. 세 번째는 플래그 이름에 날짜와 담당자 이니셜을 안 넣은 것이다. 두 주 뒤 “newCheckout”이 누구 소유인지, 지워도 되는지 아무도 몰랐다. 2주차 말에 네이밍 규칙을 고정했다. 영역_동작_목적_만료월 형태, 예: billing_checkout_v2_rollout_202607. 만료 월이 없는 플래그는 코드 리뷰에서 질문 대상이 되도록 했다.
도입 초반 — SDK 연결보다 서버 진실 공급원·기본값 꺼짐·이름 규약이 먼저 필요하다는 점이 드러난 구간
3주차: 5%→25%→50% 점진 롤아웃이 바꾼 배포 리듬
3주차에는 실제 고객 트래픽에 점진 노출을 걸었다. 대상은 결제 수단 선택 UI 개편이었다. 코드는 월요일에 이미 프로덕션에 들어가 있었고, 플래그는 내부 직원 속성만 허용한 상태였다. 수요일 오전에 유료 플랜 고객 중 5%로 열고, 에러율·결제 성공률·고객 문의 티켓을 반나절 관찰한 뒤 25%로 올렸다.
예전 방식이라면 이 UI는 “금요일 배포 금지 목록”에 올라갔을 기능이다. 이번에는 목요일에 50%, 다음 주 화요일에 100%로 올리는 일정으로 짰다. 중간에 모바일 사파리에서 버튼 겹침 버그가 나왔을 때, 팀은 핫픽스 브랜치를 급히 만들기 전에 롤아웃을 5%로 되돌렸다. 코드 롤백 없이 영향 범위를 줄인 첫 성공 사례였다.
관찰 지표도 단순화했다. 플래그 단위로 (1) 해당 경로 5xx 비율, (2) 결제 완료 전환, (3) 관련 고객 문의 태그 수를 같은 대시보드에 붙였다. 완벽한 실험 플랫폼은 아니었다. 다만 “켰더니 문의가 늘었는가”를 같은 날 안에 판단할 수 있게 된 것이 배포 회의 분위기를 바꿨다. 금요일 배포 금지는 공식 폐지되지 않았지만, 플래그로 덮인 기능에 한해 목·금 배포가 다시 허용되기 시작했다.
4주차: 킬 스위치가 막아 준 장애, 그리고 스틱키 버킷 함정
4주차에 두 사건이 겹쳤다. 첫째는 긍정적 사건이다. 새 웹훅 재시도 로직이 특정 고객사 페이로드에서 무한 루프에 가까운 재처리를 일으켰다. 워커 큐 깊이가 급증했고, 온콜이 원인을 찾는 동안 플래그로 해당 로직만 즉시 껐다. 전체 워커를 내리거나 이전 이미지를 롤백하지 않고도 확산을 멈췄다. “기능을 끈다”는 문장이 온콜 런북에 처음으로 공식 절차로 들어갔다.
둘째는 교훈이 된 함정이다. 백분율 롤아웃에서 사용자 아이디 기준 스틱키 할당을 빼 먹고, 요청마다 랜덤으로 평가되도록 둔 경로가 있었다. 같은 사용자가 새로고침할 때마다 신·구 UI가 뒤바뀌었다. 문의 티켓 제목은 “화면이 깜빡여요”였다. 원인은 플래그 평가 컨텍스트에 안정적인 사용자 키를 넣지 않은 것이었다. 서버 세션·인증 아이디를 평가 키로 고정한 뒤에야 증상이 사라졌다.
팀은 이 주를 기점으로 체크리스트를 추가했다. 사용자 대면 플래그는 반드시 안정 키, 서버 권한 플래그는 기본 꺼짐, 킬 스위치 후보(결제·권한·데이터 삭제·외부 웹훅)는 이름에 kill 또는 safety를 넣고 대시보드에서 별도 목록으로 본다. 도구가 제공하는 기능을 쓰는 것보다, 팀이 어떤 플래그를 생명줄로 볼지 분류하는 일이 더 중요했다.
4주차 이후 — 롤아웃 비율 조절과 즉시 차단이 온콜 런북에 공식 절차로 들어간 시점
5~6주차: 플래그 수명 관리와 코드 제거 의식
롤아웃이 익숙해지자 다른 문제가 생겼다. 플래그가 쌓였다. 100%로 열린 지 세 주가 지난 토글이 코드와 대시보드에 그대로 남아, 분기 경로가 읽기 어려운 “반쯤 죽은 코드”가 됐다. 신입 온보딩 때 “이 플래그는 왜 있나요?” 질문이 반복됐다.
5주차에 수명 정책을 문서로 고정했다. (1) 생성 시 소유 스쿼드와 제거 예정일을 필수 필드에 적는다. (2) 100% 롤아웃 후 14일이 지나면 제거 이슈를 자동에 가깝게 만든다(실제로는 주간 점검 목록). (3) 영구 플래그는 예외적으로만 허용하며, 킬 스위치·유료 플랜 게이트·규정 대응처럼 이유가 문서에 있어야 한다. (4) 만료된 플래그의 if 분기는 “기능 완료”가 아니라 “부채”로 스프린트에 넣는다.
6주차에는 첫 대규모 정리 PR이 나갔다. 결제 UI 개편 플래그와 죽은 실험 세 개를 제거했다. 코드 리뷰 코멘트는 짧았다. “플래그 없는 경로가 다시 기본 경로가 됐다.” 배포 회의에서 플래그 대시보드를 열어 만료 임박 목록을 확인하는 5분 의식이 정착했다. 언리시 도입의 성공 지표를 “플래그 개수”가 아니라 “만료 대비 제거율”로 바꾼 것도 이 시점이다. 켜 두기만 하고 안 지우는 토글은, 결국 환경 변수 하드코딩과 같은 종류의 부채였다.
결과: 6주 후 달라진 네 가지와 아직 남은 구멍
6주 후 팀이 내부적으로 정리한 변화는 네 가지였다. (1) 코드 배포와 기능 노출의 일정이 분리됐다. (2) 부분 장애 시 전체 롤백 전에 끌 수 있는 스위치가 생겼다. (3) 금요일 배포에 대한 과도한 공포가, 플래그로 덮인 변경에 한해 완화됐다. (4) 실험·파일럿 고객 요청을 핫픽스 브랜치 없이 받을 창구가 생겼다.
한계도 분명했다. 피처 플래그는 데이터베이스 마이그레이션 방향을 되돌리지 않는다. 파괴적 스키마 변경을 플래그로 감싸면 오히려 두 스키마를 동시에 지원하는 지옥이 열린다. 팀은 “확장만 하는 마이그레이션 → 코드 전환(플래그) → 수축 마이그레이션” 순서를 별도 문서로 남겼다. 또한 플래그 평가를 모든 핫 패스에 동기 원격 호출로 붙이면 지연이 생긴다. SDK 캐시·초기화 실패 시 안전 기본값 설계가 성능·안정성 양쪽에서 필수였다.
조직 측면의 한계도 있었다. 제품 매니저가 대시보드에서 플래그를 직접 켜고 싶어 했지만, 권한·결제 관련 토글은 엔지니어링 승인 없이 못 바꾸게 잠갔다. 속도와 안전의 타협이다. 모두가 모든 스위치를 돌리는 구조는, 금요일 핫픽스와 다른 형태의 사고 가능성을 만든다. 이 팀은 “누가 무엇을 켤 수 있는가”를 코드 리뷰만큼 중요하게 취급하기 시작했다.
다른 팀이 가져갈 도입 플레이북
이 사례를 다른 제품 팀에 이식할 때 팀이 권한 순서다. 도구 이름보다 순서가 중요하다.
1단계 — 사고 유형을 적는다. 최근 90일 핫픽스·롤백·금요일 장애를 나열하고, “코드는 이미 있는데 노출만 막고 싶었다” 항목을 표시한다. 그 목록이 첫 플래그 후보다.
2단계 — 서버 진실 공급원 규칙을 먼저 쓴다. 권한·결제·개인정보는 서버에서만 평가한다. 프론트 숨기기는 보조 수단이다.
3단계 — 기본값 꺼짐과 안정 평가 키. SDK 실패 시 안전 쪽, 사용자 대면 롤아웃은 스틱키 키 필수.
4단계 — 이름 규약과 소유자. 영역·목적·만료월. 소유 없는 플래그 생성 금지.
5단계 — 한 기능으로 점진 롤아웃 완주. 5%→25%→50%→100%와 관찰 지표 세 개를 한 사이클 돌려 본다.
6단계 — 킬 스위치 런북. 온콜 문서에 “플래그로 끄는 절차”를 스크린샷 없이 명령·클릭 경로 수준으로 적는다.
7단계 — 수명·제거 의식. 100% 이후 제거 이슈, 주간 만료 점검. 도입 성공을 플래그 수가 아니라 제거율로 본다.
언리시가 아니어도 이 순서는 유지된다. 상용 플래그 서비스나 작은 자체 테이블로 시작해도, “배포와 노출 분리 · 서버 진실 · 수명 관리” 세 축만 맞으면 비슷한 효과를 노릴 수 있다.
이 사례가 특히 참고될 팀
인원 4~15명 규모로 주 수회 이상 배포하는 구독 제품 팀, 아직 기능 노출을 브랜치·환경 변수·핫픽스에만 의존하는 조직에 가장 직접적이다. 파일럿 고객사에만 기능을 열어 달라는 요청이 반복되거나, 보안·결제 회귀 시 전체 롤백 말고는 수단이 없다면 우선순위가 높다.
반대로 배포가 월 1회 미만이고 사용자가 내부 소수뿐이라면, 플래그 플랫폼보다 테스트와 스테이징 품질이 먼저일 수 있다. 이 팀도 평가 키·기본값·서버 검증 규약 없이 도구만 얹었다면 신·구 UI 깜빡임과 권한 구멍만 늘었을 것이다. 또한 스키마 파괴 마이그레이션이 병목인 팀에게 플래그는 해결책이 아니다. 데이터 전환 전략을 먼저 짜야 한다.
스타트업 제품 조직이라면 “실험 속도”만 보고 플래그를 늘리지 않는 편이 좋다. 이 사례의 핵심 이득은 정교한 실험 분석이 아니라, 사고 반경을 코드 롤백 없이 줄이는 운영 능력에 가까웠다. 그 목표가 공유될 때 도구 비용과 규약 비용이 정당화된다.
환경 변수는 대개 프로세스·배포 단위로 전체가 켜지거나 꺼진다. 재배포 없이 특정 사용자·비율만 조절하기 어렵다. 피처 플래그 플랫폼은 이미 배포된 코드 위에서 평가 컨텍스트(사용자 아이디, 속성, 비율)로 노출을 나눈다. 이 사례 팀이 못 했던 “파일럿 고객만”, “5%만”, “지금 즉시 끄기”가 그 차이다.
Q. 프론트엔드에만 플래그를 두면 안 되는 이유는?
UI를 숨겨도 API가 열려 있으면 권한·결제·데이터 노출 사고는 그대로 난다. 이 팀은 초반에 그 구멍을 겪었고, 이후 보안·금전 경로는 서버 평가만 진실로 삼았다. 프론트 플래그는 버튼·카피·레이아웃 실험처럼 실패해도 데이터 경계가 깨지지 않는 범위에 한정하는 편이 안전하다.
Q. 점진 롤아웃 중 버그를 발견하면 코드를 바로 롤백해야 하나?
영향이 플래그로 격리 가능하면 먼저 롤아웃 비율을 낮추거나 끈다. 이 사례의 결제 UI는 코드 롤백 없이 5%로 되돌려 확산을 막았다. 다만 데이터 손상·보안 유출처럼 이미 저장된 부작용이 있으면 플래그를 꺼도 복구 작업은 별도로 필요하다. 킬 스위치는 확산을 멈추는 장치이지, 데이터 수리 도구는 아니다.
Q. 플래그가 쌓여 코드가 더러워지면 어떻게 관리하나?
생성 시 소유자와 만료월을 의무화하고, 100% 이후 제거 이슈를 스프린트 부채로 넣는다. 영구 플래그는 킬 스위치·플랜 게이트처럼 이유를 문서에 남긴 것만 허용한다. 이 팀은 성공 지표를 플래그 개수가 아니라 만료 대비 제거율로 바꿨다. 지우지 않는 토글은 환경 변수 하드코딩과 같은 종류의 기술 부채다.
Q. 데이터베이스 마이그레이션도 플래그로 감싸면 되나?
파괴적 변경을 플래그 하나로 감추는 방식은 비추천이다. 두 스키마를 동시에 이해하는 코드가 오래 남는다. 확장 마이그레이션으로 새 열·테이블을 추가하고, 읽기·쓰기를 플래그로 전환한 뒤, 수축 마이그레이션으로 옛 구조를 제거하는 순서가 이 팀이 남긴 규칙이다. 플래그는 코드 경로 전환용이지, 데이터 모델 부채를 없애 주지 않는다.
Q. 소규모 팀이 상용 서비스와 오픈소스 셀프호스팅 중 무엇을 고르면 되나?
관측·플래그 플랫폼 운영 인력이 없다면 호스팅형으로 시작해 규약(서버 진실, 이름, 수명)에 시간을 쓰는 편이 이 팀 판단과 같다. 데이터 상주·규정 때문에 내부망 강제라면 셀프호스팅을 검토한다. 초기 선택의 핵심은 대시보드 예쁨이 아니라, SDK 안정성·감사 로그·권한 분리·킬 스위치 속도다. 나중에 이벤트 규모와 실험 분석 요구가 커지면 상용 실험 플랫폼으로 확장할 여지를 문서로 남겨 두면 된다.