Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Flipper 기능 플래그로 Rails에 새 기능 안전하게 배포하기

한번 상상해 보세요. 여러분은 Rails 개발자이고, 지난 며칠간 모두가 애타게 기다리던 멋진 기능을 개발했습니다. 규모도 크고 복잡하지만 철저한 테스트를 거쳤기 때문에 모든 것이 정상적으로 동작할 것이라 확신합니다. 마감 기한도 다가오니 배포를 진행합니다. 그런데 순식간에 모든 것이 폭주하기 시작합니다.

새 기능이 일부 사용자에게는 앱 전체를 망가뜨리고 만 것입니다. 원인을 파악하기도 어렵습니다. 테스트 중에는 어떠한 버그도 발견되지 않았는데 말이죠. 변경 사항을 롤백하지만 이미 피해는 발생한 뒤입니다. 고객들은 불만스러워하고, 앞으로 한동안은 손상 복구와 원인 분석에 매달려야 할 것입니다.

문제의 핵심은 아무리 많은 테스트를 거치더라도 복잡한 변경 사항을 한 번에 릴리스하는 것은 본질적으로 위험하다는 점입니다. 이 기능을 소수의 사용자에게 먼저 점진적으로 공개했다면 얼마나 좋았을까요? 문제가 처음 나타나는 즉시 해당 기능을 끌 수 있었다면 더욱 좋았겠죠.

바로 이 지점에서 기능 플래그(feature flag)가 빛을 발합니다. 이 글에서는 Rails에서 기능 플래그가 어떻게 작동하는지 살펴보고, Flipper 젬(gem)을 활용해 실제로 구현하는 방법까지 자세히 알아보겠습니다.

Ruby on Rails에서 기능 플래그란 무엇인가?

기능 플래그란 애플리케이션이 실행되는 도중에 특정 기능을 켜거나 끌 수 있게 해주는 메커니즘입니다. 플래그가 활성화되어 있으면 해당 기능이 사용되고, 그렇지 않으면 사용되지 않습니다. 의사 코드(pseudocode)로 표현하면 대략 다음과 같은 형태입니다.


'기능 플래그'라는 이름은 다소 오해를 불러일 수 있습니다. 런타임에 애플리케이션의 동작을 제어하는 것은 '기능'에만 국한되지 않으며, 프로그램 내의 어떤 부분에든 적용할 수 있습니다. 예를 들어 성능 최적화 실험이나 A/B 테스트에도 기능 플래그를 활용할 수 있습니다. 일반적인 조건문과 기능 플래그의 가장 큰 차이점은 런타임에 수정할 수 있다는 점입니다.

설명을 위해 환경 변수(environment variable)를 이용한 간단한 기능 플래깅 메커니즘을 가정해 보겠습니다.


환경 변수의 값을 변경하기만 하면 애플리케이션의 특정 부분을 자유롭게 켜고 끌 수 있습니다. 어떤 부분이든 상관없습니다!

기능이 비활성화된 경우 UI의 일부를 숨길 수도 있습니다. 또한 코드의 일부를 교체(switch)할 수도 있는데, 최근 리팩터링 결과가 마음에 들지 않을 때 매우 유용합니다. 개인적으로 가장 좋아하는 활용법 중 하나는 라우팅 제약 조건(routing constraints)을 사용해 특정 라우트 전체를 숨기는 것입니다.



이처럼 기능 플래그에는 다양한 활용처가 있습니다. 하지만 이러한 단순한 구현에는 한 가지 치명적인 약점이 있습니다. 바로 모든 사용자에게 기능이 일괄적으로 켜지거나 꺼진다는 점입니다.

특정 사용자나 사용자 그룹에게만, 혹은 일부 시간에만 기능을 활성화하고 싶다면 어떻게 해야 할까요? 이제 Flipper를 살펴볼 차례입니다.

Flipper 젬으로 기능 플래그 사용하기

Flipper는 Rails에서 기능 플래그와 다양한 토글 방식을 손쉽게 사용할 수 있게 해주는 젬입니다. 매우 모듈화되어 있어서 메인 젬 외에도 스토리지 어댑터(storage adapter)를 직접 선택해야 합니다. 여기서는 우선 ActiveRecord 어댑터를 사용해 보겠습니다.

Gemfile에 다음 줄을 추가하고 bundle install을 실행하세요.


ActiveRecord 어댑터를 사용하면 Flipper는 기능 플래그 정보를 데이터베이스에 저장하기 때문에 새로운 데이터베이스 테이블 생성이 필요합니다. Flipper는 필요한 마이그레이션 파일을 생성해 주는 편리한 제너레이터를 함께 제공합니다.


Flipper는 앞서 살펴본 단순한 구현과 같은 방식으로 사용할 수 있습니다.


가장 큰 차이점은 더 이상 환경 변수를 수정할 필요 없이 Flipper가 제공하는 사용자 인터페이스(UI)로 기능을 토글할 수 있다는 점입니다. Flipper UI 젬을 추가하고 설치해 보겠습니다.


라우트 파일을 수정해 Flipper UI를 마운트(mount)합니다.


애플리케이션을 실행하고 Flipper UI에 접속하면 사용 가능한 모든 기능과 그 상태를 한눈에 확인할 수 있습니다. 바로 my_awesome_feature라는 기능을 하나 만들어 보세요.

Flipper 기능 플래그로 Rails에 새 기능 안전하게 배포하기

Flipper의 다양한 기능 토글

Flipper UI에서는 단순히 기능을 켜고 끄는 것 이상의 작업이 가능하다는 것을 눈치채셨을 겁니다. Flipper는 특정 사용자 집합에게만, 심지어 일정 비율의 요청에만 기능을 활성화할 수도 있습니다. 기능 플래그는 총 5가지 메커니즘 중 하나를 골라 설정할 수 있습니다.

그중 하나는 전역 on/off 스위치 역할을 하는 boolean 토글입니다. 나머지 네 가지가 훨씬 흥미로운데요, 바로 그룹(groups), 액터(actors), 액터 비율(percentage of actors), 시간 비율(percentage of time)입니다.

그룹(Groups)

특정 사용자 그룹에게만 기능을 활성화하려면 그룹 토글을 사용합니다. 파일이 없다면 config/initializers/flipper.rb를 생성하고, 예를 들어 유료 사용자(paid users) 같은 새로운 그룹을 등록하는 코드를 추가합니다.


이 토글을 사용하려면 Flipper 호출 시 사용자 — 문서에서는 '액터(actor)'라고 부릅니다 — 와 기능 이름을 함께 전달하도록 수정합니다.


Flipper는 이니셜라이저에서 정의한 블록을 실행해 해당 사용자에게 기능이 활성화되어 있는지 판단합니다. 위 예시에서는 paid? 메서드를 호출하게 됩니다. 이제 이 그룹을 Flipper UI에서도 사용할 수 있습니다. 'Add a Group' 버튼을 클릭하고 등록한 그룹을 선택하세요.

Flipper 기능 플래그로 Rails에 새 기능 안전하게 배포하기

그룹이 반드시 유료 사용자일 필요는 없습니다. 팀 멤버나 베타 프로그램에 가입한 사용자일 수도 있습니다. 그룹 토글은 사용자의 일부에게만 기능을 공개하고 싶을 때 유용합니다. 베타 릴리스나 사내 릴리스를 떠올려 보면 쉽게 이해됩니다.

액터(Actors)

때로는 단 한 명의 개인에게만 기능을 공개하고 싶을 때가 있습니다. 이럴 때 액터 토글이 적합합니다.

이 메커니즘을 사용하기 위해 이니셜라이저를 변경할 필요는 없습니다. 다만 액터 모델 — 대부분의 경우 사용자(User) 모델입니다 — 이 flipper_id를 구현하도록 해야 합니다. Flipper::Identifier를 include하면 이를 손쉽게 처리할 수 있습니다.



이 ID를 활용하면 개별 사용자 단위로 기능을 활성화할 수 있습니다.

Flipper 기능 플래그로 Rails에 새 기능 안전하게 배포하기

이 토글은 특정 개인에게 기능을 미리 보여줄 때 특히 유용합니다. 예를 들어 파트너가 일반 공개 전에 기능을 사용할 수 있도록 특정 테스트 계정에만 접근 권한을 부여하는 경우를 생각해 보세요.

액터 비율(Percentage of Actors)

기능 사용률을 서서히 늘려 가고 싶다면 액터 비율 토글이 정답입니다. 성능 최적화를 구현했지만 프로덕션 환경에서 어떻게 동작할지 확신이 서지 않는다고 가정해 봅시다. 먼저 소수의 사용자에게 배포한 뒤, 애플리케이션을 모니터링하면서 점진적으로 비율을 높여 가는 것입니다.

Flipper 기능 플래그로 Rails에 새 기능 안전하게 배포하기

상황이 악화되더라도 언제든 비율을 0으로 되돌리면 됩니다.

시간 비율(Percentage of Time)

마지막으로, 요청(request)의 일정 비율에 대해서만 기능을 활성화할 수도 있습니다. 이 토글을 사용할 때는 액터를 전달할 필요가 없습니다.


저는 두 가지 서로 다른 구현체의 성능을 비교할 때 이 방식을 즐겨 사용합니다. 다만 이 토글은 무작위(random)로 동작한다는 점에 유의하세요. 동일한 사용자라도 요청마다 다른 동작을 경험하게 됩니다.

Flipper 고급 설정

앞서 ActiveRecord 어댑터를 사용해 기능 플래그 정보를 데이터베이스에 저장하는 방법을 살펴봤습니다. 하지만 필요에 따라 다른 어댑터를 사용하는 것이 좋을 수도 있습니다.

대체로 스토리지 어댑터를 교체하는 작업은 해당 젬을 설치하고 Flipper 설정을 맞춰 업데이트하는 것만으로 충분합니다. 예를 들어 Redis 어댑터를 사용하려면 Gemfile에 flipper-redis 젬을 추가하고 Flipper 이니셜라이저를 수정하면 됩니다.



스토리지 어댑터 설정 외에도 Flipper에서 조정할 수 있는 요소는 매우 다양합니다.

기능 플래그를 추가하면 어느 정도의 성능 저하가 발생하기 때문에 일반적으로 메모이제이션(memoization)과 캐싱(caching)을 적용하는 것이 좋습니다. 또한 Flipper UI에 대한 접근을 인증된 사용자로 제한하고 싶을 가능성도 높습니다. 이 주제들은 이 글의 범위를 벗어나지만, 더 깊이 알아보고 싶다면 Flipper 공식 문서를 참고하시기 바랍니다.

Rails에서 기능 플래그 사용 시 주의사항

기능 플래그는 훌륭한 도구입니다. 하지만 다른 많은 것들과 마찬가지로 트레이드오프(trade-off)도 존재합니다.

기능 플래그를 추가하고 관리하는 일은 조직적인 오버헤드를 발생시킵니다. 플래그를 사용해 숨겨진 기능을 릴리스하고 배포하는 것은 '일반적인' 코드를 배포하는 것만큼 단순하지 않습니다. 물론 잊지 말고 기능을 켜야 하니까요! 보통 이것은 크게 어렵지 않지만, 더 이상 필요 없는 기능 플래그를 정리(cleanup)하는 작업은 만만치 않을 수 있습니다.

코드에 기능 플래그를 추가할 때마다 조건문이 하나씩 늘어납니다. 주의하지 않으면 코드가 금세 조건문으로 어지럽혀집니다. 어떤 플래그를 언제, 왜 만들었는지 추적하는 것도 쉽지 않습니다. 아쉽게도 Flipper UI 자체는 기능 플래그 관리에 큰 도움이 되지 못합니다.

또한 기능 플래그 사용 시 약간의 성능 페널티가 발생한다는 점도 인지해야 합니다. 이를 완화하는 방법들이 있긴 하지만, Flipper를 잘못 설정하거나 잘못 사용하면 눈에 띄는 영향을 줄 수 있습니다.

마무리: Ruby on Rails에서 기능 플래그 시작하기

이번 글에서는 기능 플래그가 어떻게 더 안심하고 릴리스할 수 있도록 도와주는지 살펴보고, 그 동작 원리를 이해했으며, Flipper 젬으로 기능 플래그를 시작하는 방법까지 확인했습니다.

기능 플래그가 유용한 상황은 정말 많습니다. 사용 사례에 따라 boolean, 그룹, 액터, 액터 비율, 요청 비율 중 하나의 토글을 선택하면 됩니다.

기능 플래그를 다루는 데는 몇 가지 주의점이 있고 익숙해지는 데 시간이 걸릴 수 있지만, 그럼에도 불구하고 여러분의 도구함에 꼭 갖추고 싶은 강력한 도구임은 분명합니다.

그럼 다음 시간까지, 행복한 코딩 되세요!

P.S. Ruby Magic 포스트가 발행되는 대로 바로 읽고 싶으시다면 Ruby Magic 뉴스레터를 구독하세요. 어떤 글도 놓치지 않을 수 있습니다!