우리 모두 훈련을 알고 있죠? 코드 변경 작업을 마치고 [희망적으로] 로컬 컴퓨터에서 테스트한 후 변경 사항을 주기의 다음 단계로 진행합니다. 로컬 테스트는 매우 편향되어 있으며 이상적으로는 보다 안정적인 환경에서 변경 사항을 검증하고 싶습니다(또한 변경 사항을 구현한 엔지니어의 관점만 따르지 않습니다).
다음 단계는 매우 자연스러운 것 같습니다. 변경 사항을 안정적인 스테이징 환경으로 푸시하고 파트너(QA, PM, 기타 엔지니어)가 변경 사항을 이동하기 전에 검증을 돕도록 하십시오. 그런 다음 프로덕션 단계로 진행하기에 충분하다고 믿을 때까지 버그 수정과 재검증이 이어집니다. 엄청난!
그러나 대부분의 상황에서는 이런 일이 발생하지 않습니다. 다양한 이유가 있을 수 있지만 이유에 관계없이 변경 사항을 충분히 테스트하거나 검증하기 전에 프로덕션 서버에 변경 사항을 승격해야 하는 경우가 많습니다.
문제는... 뭔가 고장나면 어쩌지? 실제로 문제를 더 일찍 감지하는 방법은 무엇입니까? 좋은 소식: 프로덕션 환경에서 테스트하고 검증하는 것이 귀하와 귀하의 회사를 위한 안전한 관행일 뿐만 아니라 좋은 아이디어가 될 수도 있는 몇 가지 도구와 관행을 채택하는 것이 가능합니다.
프로덕션 테스트에 들어가기 전에 측정 항목에 대해 이야기해야 합니다. 우리가 제공하는 변경 사항이 원하는 효과를 생성하는지, 원치 않는 부작용을 일으키지 않는지, 제품이 여전히 안정적인지 등을 검증하는 데 측정 항목이 필요합니다. -측정 기준을 확립했지만 변경 사항을 적용할 때 기본적으로 눈이 멀었습니다. 기사의 많은 주제에서 측정항목을 참조할 것이므로 염두에 두어야 할 두 가지 유형의 측정항목을 살펴보겠습니다.
KPI, 목표, 사용자 행동과 같은 비즈니스 관련 지표는 변경 사항을 구현한 후에 모니터링하여 영향을 평가해야 합니다. 변경하기 전에 영향을 받을 것으로 예상되는 측정항목을 식별하세요. 변경되어서는 안 되는 항목을 나타내는 가드레일 측정항목도 마찬가지로 중요합니다. 이러한 가드레일의 예상치 못한 변화는 새로운 변경에 문제가 있음을 의미할 수 있으므로 검토가 필요합니다.
비즈니스 지표가 정의되면 기술 지표를 이해하는 것도 중요합니다. 이는 시간이 지남에 따라 변경 사항이 도입될 때 시스템을 정상 상태로 유지하는 데 필수적입니다. 여기서는 시스템 안정성, 오류율, 볼륨, 기계 용량 제약 등에 대해 이야기하고 있습니다.
좋은 기술 지표는 비즈니스 지표에서 관찰된 문제를 설명하거나 회귀의 근본 원인을 빠르게 찾는 데도 유용합니다. 예를 들어, 마지막 버전 출시 이후 특정 기능에 대한 사용자의 참여도가 훨씬 낮아지는 것을 관찰했다고 가정해 보겠습니다. 요청 시간 초과 또는 오류율이 증가하면 어떤 서비스/엔드포인트가 문제를 일으키는지 빠르게 확인할 수 있습니다.
우리는 비즈니스 및 기술 지표가 잘 정의되어 있습니다. 좋습니다! 이제 우리는 그들을 모니터링해야 합니다. 이를 수행하는 방법은 다양하지만 일반적인 첫 번째 단계는 시간 경과에 따른 측정항목을 추적하는 대시보드를 구축하여 비정상적인 급증을 쉽게 발견하는 것입니다. 대시보드에서 특히 비즈니스와 관련이 있을 수 있는 특정 세그먼트를 기반으로 데이터를 빠르게 필터링할 수 있다면 더욱 좋습니다. 대시보드를 적극적으로 모니터링하는 것은 새로운 변경 사항이 시스템에 미치는 영향을 신속하게 시각화하는 좋은 방법입니다. 일부 회사에서는 적극적인 모니터링을 매우 중요하게 생각하여 가능한 한 빨리 문제를 감지하고 해결하기 위해 연중무휴 모니터링 교대근무를 실시하기도 합니다.
지표를 모니터링하는 또 다른 좋은 방법은 자동 감지 및 경고를 이용하는 것입니다. 주요 지표의 경우 경고는 문제가 있을 때 실시간 알림을 제공할 수 있습니다. 기능 출시를 시작하고 프로세스가 시작된 지 몇 분 후에 오류율이 특정 임계값 이상으로 증가하고 있다는 경고를 받았다고 가정해 보겠습니다. 이러한 조기 알림을 통해 프로덕션에서 변경 사항이 더 이상 전파되는 것을 방지하고 많은 문제를 피할 수 있습니다!
마지막으로, 어떤 상황에서 얼마나 많은 정보가 필요한지 염두에 두는 것이 중요합니다. 대시보드는 제품과 시스템 성능을 시각적으로 보여 주는 데 매우 유용하지만, 1,000개의 다양한 차트를 추가하면 명확성보다는 혼란이 더 커집니다. 마찬가지로 하루에 1,000개의 알림을 받으면 이를 조사하고 조치를 취하는 것이 불가능하며 결국 무시됩니다.
측정항목이 정의되고 모니터링이 완료되었습니다. 훌륭합니다! 이제 문제를 방지하고 문제를 조기에 감지하며 생산에 미치는 영향을 최소화하는 데 도움이 되는 몇 가지 도구와 전략을 살펴보겠습니다. 프로덕션 환경이 어떻게 설정되어 있는지에 따라 이들 중 일부는 다른 것보다 구현하기가 더 어려울 수 있으며 결합해도 별 의미가 없을 수도 있습니다. 그러나 여기에 있는 각 항목은 우리가 안전하고 안정적인 생산 환경에 더 가까워지는 데 도움이 될 수 있습니다.
프로젝트가 제대로 진행되지 않을 때 종종 제외되는 자동화된 테스트는 개발을 가속화하고 생산을 보다 안전하고 빠르게 변경할 수 있습니다. 문제를 조기에 발견할수록 문제를 더 빨리 해결할 수 있으므로 프로세스에 소요되는 전체 시간이 줄어듭니다. 변경 사항을 되돌리고 수정하고 다시 푸시하는 과정은 일반적으로 매우 스트레스가 많으며 귀중한 시간을 빼앗길 수 있습니다.
단위, 통합 및 엔드투엔드 테스트를 통해 100% 테스트 적용 범위를 목표로 하는 것은 대부분의 프로젝트에 이상적일 수 있습니다. 대신 노력과 이익을 기준으로 테스트의 우선순위를 정하세요. 측정항목을 통해 이를 안내할 수 있습니다. 핵심 비즈니스 기능을 다루는 것이 영향력이 덜한 틈새 기능보다 더 중요할 것입니다. 그렇죠? 핵심 기능부터 시작하여 시스템이 발전함에 따라 확장하세요.
프로덕션에 게시 프로세스에는 프로덕션에 배포하기 전에 테스트 도구 모음을 실행하는 작업이 포함되어야 합니다. 테스트가 실패하면 게시를 일시 중지하여 프로덕션 문제를 방지해야 합니다. 다음 날 완전히 오작동하는 것을 발견하는 것보다 기능 출시를 연기하는 것이 더 좋습니다.
Dogfooding은 최종 사용자에게 도달하기 전에 내부 테스트를 위해 기능을 출시하는 프로세스입니다. Dogfood 중에 이 기능은 프로덕션 환경에서 사용할 수 있지만 내부 사용자(직원, 팀 구성원 등)에게만 제공됩니다. 이런 방식으로 외부 사용자에게 영향을 주지 않고 실제 프로덕션 데이터를 사용하여 새로운 기능이 예상대로 작동하는지 테스트하고 검증할 수 있습니다.
Dogfooding에는 다양한 전략이 있습니다. 간략한 개요를 위해 두 개의 더 큰 버킷으로 그룹화할 수 있습니다.
Canary 릴리스는 프로덕션의 변경 사항을 모든 서버에 한 번에 롤아웃하는 대신 변경 사항을 작은 하위 집합에 적용하고 한동안 모니터링하는 릴리스 프로세스입니다. 변경 사항이 안정적인지 확인한 후에만 프로덕션 환경으로 푸시됩니다.
이는 새로운 기능과 위험한 변경 사항을 테스트하여 프로덕션에서 문제가 발생할 가능성을 줄이는 가장 강력한 도구 중 하나입니다. 사용자 그룹에 대한 변경 사항을 테스트함으로써 문제가 감지되면 출시 프로세스를 중지/되돌려 대부분의 사용자에게 미치는 영향을 피할 수 있습니다.
DevOps 방식인 Blue Green 배포는 두 개의 서버 클러스터(Blue 및 Green)를 사용하고 이들 사이에서 프로덕션 트래픽을 전환하여 가동 중지 시간을 방지하는 것을 목표로 합니다. 기능 출시 중에 변경 사항은 한 세트(녹색)에 게시되고 다른 세트(파란색)는 변경되지 않은 상태로 유지됩니다. 문제가 발생하면 이전 버전에서 계속 실행되었으므로 트래픽을 블루 서버로 신속하게 되돌릴 수 있습니다.
Blue Green 배포는 앞서 논의한 Canary 릴리스와 종종 대조됩니다. 이 논의의 세부 사항을 다루지는 않겠지만 작업에 더 적합한 도구를 결정할 때 도움이 되도록 이를 언급하는 것이 중요합니다.
킬 스위치는 소프트웨어 엔지니어링의 맥락에서 시작된 것이 아니며, 그 용도를 이해하는 가장 좋은 방법은 원래 의도와 설계를 되돌아보는 것입니다. 산업에서 사용되는 기계에서 킬 스위치는 매우 간단한 상호 작용(일반적으로 간단한 버튼 또는 켜기/끄기 스위치)을 통해 가능한 한 빨리 스위치를 차단하는 안전 메커니즘입니다. 이는 비상 상황을 위해 존재하며 한 번의 사고(예: 기계 오작동)가 더 심각한 사고(부상 또는 사망)를 초래하는 것을 방지합니다.
소프트웨어 엔지니어링에서 킬 스위치는 비슷한 목적으로 사용됩니다. 시스템을 계속 실행하기 위해 특정 기능을 잃거나 죽이는 것을 허용합니다. 구현은 높은 수준에서 조건 확인(아래 코드 조각 참조)이며 일반적으로 특정 변경 사항이나 기능의 진입점에 추가됩니다.
if (feature_is_enabled('feature_x')) {xNewBehaviour();} else {xOldBehaviour();}
예를 들어 새로운 타사 API로의 마이그레이션을 제공한다고 가정해 보겠습니다. 테스트에서는 모든 것이 괜찮고 Canary 릴리스에서는 안정적이며 변경 사항은 프로덕션에 100% 적용됩니다. 얼마 후, 새 API가 볼륨 문제로 인해 어려움을 겪기 시작하고 요청이 실패하기 시작합니다(기술적 지표를 기억하십니까?). 킬 스위치가 있기 때문에 API 요청을 이전 API로 즉시 되돌릴 수 있으며 이전 버전으로 되돌리거나 신속하게 핫픽스를 제공할 필요가 없습니다.
기술적으로 말하면 킬 스위치는 실제로 기능 토글(기능 플래그라고도 함)의 특정 사용 사례입니다. 주제를 다루면서 기능 토글의 또 다른 큰 이점인 트렁크 기반 개발 활성화에 대해 언급할 가치가 있습니다. 기능 토글 덕분에 새 코드가 불완전하거나 아직 테스트되지 않은 경우에도 안전하게 프로덕션에 푸시할 수 있습니다.
위에 예시된 코드를 보면 애플리케이션에 이전 동작과 새 동작이 동시에 존재하는 이것이 실제로 좋은 패턴인지 궁금해하는 사람이 있을 것입니다. 나는 이것이 우리가 원하는 코드베이스의 최종 상태가 아닐 가능성이 높다는 점에 동의합니다. 그렇지 않으면 모든 단일 코드 조각이 if/else 절로 둘러싸여 코드를 즉시 읽을 수 없게 됩니다.
그러나 항상 성급하게 이전 동작을 삭제해서는 안 됩니다. 예, 코드 사용이 중단되자마자 코드를 정리하고 기술적 부채를 피하고 싶은 유혹이 매우 큽니다. 하지만 기능 전환을 통해 한동안 그대로 두는 것도 괜찮습니다. 때로는 새로운 기능이 안정화될 때까지 시간이 걸릴 수 있으며, 짧은 시간이라도 해당 기능으로 되돌려야 할 경우를 대비해 백업 옵션을 갖는 것이 안전한 메커니즘입니다.
각 릴리스의 수명 주기는 다르므로 오래된 코드를 제거하기에 좋은 시기가 언제인지 추적하는 것이 좋습니다. 코드를 깔끔하게 유지하고 유지 관리 오버헤드를 줄이면 코드에서 기능을 비활성화했지만 비활성화된 지 얼마나 되었는지를 고려하면 기능이 손상될 수 있는 반대 상황을 피할 수 있습니다.
보다 안전한 변경을 구현하기 위해 제가 가장 좋아하는 기술 중 하나는 섀도우 테스트 또는 섀도우 모드로 알려져 있습니다. 결과를 비교하기 위해 이전 동작과 새 동작을 모두 실행하지만 적용 가능한 경우 새로운 동작 부작용 중 일부를 비활성화하는 것으로 구성됩니다. 이 간단한 예를 살펴보겠습니다.
int sum(int a, int b) {int currentResult = currentMathLib.sum(a, b);int newResult = newMathLib.sum(a, b);logDivergences(a, b, currentResult, newResult);return currentResult;}void logSumDivergences(int a, int b, int currentResult, int newResult) {if (currentResult != newResult) {logger.warn( 'Divergence detected when executing {0} + {1}: {2} != {3}',a, b, currentResult, newResult);}}
두 합계 연산이 모두 실행되지만 새 연산은 차이를 비교하고 기록하는 데만 사용됩니다. 이 기술은 복잡한 시스템 변경을 모니터링하는 데 특히 유용하며 이전 동작과 새 동작 간에 어느 정도 패리티가 있을 것으로 기대합니다. 또 다른 좋은 사용 사례는 우리가 잘 알지 못하는 제품을 변경해야 할 때, 또는 의도한 변경으로 인해 어떤 극단적인 경우가 영향을 받을 수 있는지 잘 모르는 경우입니다.
보다 복잡한 시나리오에서는 섀도우 테스트를 활성화하기 전에 일부 부작용을 비활성화해야 할 수도 있습니다. 예를 들어, 사용자를 등록하고 DB에 저장하여 사용자 ID를 반환하는 새로운 백엔드 API를 구현한다고 가정해 보겠습니다. 전체 프로세스를 실행하기 위해 섀도우 DB를 배치할 수도 있지만 "등록 성공" 이메일을 각 백엔드 API에 하나씩 두 번 보내는 것은 확실히 좋은 생각이 아닙니다. 또한 동일한 예에서는 반환된 사용자 ID를 단순히 비교하는 것이 그다지 유용하지 않기 때문에 더 깊은 비교 논리가 필요합니다.
마지막으로, 무엇을 모니터링하고 테스트해야 하는지, 그리고 패리티가 달성되지 않을 경우 어떤 기준을 적용할지 이해하는 것이 중요합니다. 일부 중요한 시나리오에서는 결과가 정확히 동일할 때까지 섀도우 테스트를 반복해야 합니다. 다른 경우에는 새로운 구현이 손실보다 큰 추가 이점을 제공하는 경우 어느 정도의 차이가 있어도 괜찮을 수 있습니다.
강력한 보호 장치가 있어도 시스템이 불안정해질 수 있습니다. 이런 일이 발생하면 우리는 적절한 수준의 세부사항으로 무슨 일이 일어나고 있는지 이해할 수 있어야 합니다. 그렇지 않으면 효율적인 수정이 매우 어려울 수 있습니다. 상황을 해결하기 위해 로그가 나오는 곳은 다음과 같습니다.
로깅은 새로운 개념이 아니며 구현하기 쉬운 솔루션이 많이 존재하지만 효과적인 로그를 보장하는 것은 어렵습니다. 로그가 불분명하거나, 지나치게 복잡하거나, 부족하거나, 관련 없는 항목으로 넘쳐 문제 해결을 어렵게 만드는 경우가 많습니다. 그러나 로그는 문제 해결만을 위한 것이 아닙니다. 적절한 로깅은 새로운 기능과 변경 사항의 효과를 확인하는 데 도움이 됩니다. 로그 항목을 샘플링하면 사용자 여정을 추적하고 시스템이 의도한 대로 작동하는지 확인할 수 있습니다.
코드를 프로덕션 환경으로 배송하는 것은 때때로 위험할 수 있지만 프로세스를 훨씬 더 안전하게 만들기 위한 많은 전략이 있습니다. 문제를 식별하더라도 무엇이 허용되는지 여부를 아는 것도 중요합니다. 모든 실패가 롤백으로 이어지는 것은 아닙니다. 심각한 보안 결함을 수정하거나 새로운 규정을 준수하려고 하면 어떻게 되나요? 문제 발생 시 언제 중단하거나 진행할지 결정하려면 명확한 기준을 갖고 변경이 얼마나 중요한지 이해하는 것이 매우 중요합니다. 처음으로 돌아가서, 의사결정 과정에 도움이 되는 주요 측정항목이 있습니다.
모두들 안전 착륙하세요!