디바운싱, 쓰로틀링 모두 클라이언트에서 대량의 이벤트 요청을 최적화 하기 위한 용도로 많이 사용된다. 이를 어떻게 최적화 하냐에 따라서 나타나는 비용이 달라지게 되는데 이를 사용해야하는 이유와 사용처를 공부해보자.
디바운싱이란 사용자가 여러번의 요청을 보내는 경우에 일정시간을 두어, 맨 처음 혹은 마지막에 한 번만 실행하고, 나머지 일정시간 동안에는 이벤트들을 무시하는 기법이다.
여러가지 이벤트를 만들다보면, 다수의 이벤트가 실행될 수 있는 이벤트들이 존재한다. key 이벤트 혹은 drag 이벤트, resizing 관련 이벤트, scroll 이벤트 등 다수의 이벤트를 만드는데 용이한 이벤트들이 있다. 다수의 이벤트를 요청하는 것은 당연히 많은 메모리가 필요하며, 그게 만약 서버에 요청하는 이벤트라면, 그리고 사용자가 많은 서비스라면 엄청난 서버 비용이 발생하게 될 것이다.
디바운싱은 주로 검색 관련 이벤트에서 자주 쓰이는 편이다. 사용자가 원하는 결과는 모든 타이핑이 끝난 경우가 많기 때문에, 타이핑 마다 이벤트를 보내기 보다는 타이핑 이벤트 종료 후 일정 기간이 지난 뒤에 한번만 이벤트 요청을 보내도록 하는 것이다.
js
에서는 setTimeout
과 clearTimeout
을 활용하여 구현하는 것이 가능하다.
let checkValid
function inputValidHandler() {
// 기존 checkValid의 함수를 삭제하고.
clearTimeout(checkValid)
// props 값을 토대로, 다시 checkValid를 생성한다.
// 그럼 가장 마지막 키업 이벤트만 실행되게 된다.
checkValid = setTimeout(() => {
let errorComponent = ''
for (let func in onValid) {
if (!onValid[func].func(inputRef.current.value, meta)) {
errorComponent = <ErrorComponent text={onValid[func].message} />
break
}
}
console.log('유효성 체크')
setErrorComponent(errorComponent)
onData({
value: inputRef.current.value,
valid: errorComponent === '' ? true : false,
})
}, 200)
}
...
<input
className={`${classes.input} ${colorInputClass}`}
name={id}
type={type}
placeholder={placeholder}
onKeyUp={inputValidHandler}
ref={inputRef}
tabIndex="-1"
disabled={disabled}/>
디바운싱도 어떤 시점에 이벤트를 호출하는 지에 따라 leading edge
와 trailing edge
로 나뉜다. 개발 환경마다 사용하는 것을 다르지만, 사례는 trailing edge
가 많으며 일반적으로 디바운싱을 적용했다는 의미는 trailing edge
를 말한다.
leading edge
: 연속적으로 들어오는 입력 가운데 처음의 입력만 실행한다.trailing edge
: 연속적으로 들어오는 입력 가운데 마지막 입력만 실행한다.쓰로틀링은 사용자가 다수의 이벤트를 요청하는 경우 일정한 시간마다 한번씩 실행하도록 하는 기법이다.
쓰로틀링은 주로 일괄처리가 아닌 반복적인 요청을 주기적으로 사용해야 하는 경우에 좋다. 대표적으로 무한스크롤이 있다. 스크롤 이벤트 특성상, 매우 짧은 시간에 요청이 여러번 일어날 수 있으면서도 현재 스크롤 위치를 주기적으로 체크할 필요가 있다. 이 때 일정기한을 두어, 이벤트 호출을 제어하는 것 만으로도 서비스 성능을 크게 높일 수 있다.
js
에서는 동일하게, setTimeout
을 활용하여 구현이 가능하다.
// throttleCheck를 통해서 throttle 상태가 아닐 때만 callback을 실행합니다.
// setTimeout은 timer id를 반환하기 때문에 호출 시점에 throttleCheck에 숫자가 할당되어 true 값으로 여겨집니다.
// callback의 실행이 완료되고 throttleCheck를 false로 바꾸어 다시 함수 호출이 가능한 상태로 바꿉니
export const throttling = () => {
let throttleCheck: any;
return {
throttle(callback: any, milliseconds: number) {
if (!throttleCheck) {
throttleCheck = setTimeout(() => {
callback();
throttleCheck = false;
}, milliseconds);
}
},
};
};
최적화를 위해 꼭 어떤 이벤트에는 쓰로틀링을 써야한다, 어떤 이벤트에는 디바운싱을 써야한다 등의 정답은 없다.
네비게이션을 예로 들어보자면, 드래그를 통해 주변 편의점을 찾는 API 이벤트를 만드는 경우, 쓰로틀링을 적용하면 일정 주기마다 편의점을 보여주니 사용자에게 장점이 되지만, 서버는 주기적으로 반복하여 응답을 해줘야 할 것이다.
디바운싱을 적용하면, 일괄처리하여 마지막 드래그 환경의 편의점만 보여주면 되니 서버에는 도움이 되지만, 연속적인 드래그 이벤트 동안에는 편의점이 렌더링 되지 않을테니 사용자에게 불리할 수 있다.
두 기법을 이해하고, 현재 서비스가 추구해야할 것을 기준으로 알맞다고 생각되는 기법을 사용하는 것이 좋다. 결국 둘다 안 사용하는 것보다는 좋은 결과가 나올 것이니, 둘 중 하나를 사용했다면 분명 서비스에 기여하는 코드임이 분명하다.
참고
[10분 테코톡] 자스민의 디바운싱과 쓰로틀링
[개발면접3분] 쓰로틀링 vs 디바운스
자바스크립트의 디바운싱과 쓰로틀링
scroll 이벤트를 활용한 React 무한 스크롤 구현(feat. throttle)
[JS] Debounce와 Throttle
Debounce and throttle