NGINX split client module A/B 테스트 활용 가이드
이 포스트는 NGINX split client module 을 활용하여, 요청을 전송한 클라이언트를 확률에 따라 그룹별로 분리하고, 그룹별로 요청을 다르게 처리하도록 구성해 A/B 테스트에 활용하는 방법에 관해 설명합니다.
NGINX split client module을 활용한 A/B 테스트를 통해 이전 버전의 앱과 신규 버전의 앱을 구성 후 소수의 클라이언트의 요청만 신규 버전으로 전달해 사용자의 반응을 미리 확인하거나, 3개 이상의 그룹으로 분리하여 다양한 UI별 선호도를 확인하는 등 클라이언트의 요청을 분산하여 다양한 방법으로 활용할 수 있습니다.
해당 모듈은 NGINX OSS, NGINX Plus 모두 사용 가능하며 컴파일 설치 시 별도 매개변수 없이 자동으로 빌드됩니다.
목차
1. A/B 테스트란?
2. NGINX split client module 사용 방법
3. NGINX split client module 테스트
4. 추가 활용 예시
5. 결론
1. A/B 테스트란?
A/B 테스트는 콘텐츠의 두 가지 이상의 요소를 비교하여 어느 쪽이 더 효과적인지를 분석하는 방법입니다. 웹사이트 디자인, 마케팅 캠페인 등 다양한 영역에서 활용할 수 있습니다. 사용자는 무작위로 두 가지 이상의 그룹으로 나뉘어 각각의 버전을 사용하고, 이를 통해 얻은 데이터로 어떤 버전이 목표 달성에 더 유리한지를 평가할 수 있습니다.
2. NGINX split client module 사용 방법
NGINX split client module(ngx_http_split_clients_module)은 split_clients 지시문을 통해 사용할 수 있습니다. 해당 지시문은 http 컨텍스트에서만 사용할 수 있습니다.
split_clients 지시문의 사용 예시는 다음과 같습니다.
split_clients $remote_addr $variant {
0.5% .one;
2.0% .two;
* "";
}
$remote_addr(클라이언트 IP)는 클라이언트를 구분하기 위한 키(key) 값입니다.$variant는 분리된 그룹별로 할당될 값을 저장하는 변수입니다.- 동일한 키(
$remote_addr)의 요청은 항상 같은 그룹에 할당됩니다. - NGINX는 키(
$remote_addr) 값을 해시 함수에 통과시켜 확률에 따라 그룹으로 할당합니다. - 확률의 *값은 100에서 나머지 확률을 뺀 값입니다.
- 모든 확률의 합은 100%를 넘을 수 없으며, 합이 100% 미만일 경우 * “”; 설정이 자동으로 적용됩니다.
- 0.5%의 클라이언트에게는
.one값이 할당 - 2.0%의 클라이언트에게는
.two값이 할당 - 나머지 97.5%의 클라이언트에게는 빈 문자열(
"") 할당
요청은 설정한 확률에 따라 분배되지만, 정확하게 비율에 맞게 나눠떨어지지는 않습니다. 즉, 3% 설정 후 100번의 요청을 전송해도, 무조건 3번의 요청이 해당 그룹으로 할당되지는 않습니다.
위 예시 설정과 아래 예시를 결합하면, 다음과 같은 결과가 나타납니다.
server {
location / {
index index${variant}.html;
- 0.5%의 클라이언트에게는 index.one.html 페이지를 표시
- 2.0%의 클라이언트에게는 index.two.html 페이지를 표시
- 나머지 클라이언트에게는 index.html 페이지를 표시
이처럼 NGINX split client module을 통해 지정된 확률에 따라 클라이언트를 분리할 수 있으며, 구분하는 키값에 따라 클라이언트는 동일한 그룹에 계속 속하기 때문에 반복적인 요청에도 다른 그룹으로 이동되지 않아 A/B 테스트 진행 중에도 클라이언트는 일관된 경험을 할 수 있습니다.
3. NGINX split client module 테스트
모듈을 간단하게 구성하여 테스트하기 위해 다음과 같이 NGINX를 구성했습니다.
ab.conf
split_clients $http_user_id $version {
70% v1;
* v2;
}
server {
listen 8080;
root /usr/share/nginx/html;
location / {
index ${version}.html;
}
}
User-ID 헤더의 값에 따라 클라이언트를 70:30 확률로 그룹으로 분리하고, $version 변수에 v1/v2 값을 할당합니다.
NGINX 서버의 8080 포트로 요청을 전송하면 그룹에 따라 v1.html 혹은 v2.html 페이지를 표시합니다.
각 HTML 페이지에는 ‘App v1’ 또는 ‘App v2’ 텍스트가 포함되어 있습니다.
User-ID 헤더의 값을 변경하며 요청을 전송하기 위해 파이썬의 requests 모듈을 통해 요청을 전송하고, User-ID 헤더의 값에 따른 응답 및 응답별 총횟수를 출력하도록 구성했습니다.
import requests
from collections import Counter
# 요청을 보낼 URL
url = 'http://192.168.200.176:8080/' # NGINX 서버 주소
# 사용자 ID 및 응답 결과를 저장할 리스트
results = []
# ID 1부터 1000까지 반복 요청
for user_id in range(1, 1001):
response = requests.get(url, headers={'User-ID': str(user_id)})
# 응답 텍스트 저장
results.append(response.text)
# 각 응답 결과물의 횟수 계산
result_count = Counter(results)
# 출력 결과 (ID 1부터 1000)
for user_id, result in zip(range(1, 1001), results):
print(f"User ID: {user_id}, Response: {result}", end="")
# 최종 응답 결과물별 횟수 출력
print("\n응답별 횟수:")
for result, count in result_count.items():
print(f"{result}: {count}회")
해당 코드를 통해 요청을 전송하여 다음과 같은 출력을 확인할 수 있습니다.
User ID: 1, Response: App V1
User ID: 2, Response: App V1
User ID: 3, Response: App V1
User ID: 4, Response: App V1
User ID: 5, Response: App V1
User ID: 6, Response: App V1
User ID: 7, Response: App V2
User ID: 8, Response: App V1
User ID: 9, Response: App V1
User ID: 10, Response: App V1
...
User ID: 998, Response: App V1
User ID: 999, Response: App V1
User ID: 1000, Response: App V1
응답별 횟수:
App V1
: 720회
App V2
: 280회
설정한 확률의 근삿값으로 응답 횟수가 나타난 것을 확인할 수 있습니다.
동일한 ID로 여러번 요청을 전송하면, 매번 같은 응답을 수신하는지 확인하겠습니다.
이전의 단계에서 App V2 응답을 수신했던 User-ID 헤더의 값이 7인 요청을 100회 전송하겠습니다.
import requests
from collections import Counter
# 요청을 보낼 URL
url = 'http://192.168.200.176:8080/' # NGINX 서버 주소
# 사용자 ID 7의 응답 결과를 저장할 리스트
results = []
# ID 7로 100번 반복 요청
for _ in range(100):
response = requests.get(url, headers={'User-ID': '7'})
# 응답 텍스트 저장
results.append(response.text)
# 각 응답 결과물의 횟수 계산
result_count = Counter(results)
# 최종 응답 결과물별 횟수 출력
print("응답별 횟수:")
for result, count in result_count.items():
print(f"{result}: {count}회")
응답별 횟수:
App V2
: 100회
100회의 응답이 모두 이전과 동일함을 확인할 수 있습니다.
키값을 통해 분류된 클라이언트는 동일한 그룹에 계속 속하기 때문에, 반복되는 요청에도 일관된 경험을 할 수 있습니다.
4. 추가 활용 예시
NGINX의 if 조건식과 NGINX split client module을 통해 할당된 변수를 조합하여 다음과 같이 여러 그룹으로 분류하여 활용할 수 있습니다.
split_clients $http_user_id $version {
33.3% v1;
33.3% v2;
* v3;
}
upstream app-v1 {
server localhost:8001;
}
upstream app-v2 {
server localhost:8002;
}
upstream app-v3 {
server localhost:8003;
}
server {
listen 9000;
location / {
if ($version = v1) {
proxy_pass http://app-v1;
}
if ($version = v2) {
proxy_pass http://app-v2;
}
if ($version = v3) {
proxy_pass http://app-v3;
}
}
}
동일한 확률로 클라이언트를 User-ID 헤더의 값에 따라 3개의 그룹으로 나누고, 변숫값에 따라 서로 다른 upstream 그룹으로 요청을 프록시 합니다.
이전과 같이 테스트 요청 전송 시 결과입니다.
User ID: 1, Response: Server1
User ID: 2, Response: Server1
User ID: 3, Response: Server2
User ID: 4, Response: Server1
User ID: 5, Response: Server2
User ID: 6, Response: Server2
User ID: 7, Response: Server3
User ID: 8, Response: Server1
User ID: 9, Response: Server2
User ID: 10, Response: Server1
응답별 횟수:
Server1
: 366회
Server2
: 328회
Server3
: 306회
5. 결론
이번 포스트에서는 NGINX split client module을 활용하여 A/B 테스트에 활용하는 방법을 알아봤습니다.
NGINX split client module을 통해 클라이언트를 식별할 키값 / 그룹별 확률을 손쉽게 설정하여 A/B 테스트에 활용할 수 있었습니다. 또한 동일한 키값을 가진 클라이언트는 동일한 그룹에 계속 속하게 되어 여러 번 요청을 전송해도 동일한 응답 결과를 수신하는 것도 알게 되었습니다.
NGINX split client module을 활용하면 이처럼 A/B 테스트를 위한 환경을 손쉽게 구성할 수 있습니다.
NGINX split client module을 포함한 다양한 기능이 있는 NGINX OSS의 엔터프라이즈 버전인 NGINX Plus를 체험해 보고 싶으시다면 NGINX STORE를 통해 문의해 무료로 NGINX Plus trial을 체험해 보세요.