여기까지는 얼마나 정확히 세는가의 이야기였습니다. 토큰 버킷은 다른 질문에 답합니다. 순간적으로 몰아 쓰는 것을 얼마나 허용할지입니다.
세는 것이 아니라 나눠 주는 것
한도 분당 1,000회 을 초당 약 17개로 바꿔 토큰을 채웁니다. 요청 하나가 토큰 하나를 가져가고, 없으면 거절합니다. 쓰지 않은 토큰은 쌓이지만 통 크기까지만 쌓입니다.
여기서 두 값이 분리됩니다.
| 값 | 정하는 것 |
|---|---|
| 채우는 속도 | 오래 보면 평균 몇 회까지 쓸 수 있는지 |
| 통 크기 | 한 번에 몰아 쓸 수 있는 최대량 |
앞의 방식들은 이 두 값이 한 숫자에 묶여 있었습니다. 분당 1,000회라고 하면 1,000회를 1초에 다 쓰는 것도 허용됩니다. 토큰 버킷은 평균은 유지하면서 순간 허용량만 따로 줄일 수 있습니다.
버스트를 허용하는 것이 오히려 필요하다
몰아 쓰기를 전부 막으면 정상 사용자가 불편해집니다. 목록을 불러온 뒤 상세를 20건 조회하는 화면은 짧은 순간에 21회를 씁니다.
통 크기 1,000: 하루 조용하던 키가 갑자기 1,000회를 쓸 수 있다
통 크기 50: 순간 몰아 쓰기를 50회로 제한한다. 평균은 그대로
통 크기가 곧 정책입니다. 서버 용량이 걱정이면 줄이고, 사용자 편의가 중요하면 늘립니다. 이 값을 요구사항에서 정하지 않으면 대개 한도와 같게 두는데, 그러면 앞의 방식들과 다를 바가 없습니다.
토큰을 실제로 채우지 않는다
초당 17번 값을 올리는 작업을 키마다 돌리면 활성 키 10만 개에 초당 170만 번의 쓰기가 됩니다. 그럴 필요가 없습니다.
마지막으로 만진 시각과 그때 남은 토큰 수만 저장한다
요청이 올 때 지난 시간을 재서 그동안 찼을 만큼을 더한다
통 크기를 넘지 않게 자른다
요청이 없는 동안에는 아무 일도 하지 않습니다. 시각과 개수 두 값만 있으면 언제든 지금 상태를 계산할 수 있습니다. 조용한 키에 비용이 들지 않는 것이 이 방식의 큰 장점입니다.
두 값을 함께 고쳐야 한다
시각과 토큰 수를 따로 읽고 쓰면 동시 요청이 서로를 덮습니다. 저장소가 여러 값을 한 번에 다뤄 주는 수단(원자적 실행이나 조건부 갱신)이 필요합니다. 다음 단계에서 이 문제를 다룹니다.
비용이 가장 작은 방식이다
| 방식 | 키마다 저장 |
|---|---|
| 시각 전부 | 통과 요청 수만큼 |
| 1초 버킷 60개 | 정수 60개 |
| 토큰 버킷 | 시각 하나와 개수 하나 |
정확도 이야기와 섞이지 않게 유의합니다. 토큰 버킷은 평균을 정확히 지키면서 순간 허용량을 따로 정하는 방식이고, 슬라이딩 윈도우 계열은 최근 구간의 개수를 정확히 세는 방식입니다. 무엇을 지켜야 하는지에 따라 고릅니다.