brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Nov 17. 2019

개발자를 위한
A/B 테스트 해시 샘플링

이번 글에서는 개발자를 위한 A/B 테스트 해시 샘플링 기법에 대해서 설명하겠다. A/B 테스트는 한페이지로 정리할 수 있는 단순한 개념이 아니다. 이 글은, 해시 샘플링 기법에 대해서 다룰 예정이지만, A/B 테스트에 대한 기본적인 개념은 상세하게 설명하지는 않는다.


개발자의 시각에서 작성한 글이므로, "기획자" 또는 "데이터_사이언티스트" 는 다른 자료를 찾아보길 바란다.


필자는 최근에 A/B 테스트 및 해시 샘플링 관련 업무를 진행하였다. 하지만, 해시 샘플링은 특정 회사에서 특허를 갖고 있는 기술이 아니며, 일반적인 분산 기법에 불과하다. 필자가 전회사에서 수차례 사용했던 방법이고, 외국 논문도 많으며, 여러 회사에서 다양하게 사용하는 일반적인 방법이다. 해당 방법이 특정 한 회사의 주요 기술이 아니라는 점을 먼저 이해해주길 바란다. 또한 이 글에 포함된 소스코드는 회사 코드와는 전혀 상관이 없으며, 업무를 진행하기 전에 개인적으로 작성한 코드이다.


"A/B 테스트"에 대해서(개발자의 시점에서)


"A/B 테스트"에 대해서 개발자의 시점에서 정리를 하였다.


A/B 테스트

마케팅과 웹 분석에서, A/B 테스트(버킷 테스트 또는 분할-실행 테스트)는 두 개의 변형 A와 B를 사용하는 종합 대조 실험(controlled experiment)이다. A/B 테스트의 목표는 관심 분야에 대한 결과를 늘리거나 극대화하는 웹 페이지에 대한 변경 사항이 무엇인지를 규명하는 것이다. A/B 테스트는 변수 A에 비해 대상이 변수 B에 대해 보이는 응답을 테스트하고, 두 변수 중 어떤 것이 더 효과적인지를 판단함으로써 단일 변수에 대한 두 가지 버전을 비교하는 방법이다. [1]


A/B 테스트 도구

A/B 테스트를 위한 적절한 도구는 실험을 돌리기 위한 유연성과 기능을 제공할 수 있지만, 세계 최고의 실험자로 만들어주지는 못한다. 궁긍적으로 여전히 성과 있는 아이디어를 설계하고 실행해야 할 텐데, 가장 단순한 도구로도 가능하다. 사업 수요, 실험 성숙도, 필요한 지원, 역량 등에 맞는 도구를 선택하자. [2]


도구는 수단을 뿐임을 잊지 말라. 그 자체로 대안을 제시하지는 못한다. 성과 있는 실험의 설계와 실행은 자신의 몫이다. [2]


A/B 테스트를 보완하는 Multi-Armed Bandit(MAB) 알고리즘

A/B 테스트를 보완하는 MAB 알고리즘에 대한 좋은 글을 추천한다. 필자의 전회사 대표님의 글이다.

http://hub.zum.com/kimws/2586

이 글에서는 MAB 에 대해서 자세하게 논하지 않는다. 아주 기본적인 A/B 테스트에 대해서만 얘기할 예정이다.


A/B 테스트 프로세스

"인터넷 시대의 과학적 방법론, A/B 테스트A/B 테스트 [송범근 님의 브런치]"를 참고하였다. [3]

1. 기존에 존재하는 데이터를 분석한다.

2. 목표를 구체화한다.

3. 지표를 선정한다.

4. 가설을 수립한다.

5. 실험을 설계 및 실행한다. (이 글의 주제인 A/B 테스트 샘플링 과정 포함)

6. 결과를 분석한다.


일반적으로 개발자는 실험을 설계하고 실행하는 단계에서 역할을 수행한다. 기존 데이터를 모으고 분석하는 과정은 "데이터 사이언티스트 및 데이터 엔지니어" 가 주로 진행하며, 데이터 분석 결과를 바탕으로 목표를 선정하고, 가설을 수립하는 과정은 "서비스 기획자"의 주요 임무이다. 수립된 가설을 바탕으로 실험을 설계하고 실행하는 과정은 "서비스 기획자 및 개발자"가 협업해서 함께 진행하며, 마지막으로 최종 결과를 바탕으로 분석하는 과정은 "데이터 사이언티스트 와 서비스 기획자" 가 협업해서 진행할 것이다.


물론, 해당 과정이 항상 일반적이지는 않다. 회사마다 방법이 다를 것이다.


어쨋든, 필자가 그동안 경험했던 A/B 테스트는 단 한번으로 끝난 경우는 없었다. A/B 테스트는 결과를 분석하고 목표를 구체화하는 과정이 끊임없이 반복된다. 이 글에서는 개발자의 시점에서, 결과 분석에 대해서는 자세히 논하지 않을 예정이며, 실험을 설계하고 실행하는 과정에서의 "A/B 테스트를 위한 해시 샘플링" 에 대해서 집중해서 글을 작성하겠다.  


A/B 테스트 사례

A/B 테스트는 두 가지 방법을 비교해서 어느 방법이 좋은지에 대한 결과를 도출해내는 과정이다. 필자가 많이 경험했던 A/B 테스트 사례를 소개하겠다. 포털 메인에 스포츠 뉴스 기사가 노출되고 있다고 가정하자. 해당 뉴스 기사 링크를 클릭하면 뉴스 상세 페이지로 이동하게 된다. 뉴스 상세페이지로 이동하면 추가 광고가 노출되기 때문에, 매출을 올리기 위해서는, 많이 클릭하도록 유도해야 한다.


스포츠 뉴스 기사를 어떤 상황에서 더 많이 클릭하는지 A/B 테스트를 통해서 검증하고자 한다.


가설 : 이미지 썸네일 형태의 링크는, 단순 텍스트 형태의 링크 보다 상대적으로 더 많이 클릭한다.

실험 설계 및 실행 : A 는 단순 텍스트 링크, B 는 이미지 썸네일 형태의 링크

실제로 이미지 썸네일 형태의 링크인 경우 더 효율적인 결과를 얻을 수 있다. 물론, 해당 결과 하나만으로 썸네일이 무조건 좋다고 단정 지을수는 없다. 왜냐하면, 단순히 해당 영역에 대한 결과는 좋아졌지만, 화면에 보여줄 수 있는 컨텐츠의 개수는 줄어들었기 때문이다.


A/B 테스트 결과를 쉽게 단정지어서는 안된다.


어쨋든, 이 글에서는 A/B 테스트 주의사항, 단점 등에 대해서는 논하지 않는다. 개발자가 고민해야할 내용은 사용자를 어떻게 A 와 B 로 효율적으로 나눌수 있는지 고민해야한다.


어떻게 하면 효율적으로 A 와 B 로 나눌 수 있을까?


A/B 테스트 샘플링

현재 서비스에서 A 화면으로 모든 사용자에게 적용 중이라고 가정해보자. 기획자는, 썸네일 이미지 링크인 경우 CTR 이 더 높을 것이라는 가정으로 A/B 테스트를 진행하자고 한다. 서비스 중인 상황이므로 너무 갑작스런 변화는 사용자의 UX 경험에 혼란을 줄 수 있기 때문에, 모든 사용자 중 5 % 의 비율만 썸네일 화면을 제공해주기로 설계하였다. 즉, A : B = 95 : 5  의 테스트를 진행하기로 하자. 100명의 사용자가 웹사이트에 접속하면 95명은 기존의 화면을 보고, 5명은 B 테스트의 화면을 보게 된다. 하지만, 일반적으로 A/B 테스트에서 특정 집단에 포함되는 경우 테스트 기간동안에는 해당 테스트를 계속 유지하게 된다. 예를 들어서, B 테스트에 선택된 5% 의 사용자는 테스트가 끝나는 시점까지 계속 B 테스트를 유지해야 한다. 물론, A/B 테스트 설계 방법에 따라서 테스트 집단을 다시 섞을 수도 있다. 하지만, 이 글에서는 테스트 기간 동안에는 한번 지정된 테스트는 계속 유지한다는 가정하겠다. B 로 선택된 사용자는, 다음에 접속했을 때도 B 테스트 화면이 노출되어야 한다. 어떻게 하면 좋을까?


경험이 있는 개발자/기획자 는 댓글로 피드백을 남겨주길 바란다.


A/B 테스트 사용자 샘플링


A/B 테스트 사용자 샘플링 기법에 대해서 알아보자.


심플한 방법으로 구현하는, A/B 테스트 샘플링

위에서 설명했듯이, 테스트 기간동안 사용자에게 설정된 화면은 계속 유지되어야 한다. 즉, 특정 사용자가 A/B 테스트가 진행중인 웹사이트를 처음 접속해서, B 테스트가 설정되었다면, 다음에 접속했을 때도 B 테스트로 수행이 되어야 한다. 일반적으로 사용자ID 기반으로 구현한다.

해당 방법은 User ID 에 해당하는 A/B 테스트 정보를 별도의 데이터베이스에 저장한다. 그래서, 웹사이트에 재접속했을 때, 사용자에게 해당하는 테스트를 수행할 수 있다. 예를 들어서, User ID 가 5a08cd58-7468-42ce-849e-0a948fc40c52 인 사용자가 처음 웹사이트에 접속하면, 할당되어있는 테스트 정보가 없을 것이다. 그래서, 5a08cd58-7468-42ce-849e-0a948fc40c52를 사용해서 계산한 버킷번호 및 A/B 테스트 정보를 계산하고, 계산한 정보를 데이터베이스에 저장해둔다. 5a08cd58-7468-42ce-849e-0a948fc40c52 에 해당하는 테스트가 A 라고 가정하면, 5a08cd58-7468-42ce-849e-0a948fc40c52 사용자가 다시 접속했을 때는, A 를 다시 실행할 것이다. 해당 방법은 매우 심플하지만, 데이터베이스에 사용자에 해당하는 A/B 테스트 정보를 전부 저장해야 한다.


하지만, 데이터베이스에 항상 저장하지 않고 버킷 번호 및 테스트를 설정할 수 있을까?  


만약, 사용자 ID 에 해당하는 A/B 테스트 정보가 항상 동일한 결과가 나온다면, 굳이 데이터베이스에 저장할 필요는 없다. 예를 들어서, 사용자 ID의 1의 자리가 홀수이면 A 테스트 그룹, 1의 자리가 짝수이면 B 테스트 그룹으로 일관되게 구분할 수 있다. 이 경우에, 사용자 ID 는 변하지 않기 때문에 1의 자리가 홀수인 사용자는 항상 A 테스트를 수행할 것이다. 물론, 이렇게 단순하게 A/B 테스트를 결정하는 방법은 한계가 있다.


좀 더 나이스한 방법을 찾아보자.


Hash 함수를 사용해서, A/B 테스트 샘플링

Hash 함수를 사용해서 일관된 버킷 번호를 구할 수가 있다. 그리고, 버킷 번호로 A/B Test 를 선택하게 되는 것이다.

A/B 테스트를 95:5 의 비율로 진행한다고 가정하면, A가 속하는 범위는 0~94 가 되고, B 가 속하는 범위는 95 ~ 99 가 된다. 그림을 그려보면 아래와 같다. Bucket Number 는 총합이 100인, 0 ~ 99 사이의 int 타입이다.

필자의 글이 이해가 잘 되는가? 필자의 글 내용이 이상하면 댓글로 좀 알려주길 바란다. 어쨋든, 중요한 사실은 해시 함수를 사용해서, 일관된 버킷 번호를 조회할 수 있다는 점이다. 그리고, 해당 버킷 번호를 사용해서 일관되게 사용자에게 테스트를 할당할 수 있다. 사용자의 id 가 바뀌지 않는 이상, 일관되게 테스트가 선택된다.


Bucket 이라는 단어를 다시 생각해보자.

Bucket 이란 물통(?) 이라는 단어이다. 필자의 테스트 샘플은 A : B = 95 : 5 의 비율로 설정하였다. 즉, A 테스트는 물통이 매우 크고, B 테스트는 물통이 작다.


참고로, Control Test 는 기존 사용자에게 제공하던 방식이고, Treatment Test 는 신규로 제공하는 새로운 기능이다.


실험마다 테스트 집단이 동일해야 하는가? (아니다..)

실험마다 테스트 집단은 항상 동일하지 않는다는 조건이라면? Bucket Number 를 계산하는 Hash Function 에 변화가 필요하다. 왜냐하면, 사용자의 ID 를 기준으로 Hash Function 을 계속 수행하면, 항상 동일한 버킷 번호가 조회되기 때문이다. 이 경우에, Hash Function 에 입력되는 문자열을 사용자 ID 만을 사용하지 않고, 여러 조건을 조합해서 문자열을 만드는 방법이 있다.

Hash Function ( 사용자 ID )  = 30

Hash Function ( 사용자 ID + 실험번호(1)) = 45

Hash Function ( 사용자 ID + 실험번호(2)) = 95

실험 번호를 붙여서, Hash Function 에 입력되는 문자열이 계속 변경 되기 때문에, 계산된 Bucket Number 역시 실험마다 변경이 될 것이다. 만약, 날짜마다 변경이 되도록 설정하고 싶다면 아래와 같이 적용할 수도 있다.

Hash Function ( 사용자 ID + 실험번호(1) + 20191026) = 33

Hash Function ( 사용자 ID + 실험번호(1) + 20191026) = 65


A/B 테스트 사용자 샘플링 구현하기


자, 코딩을 해서 구현해보자. 하지만, 방법이 중요하다고 생각하지는 않는다. 필자의 방법이 정답이라고 생각하지도 않는다. 스프링부트 환경에서 구현해본 필자의 샘플 코드는 아주 심플하기 때문에 실무에서는 도움이 못 될 것이다. 각자 스스로 직접 구현을 해보길 바란다.

참고로, 해당 관련 업무를 진행하기 전에 파일럿 프로젝트 겸 하루만에 빠르게 작성했던 코드이며, 실제 업무에서는 전혀 반영하지는 못했다. 아쉽지만, 다른 팀원들이 잘 구현해줘서 고맙게 생각한다.


테스트 시나리오

A/B 테스트를 진행한다. A 테스트는 텍스트 링크를 제공하고, B 테스트는 썸네일 형 링크를 제공한다. 아래와 같은 테스트를 위한 API 를 구현한다.


디펜던시

스프링 부트 2.1.9.RELEASE 버전에서 구현한다. 그리고, 사용자 ID 를 사용하기 위해서 스프링 세션을 구현하였다. 사실, 해당 샘플 예제에 스프링 세션은 적합하지 않을 수 있다. 간단한 샘플일 뿐이니깐, 그냥 참고만 하길 바란다.


엔드포인트, 서비스 레이어 구현

자세한 설명은 생략한다.

API 를 실행하면 클라이언트 브라우저에 쿠키가 생성된다. 해당 쿠키는 세션을 식별하기 위한 쿠키이다. 참고로, 스프링 세션을 연동했기 때문에 쿠키 이름이 SESSION 으로 기본으로 설정되었다.

세션 관련해서는 필자의 예전 글을 참고하길 바란다.

https://brunch.co.kr/@springboot/114

해당 사용자의 로그는 아래와 같은데, A/B 테스트 중에서 A 테스트가 선택되었다.

해당 사용자는 세션이 만료되기 전까지는 계속 동일한 테스트 집단에 속할 것이다. 자, 이제 쿠키를 계속 삭제하면서 B 테스트가 나올 때까지 반복해보자. B 테스트는 버킷(물통) 사이즈가 5밖에 되지 않기 때문에, 즉 5% 확률이기 때문에 바로 나오지 않고 수차례 반복해야 한다.


사용자 ID 에 맞는 Bucket Number 는 어떻게 구하는가?

guava 라이브러리의 Hashing 클래스를 사용해서 간단하게 작성하였다.


Bucket Number 를 통해서 A/B 테스트 선택은 어떻게?

코드가 많이 지저분하다.


테스트 실제 수행 방법

필자의 샘플 코드에서는 각각의 컴포넌트는 Consumer 인터페이스를 구현하였다.

A/B 테스트에 해당하는 컴포넌트를 서비스 레이어에서 실행한다.

소스 코드에 대한 설명은 더이상 하지 않겠다.

https://github.com/sieunkr/ab-test/tree/master/hash-bucket


글 마무리


A/B 테스트 해시 샘플링에 대해서 알아봤다. 사실, A/B 테스트는 필자의 이 글 하나로 모두 설명할 수 있는 간단한 개념이 아니다. 실무에서는 더 복잡한 로직, 프로세스가 도입이 된다. 또한, 기획자, 데이터 사이언티스트 , 머신러닝 전문가 등 다양한 조직에서 협업을 해야 좋은 결과를 얻을 수 있다. 더 자세한 내용은 회사 관련 내용(대외비)라서 이 글에서는 다루지 못하였다. 이정도로 글을 마무리하겠다. 끝.


레퍼런스


[1] 위키피디아 https://ko.wikipedia.org/wiki/A/B_%ED%85%8C%EC%8A%A4%ED%8A%B8

[2] A/B 테스트를 통한 웹사이트 전환율 최적화 [콜린 맥파랜드 지음 - 에이콘 출판사]

[3] 인터넷 시대의 과학적 방법론, A/B 테스트 [송범근 님의 브런치]

[4] Double-bucketing in A/B Testing 

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari