[CRM 마케터의 SQL] 랜덤 샘플링 하기 (+해시 샘플링 쿼리로 구현하기)
취준 시절 SQL을 공부하면서 가장 궁금했던 것 중 하나는 "CRM 마케터" 직무에서 자주 쓰는 SQL은 무엇인지 궁금했던 것 같다. 작년 입사 후 1년간 신입 CRM 마케터로 일하면서 자주 사용했던 SQL 쿼리들을 [CRM 마케터의 SQL] 시리즈로 연재해볼까 한다.
*이 시리즈는 필자가 실무에서 자주 사용하는 SQL 쿼리들을 ChatGPT 데이터로 임의로 구현한 내용입니다. 기업마다 사내 실험/분석 환경 및 직무 R&R에 따라 본 내용을 마케터가 수작업으로 하지 않아도 될 수 있습니다. (하지만 저는 최첨단 수동화 기법을 사용합니다...)
직무 특성상 CRM 실험, 프로모션 분석 등등의 목적으로 랜덤 샘플링을 하는 일이 자주 있다.
랜덤이 다 같은 랜덤이지 뭐가 다르냐 싶긴 하지만....조금 더 잘 섞기 위한 노력과 스킬이 필요할 때가 있다. 실험 대조군의 특성이 비교가능하도록 잘 나눠져야 한다거나, 실험 대조군 나누고 추출 할 때 마다 유저가 달라지면 안된다든가 등등
암튼 주로 사용하는 두 가지 방법을 정리해봤다.
1. 간편하게 두 그룹으로 나누는 방법 : ORDER BY RANDOM() LIMIT N 사용 (난수 생성 후 정렬)
진짜 유저 그룹을 후딱 나눠야 한다거나
귀찮거나
할 때 사용하는 방법이다.
사용하기 편리하고 아주 기초적이라서 SQL 기초만 안다면 누구든 써먹을 수 있다.
select user_id
from user_tb
order by random() asc
limit 100 -- 원하는 규모의 모수를 입력
하지만 정말 큰 단점은 추출 할 때 마다 시드값이 고정되어 있지 않아서 추출 할 때마다 유저가 계속 바뀐다는 점이다.
따라서 유저 리스트를 따로 임시테이블이나 csv 파일로 저장해놔야하는 번거로움이 있다.
그리고 동일한 조건 모수를 추출하고 랜덤으로 A B 그룹을 분기하고 싶을 때는 저 방법을 쓰기가 조금 까다로워진다. (쿼리를 두 번 쓰든...먼저 뽑은 유저를 저장해놓고 where문에서 제외를 하든...암튼 귀찮다)
2. 해시함수를 사용하는 방법 (Hash Sampling)
두 번째 방법은 해시 샘플링이다.
요즘은 위 단점을 보완한 해시샘플링을 더 자주 쓴다. salt 값을 잘 활용하면 시드값이 고정 되는게 큰 장점이다.
salt는 암호쪽 분야에서 많이 사용되는 개념인 듯 한데, 간단히 이해 한 바로는 원본 데이터를 복원하기 어렵도록 암호화 전 추가하는 값을 의미한다.
왜 쓸까? 예를 들어 xxhash64(user_id) % 2 = 0 조건으로 유저를 추출한다고 하면 항상 같은 유저가 추출될 것이다. 이 때 저 조건은 놔두고 실험 번호와 추출 일자를 salt 값으로 사용하면 더욱 고도화된 샘플링이 가능하다.
일단 코드로 구현하면 아래와 같다.
-- 단순히 특정 테이블에서 10%만 랜덤 추출하고 싶을 때
SELECT user_id
FROM user_tb
WHERE xxhash64(user_id) % 10 = 0 -- 해시함수는 여러가지가 있는데 알아서 잘 쓰면 된다
-- salt 값을 사용하는 경우
-- 유저 아이디, 실험 일자, 실험 번호를 concat 한 뒤, 이를 해시함수로 암호화하여 정렬
select *
from (
SELECT
user_id
, row_number() over(order by xxhash64(concat(user_id,'2025-01-14','Exp 1'))) as rn
FROM user_tb
)
WHERE rn <= 100 -- 원하는 모수 규모를 입력
해시 함수는 여러가지가 있다고 한다
만약 두 그룹 이상으로 나누고 싶다면 어떻게 해야할까? 그 때는 아래와 같이 그룹을 쉽게 분기할 수 있다.
-- 두 그룹으로 분기한다면
select
user_id
, case
when rn % 2 = 0 then 'A'
else 'B'
end as group_name
from (
SELECT
user_id
, row_number() over(order by xxhash64(concat(user_id,'2025-01-14','Exp 1'))) as rn
FROM user_tb
)
WHERE rn <= 100 -- 원하는 모수 규모를 입력
두 그룹이든 세 그룹이든 방법은 나머지 값을 이용해서 원하는 반큼 그룹을 나눌 수 있다.
그렇다면 난수 생성방식이랑 해시샘플링 방식은 무슨 차이가 있고 어떨 때 해시샘플링을 사용할까?
해시 샘플링은 난수 생성 방식과 다르게 유저 단위로 맵핑 되는거라서 같은 salt와 해시 함수를 사용하면 항상 같은 유저가 같은 그룹에 할당된다. 즉 유저를 나눠놓고 유저 리스트만 저장하면 되지, 유저가 어느 그룹에 속했는지 메모해 둘 필요가 없다는 것이다. 심지어 DB 액세스 조차 필요 없다. 왜냐면 salt 값이 같다면 항상 같은 유저가 추출되기 때문이다.
사실 앱푸시 소재 테스트나 프로모션 테스트 등 사전에 실험/대조군을 나눠 놓을 수 있는 테스트라면 위에서 소개한 난수 생성 방식도 나쁘지 않다.
하지만 실시간 스트림으로 a/b 테스트를 해야하는 상황이라면 그 모든 유저가 어느 그룹에 들어갔는지 기록하고, 그 유저가 재접속했을 때 A/B 어느 유저였는지 DB를 스캔한다거나...하면 해시 샘플링 방식을 쓸 수 밖에 없을 듯 하다. (하지만 난 엔지니어가 아니지)
개인적으로 템플릿화해놓고 추출하기에는 해시 샘플링 방식이 편리하여 요즘은 위의 방식을 자주 사용한다.
끝!
참고
https://brunch.co.kr/@springboot/283
https://zzsza.github.io/data/2019/02/23/ab-test-sampling-with-hash-function/