
타사의 그리드 라이브러리를 구입해서 잘 쓰고 있던 어느 날
4만 건 이상의 그리드 데이터 행을 전체선택 했는데 그대로 브라우저가 멈춰버렸다.
순간 이게 뭐지? 싶었지만 기다려봤는데 거의 1분 이상이 지나고 나서야 행 전체 선택이 적용이 되더라..
위 사진을 보면 내역이 약 44000건 인데,
빨간색 화살표로 표시한 체크를 클릭(전체 선택 하게 되는거임)하면
빨간색 박스로 표시한 각 행의 체크박스들에 체크 모양이 들어가면서,
각 행의 checked 라는 값도 0에서 1로 바뀌게 된다.
보아하니 대량의 데이터들을 다루면서 부하가 걸린거 같고
사용자들에게 크리티컬한 이슈라 전체선택을 할 때 적용되는 메서드를 뜯어보게 됐다.
밑에는 전체선택을 하게 되면 적용되는 그리드 행 전체선택 메서드이다.
grid.ItemAllChecked(function (grid, checked) {
grid.commit();
var checkVal = checked ? '1' : '0';
for (var i = 0, len = grid.getItemCount(); i < len; i++) {
grid.setValue(i, "SELECTED", checkVal);
}
grid['_onChangeHeaderCheckBox']("SELECTED", checked);
});
전체 선택을 했을 때 브라우저에 부하가 많이 걸린 이유는,
grid.getItemCount()로 그리드의 모든 아이템을 순차적으로 확인하고
값을 변경하는 방식이 비효율적으로 작용하기 때문인거 같았다.
이 과정에서 여러 번 setValue() 메서드를 호출하면 DOM을 계속 업데이트하게 만들어 성능 문제를 일으킬 수 있다.
특히, 데이터가 많을 때는 이 과정이 매우 느려진다.
즉, 해당 코드를 보면 모든 행을 한 번에 처리하려 했고
수천 개의 데이터를 한꺼번에 변경하려고 해서 브라우저가 멈추거나 응답이 늦어지는 문제가 발생한거다.
해당 문제를 해결하기위해 기존 처리 방식을 바꿔봤다.
모든 행을 한 번에 처리하는 것이 아니라, 작은 덩어리(Chunk)로 나누어 순차적으로 처리하는 방식으로 말이다.
비유로 이해해보자면,
🔴 기존 처리 방식:
❌ “1000권의 책을 한 번에 옮기려다가 무거워서 넘어짐”
✅ 바뀐 처리 방식:
✔️ “100권씩 나눠서 10번에 걸쳐 옮기기”
이렇게 하면 한 번에 너무 많은 부담이 가지 않고, 시스템이 멈추지 않으며 원활하게 동작할 수 있다.
🔴 기존 방식 (성능 문제 발생)
grid.ItemAllChecked(function (grid, checked) {
grid.commit();
var checkVal = checked ? '1' : '0';
for (var i = 0, len = grid.getItemCount(); i < len; i++) {
grid.setValue(i, "SELECTED", checkVal);
}
grid['_onChangeHeaderCheckBox']("SELECTED", checked);
});
👉 모든 데이터를 한꺼번에 처리 → 브라우저 멈춤 🚨
1️⃣ 기존 코드의 문제점 → 이벤트 루프 영향
💡 한 번에 수천 개의 데이터를 처리하면, 메인 스레드가 막혀서 UI가 멈춘다.
• JavaScript는 싱글 스레드(Single Thread) 기반이므로, 한 번에 너무 많은 작업을 하면 이벤트 루프(Event Loop)가 멈춰버림.
• 기존 코드에서는 for 문을 돌면서 모든 행을 한 번에 업데이트했기 때문에, 브라우저가 긴 시간 동안 다른 작업을 처리할 수 없었음.
“식당에서 주문을 받는 사람이 손님 100명의 주문을 한 번에 다 적고 주방에 넘기면,
그동안 다른 손님이 기다려야 하는 것과 같다.”
2️⃣ 해결 방법 → Chunk 처리 & 비동기 처리 적용
💡 작업을 작은 덩어리(Chunk)로 나누고, 중간중간 쉬면서 이벤트 루프를 계속 돌려준다.
🟢 개선된 방식 (Chunk 처리 적용)
grid.ItemAllChecked(async function (grid, checked) {
var checkVal = checked ? '1' : '0';
var itemCount = grid.getItemCount();
var chunkSize = 100;
var delay = 10;
async function processChunk(startIndex) {
for (let i = startIndex; i < Math.min(startIndex + chunkSize, itemCount); i++) {
grid.setValue(i, "SELECTED", checkVal);
}
await new Promise(resolve => setTimeout(resolve, delay));
if (startIndex + chunkSize < itemCount) {
await processChunk(startIndex + chunkSize);
}
}
await processChunk(0);
grid.commit();
grid['_onChangeHeaderCheckBox']("SELECTED", checked);
});
👉 100개씩 처리 & 중간에 잠시 쉬어가기 → UI 멈춤 방지 & 부드러운 동작 처리
1. 청크(Chunk) 처리
• 데이터를 chunkSize = 100 단위로 나눠서 처리.
• 한 번에 너무 많은 데이터를 처리하지 않도록 조절하여 UI가 멈추는 현상을 방지.
2. 이벤트 루프 활용 (비동기 처리)
• await new Promise(resolve => setTimeout(resolve, delay));
• 매 청크 처리가 끝날 때마다 약간의 delay를 추가하여 이벤트 루프의 블로킹을 방지.
• 이를 통해 UI가 멈추지 않고 부드럽게 동작할 수 있도록 개선.
🔹 Chunk 처리 적용 후 변화
• 한 번에 100개씩만 업데이트하고, 중간에 setTimeout을 사용해 이벤트 루프에 제어권을 넘겨줌.
• 이벤트 루프가 다른 UI 작업(예: 애니메이션 등)을 처리할 수 있도록 함.
• 결과적으로, 사용자는 UI가 멈추지 않고 자연스럽게 동작하는 느낌을 받음.
📌 비유
“손님이 많을 때, 종업원이 10명씩 나눠서 주문을 받고 주방에 넘기면,
다른 손님도 기다리는 동안 메뉴판을 보면서 주문할 준비를 할 수 있는 것과 같다.”
3️⃣ 기대 효과 → UI 성능 최적화
💡 기존 코드와 비교하여 UI 응답성이 개선됨.
기존 코드 (동기 처리) | 개선 코드 (비동기 & Chunk 처리) | |
처리 방식 | 모든 데이터를 한 번에 변경 | 100개씩 나누어 비동기 처리 |
문제점 | 브라우저가 응답하지 않음 (UI 멈춤) | UI가 자연스럽게 동작 |
이벤트 루프 | 다른 작업을 처리할 수 없음 | 이벤트 루프가 원활하게 동작 |
사용자 경험 | “버튼 누르자마자 멈춘 것 같음” | “부드럽게 선택됨” |
결론
“처음에는 모든 데이터를 한 번에 처리했기 때문에 브라우저가 멈추는 문제가 있었다.
이를 해결하기 위해 데이터를 작은 덩어리(Chunk)로 나누어 비동기 처리 하고,
setTimeout()을 활용하여 이벤트 루프(Event Loop)가 UI 업데이트를 방해하지 않도록 했다.
이 덕분에, UI가 부드럽게 동작하며 사용자 경험이 향상되었다.”
'개발 > JavaScript' 카테고리의 다른 글
실행 컨텍스트 (Execution Context) (1) | 2024.09.06 |
---|---|
Set 객체: 값의 고유한 집합 저장소 (1) | 2023.12.17 |
개인적인 This 정리 (1) | 2023.09.24 |
클래스를 이용한 모듈화 (0) | 2023.07.01 |
생성자 함수와 프로토타입의 차이? (0) | 2023.06.23 |
개발 블로그
포스팅이 좋았다면 "좋아요❤️" 누르기 !