- 저장소 샘플링은 전체 크기를 모르는 데이터 스트림에서도 정해진 개수만 메모리에 두고 모든 항목에 같은 선택 기회를 주는 추출 기법임
- 크기를 아는 배열은 섞기나 무작위 인덱스 선택으로 충분하지만, 한 번 지나간 항목으로 돌아갈 수 없는 스트림에서는 다른 접근이 필요함
- 단일 항목 선택에서는 n번째 항목을 1/n 확률로 채택해 새 항목의 선택 가능성과 기존 항목의 생존 가능성을 균형 있게 맞춤
- 여러 항목을 뽑을 때는 보관 개수 k에 맞춰 새 항목을 k/n 확률로 채택하고, 필요하면 현재 보관 중인 항목 하나를 무작위로 교체함
- 로그 수집에 적용하면 초당 최대 5개 같은 처리 상한을 넘기지 않으면서, 조용한 구간의 로그 손실과 메모리 사용량을 함께 줄일 수 있음
크기를 아는 집합에서의 샘플링
- 10장의 카드에서 3장을 무작위로 뽑는다면, 전체를 섞고 앞의 3장을 고르는 방식만으로 각 카드에 같은 선택 확률을 줄 수 있음
- 카드가 100만 장으로 늘어나면 직접 섞기 어렵지만, 배열처럼 인덱스로 접근 가능한 구조에서는 무작위 인덱스 3개를 고르면 같은 목적을 달성함
- 메모리상의 배열은 특정 인덱스 접근이 쉽지만, 카드 더미에서 436,234번째 카드를 세는 식의 작업은 현실적으로 오래 걸림
크기를 모르는 스트림에서 생기는 제약
- 한 번에 카드 1장만 보고, 동시에 1장만 들고 있을 수 있으며, 지나간 카드로 돌아갈 수 없다면 전체 개수를 모른 채 최종 1장을 골라야 함
- 로그 수집 서비스도 비슷한 형태의 문제를 만남
- 다른 서비스에서 로그 메시지를 받아 한곳에 저장함
- 나쁜 릴리스나 트래픽 급증으로 로그가 몰리면 수집 서비스가 압도될 수 있음
- 예시의 로그 수집 서비스는 초당 5개 로그를 처리할 수 있는 임계값을 가짐
- 로그의 10%만 보내는 방식은 급증 구간에서는 임계값을 넘지 않게 해주지만, 조용한 구간에서도 필요 없이 90% 로그를 버림
- 원하는 동작은 조용한 구간에서는 모든 로그를 보내고, 급증 구간에서는 초당 최대 5개까지만 보내는 방식임
- 매초 처음 본 5개 로그만 보내면 뒤에 도착한 로그가 선택될 기회를 잃기 때문에 공정하지 않음
단일 항목 저장소 샘플링
- 저장소 샘플링은 전체 개수를 모르는 상태에서도 지금까지 본 항목들 중 공정한 샘플을 유지함
- 모든 메시지를 메모리에 저장한 뒤 나중에 고를 수도 있지만, 스파이크 규모를 모르면 필요한 메모리 양도 예측하기 어려움
- 이 방식은 요청한 샘플 수보다 더 많은 메모리를 쓰지 않으면서 같은 문제를 해결함
- 단일 카드를 고르는 규칙은 간단함
- 첫 번째 카드는 항상 보관함
- n번째 새 카드는 1/n 확률로 보관함
- 새 카드를 보관하기로 하면 기존 보관 카드는 버림
- 매 카드마다 50% 확률로 교체하면 뒤쪽 카드가 유리해져 공정하지 않음
- 첫 번째 카드는 10번째 카드 이후에도 남으려면 여러 번의 교체 기회를 모두 살아남아야 함
- 마지막 카드는 한 번만 선택되면 손에 남을 수 있음
- 1/n 규칙은 새 카드의 선택 확률뿐 아니라 기존 카드의 생존 확률까지 맞춤
- 첫 번째 카드는 1/1, 즉 100% 확률로 보관됨
- 두 번째 카드에서는 새 카드가 1/2 확률로 선택되고, 첫 번째 카드도 1/2 확률로 남음
- 세 번째 카드에서는 새 카드가 1/3 확률로 선택되고, 기존 보관 카드도 50% × 2/3으로 1/3 확률이 됨
- 일반적으로 n번째 단계에서 기존 카드가 남을 확률은
1/(n-1) * (1-(1/n))이고, 새 카드가 선택될 확률은1/n으로 같아짐
여러 항목을 뽑는 확장
- 단일 항목 선택은 여러 항목 선택으로 확장할 수 있음
- k개의 항목을 선택하려면 규칙 두 가지가 달라짐
- 새 항목은
1/n이 아니라 k/n 확률로 선택됨 - 교체가 필요하면 현재 보관 중인 k개 항목 중 하나를 무작위로 골라 새 항목으로 바꿈
- 새 항목은
- 기존 항목의 선택 확률은
k/(n-1)로 표현되고, 새 항목으로 교체되지 않을 확률을 곱해 공정성이 유지됨 - 보관 중인 모든 항목이 같은 확률로 교체 대상이 되므로, 매 단계에서 각 항목이 계속 남을 가능성도 같게 유지됨
- 구현은 크기 k의 배열을 두는 방식으로 정리됨
- 새 항목마다 0부터 n 사이의 무작위 수를 생성함
- 무작위 수가 k보다 작으면 해당 인덱스의 항목을 새 항목으로 교체함
- 그렇지 않으면 새 항목을 버림
로그 수집 서비스에 적용하기
- 로그 수집 예시에서는
k=5로 설정해 한 번에 최대 5개 로그 메시지만 보관함 - 매초 선택된 로그를 로그 수집 서비스로 보내고, 이후 크기 5 배열을 비운 뒤 다시 시작함
- 이 방식은 실시간 로그 스트림 대신 일정 간격으로 로그 묶음을 보내는 덩어리진 패턴을 만듦
- 대신 전송된 로그 수는 임계값을 넘지 않고, 조용한 구간에서는 전체 로그와 전송 로그가 거의 같이 움직임
- 조용한 구간에서는 로그를 잃지 않고, 급증 구간에서는 초당 임계값보다 많은 로그를 보내지 않으며, 저장 공간도
k=5개 로그를 넘지 않음
가중치가 필요한 경우
- 일부 로그는 다른 로그보다 더 가치가 있을 수 있음
- 예를 들어 오류 로그는 모두 보관하고 싶을 수 있음
- 이런 경우에는 가중치 기반 저장소 샘플링 변형을 사용할 수 있음
- 저장소 샘플링은 처음에는 불가능해 보이는 스트림 샘플링 문제를 적은 메모리로 풀 수 있게 해주는 알고리듬임