Frontend
디바운스와 스로틀
개요
전공 시간에 분명 배웠다
컴퓨터공학을 전공하면서 디바운스와 스로틀에 대한 개념을 배운 적이 있었다. 아두이노 하드웨어 버튼의 입력 신호에 노이즈가 발생하는 과정에서 해결책으로 디바운싱이라는 개념을 접했던 것으로 기억한다. 아직 기억하고 있는 것을 보니 재밌게 배웠던 것 같다.
금일 프론트엔드 개발자 면접을 보았다. API 요청에 debounce 함수를 적용했다는 포트폴리오 내용에 연계하여 디바운싱과 스로틀링의 차이점에 대한 질문을 받았다. 배운 것이 떠올랐으나 만족스럽게 답변하지 못한 것 같아 개념을 확실하게 되짚고, 웹 개발에서 디바운싱과 스로틀링이 어떻게 사용되는지, 자바스크립트에서는 어떻게 구현하는지 정리할 필요성을 느꼈다.
디바운스 (debounce)
채터링 또는 바운싱 또는 노이즈
채터링(chattering)이란, 전자 회로 내의 스위치나 계전기의 접점이 붙거나 떨어질 때, 기계적인 진동에 의해 매우 짧은 시간 안에 접점이 붙었다가 떨어지는 것을 반복하는 현상을 말한다.
우리가 버튼을 물리적으로 누를 때, 바로 상태가 바뀌지 않는다. 기계적 진동으로 인해 매우 짧은 시간 동안 약간의 버벅임이 발생하는데, 이것을 전자 회로에서 채터링(chattering) 현상이라고 하며, 바운싱(bouncing), 노이즈(noise)도 같은 의미를 가진다.
위 그림에서 바운스 타임(Bounce Time)이 바로 바운싱이 발생하는 시간을 말한다.
디바운싱
만약 전자회로를 다루는 프로그래밍이라면 하드웨어 저항기를 달아주거나 코딩으로 반복적인 신호를 무시하는 코드를 작성하면 된다. 예를 들어, 이전과 상태가 같은지를 확인하는 과정에 ‘임의의’ 딜레이를 주어서 딜레이 시간 이후 스위치의 상태를 확인하는 방법이다.
이걸 바로 디바운스(debounce), 디바운싱(debouncing)이라고 한다.
아두이노 프로그래밍 관점에서 디바운싱 코드를 살펴보자.
// 아두이노 코드 int debounceDelay = 50; unsigned long lastDebounceTime = 0; int buttonstate = 1; int lastButtonState = 1; void loop(){ int reading = digitalRead(BUTTONPIN); if (reading != lastButtonState) { lastDebounceTime = millis(); } if ((millis() - lastDebounceTime) > debounceDelay) { if(reading!=buttonState){ buttonSate=reading; } } lastButtonState = reading; }
- 누르지 않은 상태인 이전 상태 변수(lastButtonState)
- 누른 후에는 현재 상태 변수(reading)와 다른 상태로 변화
- 상태 변화가 일어날 때 그 변화가 일어난 시간을 기록
- 변화가 일어나는 간격이 50ms 보다 짧을 때의 변화는 무시
중요한 부분은 임의의 딜레이만큼 우리는 상태 변화를 무시하겠다는 말이다.
웹에서의 디바운싱
모던 웹사이트에서는 사용자와 웹 간의 상호작용이 매우 잦으며, 이에 따른 이벤트 발생이나 API 요청도 자주 일어난다. 혹시나 비용이 많이 드는 곳에서 모든 입력에 대한 응답을 주도록 프로그래밍되어 있다면 매우 큰 낭비가 아닐 수 없다.
예를 들어, 사용자가 검색어를 입력하는 입력 칸에 키를 누를 때마다 입력 이벤트가 발생한다면, 이를 처리하는 함수가 빠르게 연속해서 여러 번 실행되어 성능 문제를 야기할 수 있다. 그게 만약 API 요청이었다면 그에 따른 비용은 어마어마할 것이다.
웹 개발에서의 디바운싱은 어떻게 이해하면 될까?
디바운싱
- 계속해서 호출되는 함수 중에 마지막 함수만 실행시키기
- 무수히 발생하는 이벤트 중 마지막 이벤트만 발생시키기
- 이전 이벤트를 무시하기
자바스크립트의 setTimeout API를 사용해서 디바운스 코드를 작성해보자.
function debounce(func, delay) { let timer; return function() { const args = arguments; clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, delay); } }
- 매개 변수로 제공하는
func
의 디바운싱된 버전을 반환하는 고차 함수 형태로 작성
let timer
는 타임아웃 ID를 저장하기 위한 변수
- 반환된 함수는 전달된 인수를 캡처하고 다음을 수행
- clearTimeout()으로 이전에 설정된 시간 초과를 지움
setTimeout(() => func.apply(this, arg), delay);
를 사용하여 새로운 시간 제한을 설정 (그동안 새로운 이벤트가 발생하지 않으면 지정된 딜레이 이후 함수를 호출func.apply(this, arg)
는 원래 함수가 올바른 this 컨텍스트와 인수로 호출되도록 보장
(apply를 통해 this 바인딩을 해준다 → 화살표 함수를 사용해도 된다)
- 자바스크립트 라이브러리 중 유명한 underscore나 lodash에도 있다.
const handleSearch = debounce(() => { // Search handling logic }, 300);
- 검색어를 마구 입력해도 이벤트 발생에는 300ms의 딜레이가 주어짐
스로틀(throttle)
하드웨어 스로틀링
스로틀(throttle) 또는 스로틀링(throttling)의 영문 뜻은 ‘조절판’, ‘목구멍’을 의미하고, 전자공학 또는 컴퓨터공학에서는 하드웨어 과부하를 막기 위해 성능을 낮추고 억제하는 것을 의미한다.
chatGPT는 throttle을 한국어로 ‘제한’이라고 번역하기도 한다!
스로틀링의 개념을 대학교에서 배우기 전에 가장 많이 접할 수 있었던 분야는 바로 PC다. 컴퓨터의 CPU나 GPU 온도가 과도하게 높아질 경우, 하드웨어의 손상을 막기 위해 클럭이나 전압을 낮추어서 기기의 온도를 떨어뜨리는 것이다. 저사양 PC로 고사양 게임을 할 때 ‘스로틀링이 걸린다’는 표현을 자주 쓰곤 했다.
이외에도 오토바이, 전동차, 비행기에서는 엔진의 과열을 막기 위해 출력을 조절하는 장치를 스로틀이라고 한다. 공통된 것은 바로 성능을 의도적으로 억제한다는 것이다.
웹에서의 스로틀링
자바스크립트에서 스크롤 이벤트를 다루어 본적이 있다. 마우스 휠을 살짝만 굴려도 무수히 많은 스크롤 이벤트가 발생한다. 만약 스크롤 이벤트가 발생했을 때 API 요청을 한다면? 이 또한 많은 비용을 야기할 것이다.
웹 개발에서 디바운싱과 스로틀링은 함께 등장하는 개념이다. 디바운싱이 무수히 호출되는 함수들 중에서 임의의 딜레이 시간 동안 모든 호출을 무시하고 마지막으로 호출된 함수만 실행한다면,
스로틀링은 무수히 호출되는 함수들 중에서 첫 함수를 실행하고 임의의 딜레이 동안은 무시한다. 그리고 딜레이 시간이 지나면 다시 이것을 반복한다.
스로틀링
- 계속해서 호출되는 함수 중에서 첫 함수를 실행하고 나머지 무시, 그리고 반복
- 계속해서 발생하는 이벤트들을 일정한 간격으로 한 번만 발생시키기
- 후속 이벤트를 무시하기
스로틀링 또한 자바스크립트의 setTimeout API를 이용해서 구현해보자.
function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }
- 매개 변수로 제공하는
func
의 스로틀링된 버전을 반환하는 고차 함수 형태로 작성
inThrottle
은 스로틀 변수로 현재 함수가 스로틀 기간에 있는지 판단하는 변수
- 반환되는 함수는
...args
를 사용해서 인수를 캡처하고 다음을 수행 - inThrottle이
false
면func.apply(this, args)
를 호출하고true
로 설정 setTimeout(() => inThrottle = false, limit)
으로 시간 제한을 시작- 지정된
limit
시간이 끝나면 다시inThrottle
을false
로 재설정
const handleScroll = throttle(() => { // Scroll handling logic }, 200);
- 스크롤을 마구 움직여도 이벤트 발생 주기는 200ms가 됨
어디에 사용해야 할까?
디바운스와 스로틀은 모두 이벤트가 자주 발생해서 성능이 저하되거나 서버에 과부하를 줄 수 있는 경우를 막기 위해 사용한다. 어떤 기능이나 이벤트에 적용하면 좋은지 대표적인 예시들을 알아보자.
대표적인 디바운스 사용처
지정된 시간 이후에만 이벤트가 중지된 이후로 함수 실행을 제한하려는 경우에 사용하자.
- 검색창 입력 이벤트 처리 (search input field)
- 브라우저 창 크기 조절 (window resize)
- 폼 유효성 검사 (form validation)
대표적인 스로틀링 사용처
이벤트 발생 빈도에 관계없이 지정된 시간 간격 내 함수가 최대 한 번만 호출되도록 하려는 경우에 사용하자.
- 스크롤 이벤트 (scroll event)
- 마우스 이동 이벤트 (mouse move event)
- 버튼 클릭 (button click)