애플리케이션의 변경 사항을 테스트할 때 개발 테스트 베드가 아닌 프로덕션 환경에서만 측정할 수 있는 몇 가지 요소가 있습니다. 예로는 UI 변경이 사용자 행동에 미치는 영향과 전반적인 성능에 미치는 영향 등이 있습니다. 일반적인 테스트 방법은 A/B 테스트 입니다. 분할 테스트 라고도 하며, 대부분의 사용자는 현재 버전을 계속 사용하는 반면, 일부(보통 소수)의 사용자는 새 버전의 애플리케이션으로 이동하게 됩니다.
이 블로그 게시물에서는 웹 애플리케이션의 새로운 버전을 배포할 때 A/B 테스트를 수행하는 것이 중요한 이유와 NGINX 및 NGINX Plus를 사용하여 사용자에게 표시되는 애플리케이션 버전을 제어하는 방법을 살펴보겠습니다. 구성 예제는 NGINX 및 NGINX Plus 지침, 매개변수 및 변수를 사용하여 정확하고 측정 가능한 A/B 테스트를 달성하는 방법을 보여줍니다.
앞서 언급했듯이 A/B 테스트를 통해 두 버전 간의 애플리케이션 성능이나 효과의 차이를 측정할 수 있습니다. 아마도 개발팀이 UI에서 버튼의 시각적 배열을 변경하거나 쇼핑 카트 프로세스 전체를 개편하고 싶을 수도 있지만, 변경 사항이 원하는 비즈니스 영향을 미치는지 확인하기 위해 거래 성사율을 비교하고 싶을 수도 있습니다. A/B 테스트를 사용하면 정의된 비율의 트래픽을 새 버전으로 보내고 나머지를 이전 버전으로 보내 두 버전의 애플리케이션의 효과를 측정할 수 있습니다.
아니면 귀하의 우려는 사용자 행동에 미치는 영향보다는 성능에 미치는 영향과 더 관련이 있을 수도 있습니다. 웹 애플리케이션에 많은 변경 사항을 배포할 계획이지만 품질 보증 환경 내에서의 테스트로는 프로덕션 성능에 미치는 영향을 제대로 파악할 수 없다고 가정해 보겠습니다. 이 경우 A/B 배포를 통해 변경 사항의 성능 영향을 측정하기 위해 소수의 정의된 비율의 방문자에게 새 버전을 노출하고, 최종적으로 모든 사용자에게 변경된 애플리케이션을 롤아웃할 때까지 비율을 점차 늘릴 수 있습니다.
NGINX와 NGINX Plus는 웹 애플리케이션 트래픽이 전송되는 위치를 제어하는 몇 가지 방법을 제공합니다. 첫 번째 방법은 두 제품 모두에서 사용할 수 있지만 두 번째 방법은 NGINX Plus에서만 사용할 수 있습니다.
두 방법 모두 클라이언트의 특성(예: IP 주소) 또는 요청 URI(예: 명명된 인수)를 포착하는 하나 이상의 NGINX 변수 값을 기반으로 요청의 대상을 선택하지만, 이러한 방법 간의 차이점으로 인해 다양한 A/B 테스트 사용 사례에 적합합니다.
split_clients
메서드는 요청에서 추출된 변수 값의 해시에 따라 요청의 대상을 선택합니다. 가능한 모든 해시 값의 집합은 여러 애플리케이션 버전으로 나뉘며, 각 애플리케이션에 집합의 다른 비율을 할당할 수 있습니다. 목적지 선택은 무작위로 이루어진다.고정
경로
방식을 사용하면 각 요청의 대상을 훨씬 더 효과적으로 제어할 수 있습니다. 애플리케이션 선택은 변수 값 자체(해시가 아님)를 기준으로 하므로 특정 변수 값이 있는 요청을 수신하는 애플리케이션을 명시적으로 설정할 수 있습니다. 정규 표현식을 사용하면 변수 값의 일부만을 기준으로 결정을 내릴 수 있으며, 결정의 기준으로 한 변수를 다른 변수보다 우선적으로 선택할 수도 있습니다.split_clients
메서드 사용이 방법에서 split_clients
구성 블록은 proxy_pass
지시문이 요청을 보내는 업스트림 그룹을 결정하는 각 요청에 대한 변수를 설정합니다. 아래 샘플 구성에서 $appversion
변수의 값은 proxy_pass
지시문이 요청을 보내는 위치를 결정합니다. split_clients
블록은 해시 함수를 사용하여 변수 값을 두 개의 업스트림 그룹 이름( version_1a 또는 version_1b) 중 하나로 동적으로 설정합니다.
http { # ...
# application version 1a
upstream version_1a {
server 10.0.0.100:3001;
server 10.0.0.101:3001;
}
# application version 1b
upstream version_1b {
server 10.0.0.104:6002;
server 10.0.0.105:6002;
}
split_clients "${arg_token}" $appversion {
95% version_1a;
* version_1b;
}
server {
# ...
listen 80;
location / {
proxy_set_header Host $host;
proxy_pass http://$appversion;
}
}
}
split_clients
지시문의 첫 번째 매개변수는 문자열( "${arg_token} "
우리의 예에서는 "이것은 각 요청 동안 MurmurHash2 함수를 사용하여 해시됩니다. URI 인수는 NGINX에서 $ arg_name
이라는 변수로 사용할 수 있습니다. 이 예에서 $arg_token
변수는 token 이라는 URI 인수를 캡처합니다. 해시할 문자열로 NGINX 변수 나 변수 문자열을 사용할 수 있습니다. 예를 들어, 클라이언트의 IP 주소( $remote_addr
변수), 포트( $remote_port
) 또는 이 둘의 조합을 해시할 수 있습니다. NGINX에서 요청이 처리되기 전에 생성된 변수를 사용하고 싶을 것입니다. 클라이언트의 초기 요청에 대한 정보를 포함하는 변수가 이상적입니다. 예를 들어, 이미 언급한 클라이언트의 IP 주소/포트, 요청 URI 또는 HTTP 요청 헤더가 있습니다.
split_clients
지시문의 두 번째 매개변수(예시에서는 $appversion
)는 첫 번째 매개변수의 해시에 따라 동적으로 설정되는 변수입니다. 중괄호 안의 문장은 해시 테이블을 "버킷"으로 나눕니다. 각 버킷에는 가능한 해시의 백분율이 들어 있습니다. 버킷은 원하는 수만큼 만들 수 있으며, 모두 같은 크기일 필요는 없습니다. 마지막 버킷의 백분율은 해시 수가 지정된 백분율로 균등하게 나누어지지 않을 수 있으므로 특정 숫자가 아닌 별표(*)로 항상 표시됩니다.
우리의 예에서 우리는 해시의 95%를 version_1a 업스트림 그룹과 연관된 버킷에 넣고, 나머지는 version_1b 와 연관된 두 번째 버킷에 넣습니다. 가능한 해시 값의 범위는 0~4,294,967,295이므로 첫 번째 버킷에는 0~약 4,080,218,930(전체의 95%)까지의 값이 포함됩니다. $appversion
변수는 $arg_token
변수의 해시를 포함하는 버킷과 연관된 업스트림으로 설정됩니다. 구체적인 예로, 해시 값 100,000,000은 첫 번째 버킷에 속하므로 $appversion은
동적으로 version_1a 로 설정됩니다.
split_clients
구성 테스트split_clients
구성 블록이 의도한 대로 작동하는지 확인하기 위해 위와 동일한 비율(95%와 나머지)로 두 개의 상류 그룹 간에 요청을 나누는 테스트 구성을 만들었습니다. 그룹 내의 가상 서버가 요청을 처리한 그룹( version_1a 또는 version_1b )을 나타내는 문자열을 반환하도록 구성했습니다( 여기서 테스트 구성을 확인할 수 있습니다). 그런 다음 curl을
사용하여 20개의 요청을 생성하고, urandom
파일에서 cat
명령을 실행하여 URI 인수 토큰 의 값을 무작위로 설정했습니다. 이는 순전히 데모 및 무작위화 목적을 위한 것입니다. 우리가 의도한 대로, 20개 요청 중 1개(95%)가 version_1b 에서 제공되었습니다(간결성을 위해 요청 10개만 표시했습니다).
# for x in {1..20}; do curl 127.0.0.1?token=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1); doneToken: p3Fsa86HfJDFwZ9ZnYz4QbaZLzcb70Ka Served from site version_1a.
Token: a7z7afUz7zQijXwsLGfj6mWyPU8sCRIZ Served from site version_1a.
Token: CjIr6W57NtPzChf4qbYMzD1Estif7jOH Served from site version_1a.
... output for 10 requests omitted ...
Token: gXq8cbG3jhl1LuYmICPTfDQT855gaO5y Served from site version_1a.
Token: VVqGuNN3cRU2slsl5AWOR93aQX8nXOpk Served from site version_1a.
Token: z7KnewxTX5Sp6wscT0fgmTDohTsQuCmy Served from site version_1b!!
Token: fWOgH9WRrR0kLJZcIaYchpLhceaQgPD1 Served from site version_1a.
Token: mTADMXrVnwnr1cd5JE6QCSkgTwfWUnDk Served from site version_1a.
Token: w7AzSNmNJtxWZaH6cXe2PWIFqst2o3oP Served from site version_1a.
Token: QR7ay0dA39MmVlXtzgOVsj6SBTPi8ECC Served from site version_1a.
스티키
경로
방법 사용어떤 경우에는 NGINX 변수 값의 일부 또는 전체에 따라 클라이언트 라우팅 결정을 내려 정적 경로를 정의할 수 있습니다. NGINX Plus에서만 사용할 수 있는 스티키
경로
지시문을 사용하면 됩니다. 해당 지시문은 하나 이상의 매개변수 목록을 받아서 목록의 첫 번째 비어 있지 않은 매개변수 값으로 경로를 설정합니다. 이 기능을 사용하면 요청에서 목적지 선택을 제어하는 변수를 우선적으로 순위를 매기고, 단일 구성에서 여러 트래픽 분할 방법을 수용할 수 있습니다.
이 방법을 사용하는 데에는 두 가지 다른 접근 방식이 있습니다.
User-Agent
와 같은 브라우저별 HTTP 요청 헤더와 같이 클라이언트에서 직접 전송된 값이 포함된 NGINX 변수를 기반으로 경로를 선택할 수 있습니다.스티키
경로
지시문은 경로 표시기를 추출하고 해당 서버로 요청을 전달합니다.우리의 예제에서는 애플리케이션 측 접근 방식을 사용합니다. 업스트림
그룹의 스티키
경로
지시문은 우선적으로 서버에서 제공한 쿠키( $route_from_cookie
에서 캡처)에 지정된 값으로 경로를 설정합니다. 클라이언트에 쿠키가 없으면 경로는 요청 URI( $route_from_uri
)에 대한 인수의 값으로 설정됩니다. 그런 다음 경로 값은 업스트림 그룹에서 어떤 서버가
요청을 받는지 결정합니다. 경로가 a
이면 첫 번째 서버, 경로가 b
이면 두 번째 서버입니다(두 서버는 애플리케이션의 두 버전에 해당).
upstream backend { zone backend 64k;
server 10.0.0.200:8098 route=a;
server 10.0.0.201:8099 route=b;
sticky route $route_from_cookie $route_from_uri;
}
하지만 a
또는 b는
실제 쿠키 또는 URI의 훨씬 더 긴 문자열에 내장되어 있습니다. 문자만 추출하려면 쿠키와 URI 각각에 대한 맵
구성 블록을 구성합니다.
map $cookie_route $route_from_cookie { ~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
첫 번째 맵
블록에서 $cookie_route
변수는 ROUTE 라는 쿠키의 값을 나타냅니다. 두 번째 줄의 정규 표현식은 PCRE( Perl Compatible Regular Expression ) 구문을 사용하여 값의 일부(이 경우 마침표 뒤의 문자열( w+
))를 명명된 캡처 그룹 경로
로 추출하여 해당 이름의 내부 변수에 할당합니다. 이 값은 첫 번째 줄의 $route_from_cookie
변수에도 할당되어, 스티키
경로
지시문에 전달할 수 있습니다.
예를 들어, 첫 번째 맵
블록은 이 쿠키에서 값 " a "를 추출하여 $route_from_cookie
에 할당합니다.
ROUTE=iDmDe26BdBDS28FuVJlWc1FH4b13x4fn.a
두 번째 맵
블록에서 $arg_route
변수는 요청 URI에서 route 라는 이름의 인수를 나타냅니다. 쿠키와 마찬가지로 두 번째 줄의 정규 표현식은 URI의 일부를 추출합니다. 이 경우 route 인수의 마침표 뒤에 있는 문자열( w+
)입니다. 값은 명명된 캡처 그룹으로 읽혀지고, 내부 변수에 할당되고, $route_from_uri
변수에도 할당됩니다.
예를 들어, 두 번째 맵
블록은 이 URI에서 값 b를 추출하여 $route_from_uri
에 할당합니다.
www.example.com/shopping/my-cart?route=iLbLr35AeAET39GvWK2Xd2GI5c24y5go.b
전체 샘플 구성은 다음과 같습니다.
http { # ...
map $cookie_route $route_from_cookie {
~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
upstream backend {
zone backend 64k;
server 10.0.0.200:8098 route=a;
server 10.0.0.201:8099 route=b;
sticky route $route_from_cookie $route_from_uri;
}
server {
listen 80;
location / {
# ...
proxy_pass http://backend;
}
}
}
스티키
경로
구성 테스트split_clients
메서드에 대해서는 테스트 구성을 만들었는데, 여기 에서 접근할 수 있습니다. 우리는 curl을
사용하여 ROUTE 라는 이름의 쿠키를 보내거나 URI에 경로 인수를 포함했습니다. 쿠키 또는 인수의 값은 urandom
파일에서 cat
명령을 실행하여 생성된 임의의 문자열이며, .a 또는 .b 가 추가됩니다.
먼저, .a 로 끝나는 쿠키로 테스트합니다.
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a" 127.0.0.1Cookie Value: R0TdyJOJvxBkLC3f75Coa29I1pPySOeQ.a
Request URI: /
Results: Site A - Running on port 8089
그런 다음 .b 로 끝나는 쿠키로 테스트합니다.
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).b" 127.0.0.1Cookie Value: JhdJZrScTnPBLhqmzK3podNRcJAIc8ST.b
Request URI: /
Results: Site B - Running on port 8099
마지막으로 쿠키 없이, 대신 요청 URI에 .a 로 끝나는 경로 인수를 사용하여 테스트합니다. 출력은 쿠키가 없는 경우( 쿠키
값
필드가 비어 있는 경우) NGINX Plus가 URI에서 파생된 경로 값을 사용함을 확인합니다.
# curl 127.0.0.1?route=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).aCookie Value:
Request URI: /?route=yNp8pHskvukXK6XqbWefhVUcOBjbJv4v.a
Results: Site A - Running on port 8089
여기에 설명된 테스트 유형은 구성이 의도한 대로 요청을 분산하는지 확인하기에 충분하지만 실제 A/B 테스트 결과를 해석하려면 요청이 처리되는 방식에 대한 훨씬 더 자세한 로깅 및 분석이 필요합니다. 로깅과 분석을 올바르게 수행하는 방법은 여러 요인에 따라 달라지며 이 게시물의 범위를 벗어나지만 NGINX와 NGINX Plus는 요청 처리에 대한 정교한 내장 로깅 및 모니터링을 제공합니다.
log_format
지시어를 사용하면 모든 NGINX 변수를 포함하는 사용자 정의 로그 형식을 정의할 수 있습니다. NGINX 로그에 기록된 변수 값은 나중에 분석하는 데 사용할 수 있습니다. 사용자 정의 로깅 및 런타임 모니터링에 대한 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요.
실험이나 A/B 테스트 계획을 설계할 때 애플리케이션 버전 간에 요청을 분배하는 방법에 따라 결과가 미리 결정되지 않도록 주의하세요. 완전히 무작위 실험을 원하는 경우 split_clients
메서드를 사용하고 여러 변수의 조합을 해싱하면 가장 좋은 결과를 얻을 수 있습니다. 예를 들어, 쿠키와 요청의 사용자 ID를 조합하여 고유한 실험 토큰을 생성하면 클라이언트의 브라우저 유형과 버전만을 해싱하는 것보다 더 무작위적인 테스트 패턴을 제공합니다. 많은 사용자가 동일한 유형의 브라우저와 버전을 사용할 가능성이 높기 때문에 모두 동일한 버전의 애플리케이션으로 이동하게 될 가능성이 높기 때문입니다.
또한 많은 사용자가 혼합 그룹 에 속한다는 점도 고려해야 합니다. 그들은 여러 기기에서 웹 애플리케이션에 접속합니다. 아마도 직장이나 집의 컴퓨터 모두에서 접속할 수도 있고, 태블릿이나 스마트폰과 같은 모바일 기기에서도 접속할 수도 있습니다. 해당 사용자는 여러 개의 클라이언트 IP 주소를 가지고 있으므로 클라이언트 IP 주소를 기준으로 애플리케이션 버전을 선택할 경우 두 가지 버전의 애플리케이션이 모두 표시되어 실험 결과가 손상될 수 있습니다.
아마도 가장 쉬운 해결책은 사용자에게 로그인을 요구하여 앞서 예시한 스티키
경로
방식처럼 세션 쿠키를 추적하는 것입니다. 이렇게 하면 테스트를 추적하고 테스트에 처음 접속했을 때 본 것과 동일한 버전으로 항상 보낼 수 있습니다. 이렇게 할 수 없다면 테스트 과정에서 변경할 가능성이 거의 없는 그룹에 사용자를 배치하는 것이 합리적인 경우도 있습니다. 예를 들어 지리적 위치를 사용하여 로스앤젤레스에 있는 사용자에게는 한 버전을 보여주고 샌프란시스코에 있는 사용자에게는 다른 버전을 보여주는 것입니다.
A/B 테스트는 여러 대체 서버 간에 트래픽을 분할하여 애플리케이션의 변경 사항을 분석하고 추적하고 애플리케이션 성능을 모니터링하는 효과적인 방법입니다. NGINX와 NGINX Plus는 모두 A/B 테스트를 위한 견고한 프레임워크를 구축하는 데 사용할 수 있는 지침, 매개변수 및 변수를 제공합니다. 또한 각 요청에 대한 귀중한 세부 정보를 기록할 수 있습니다. 테스트를 즐겨보세요!
NGINX Plus와 스티키 경로
방식
을 직접 사용해보세요. 오늘 무료 30일 체험판을 시작하거나, 사용 사례에 대해 논의하기 위해 저희에게 연락하세요 .
"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."