토큰 버킷 속도 제한

Token bucket rate limiting

일정 속도로 채워지는 토큰을 요청마다 소모해 API의 평균 요청률과 순간 버스트를 제어합니다.

···
html
<div class="demo"><div class="head"><b>TOKEN BUCKET</b><span id="status">allowed</span></div><div class="scene"><div class="request">REQUEST<br><strong>→</strong></div><div class="bucket"><div class="tokens" id="tokens"></div><small id="count">4 / 4 tokens</small></div><div class="response" id="response">200<br>OK</div></div><div class="foot"><span>refill: 1 token / tick</span><span>tap to send</span></div></div>
css
.demo{width:min(94vw,690px);height:min(86vh,310px);padding:clamp(10px,2.4vmin,20px);border:1px solid var(--line);border-radius:14px;background:var(--surface);display:flex;flex-direction:column;gap:clamp(7px,2vmin,14px);font:600 clamp(9px,2.6vmin,15px)/1.3 ui-monospace,monospace}.head,.foot{display:flex;justify-content:space-between;gap:8px}.head b{color:var(--accent)}.foot{color:var(--muted);font-size:.82em}.scene{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;gap:clamp(7px,2.5vmin,24px)}.request,.response{border:1px solid var(--line);border-radius:8px;padding:.7em;min-width:20%;text-align:center;background:var(--bg)}.request strong{font-size:1.6em;color:var(--accent)}.response{color:var(--accent);transition:color .2s,border-color .2s}.response.denied{color:var(--accent-3);border-color:var(--accent-3)}.bucket{width:38%;height:67%;min-height:72px;border:2px solid var(--accent);border-top:0;border-radius:0 0 10px 10px;padding:8px;display:flex;flex-direction:column;justify-content:end;align-items:center;gap:8px}.tokens{width:100%;display:flex;align-items:end;justify-content:center;gap:5%;height:65%}.token{background:var(--accent);width:20%;height:65%;border-radius:4px 4px 0 0;transition:opacity .2s,transform .2s}.token.empty{opacity:.14;transform:scaleY(.2);transform-origin:bottom}.bucket small{color:var(--muted);white-space:nowrap}
js
let tokens=4,tick=0;const holder=document.getElementById('tokens');holder.innerHTML='<i class="token"></i>'.repeat(4);function draw(){[...holder.children].forEach((el,i)=>el.classList.toggle('empty',i>=tokens));document.getElementById('count').textContent=tokens+' / 4 tokens'}function request(){const ok=tokens>0;if(ok)tokens--;document.getElementById('status').textContent=ok?'allowed':'rate limited';const response=document.getElementById('response');response.innerHTML=ok?'200<br>OK':'429<br>WAIT';response.classList.toggle('denied',!ok);draw()}draw();request();const timer=setInterval(()=>{tick++;if(tick%2===0)tokens=Math.min(4,tokens+1);request()},750);document.querySelector('.demo').addEventListener('pointerdown',()=>{clearInterval(timer);request()})

토큰 버킷은 최대 용량까지 토큰을 일정 속도로 보충합니다. 요청 하나가 토큰 하나를 쓰며, 버킷이 비면 요청을 거절하거나 기다리게 합니다. 남은 토큰을 모아 두면 짧은 요청 폭주를 허용할 수 있습니다.

데모에서는 요청이 들어올 때 토큰이 줄고 잠시 뒤 다시 차오릅니다. 실제 API에서는 사용자를 식별하는 키, 분산 저장소의 원자적 갱신, 거절 시 재시도 안내도 함께 설계해야 합니다.

언제 쓰나

공개 API의 남용을 줄이면서 정상 사용자의 짧은 요청 묶음은 허용할 때 사용합니다.

페이지로 열기 ↗