React Concurrency mode

김규빈·2023년 9월 11일
2
post-custom-banner

React 18+ Concurrency Mode 도입 !

1. Concurrency mode

JavaScript는 기본적으로 싱글 스레드 언어입니다. 이것은 주로 브라우저의 메인 스레드에서 실행되며, 이 스레드는 CSS 애니메이션, 사용자 입력 처리, 그리고 JavaScript의 실행 등 다양한 작업을 담당합니다. 따라서 메인 스레드에서 긴 JavaScript 작업이 실행되면, 웹페이지의 반응성이 떨어질 수 있습니다.

Javscript의 한계를 극복하고자 React18+ 에서 Concurrency mode 지원을 시작하였습니다. Concurrency란 말 그대로 동시성인데, 싱글 스레드 환경에서 동시에 작업을 한다는 말은 기술적으로 불가능한 말입니다. 작업을 동시에 한다면 무엇이 좋을까요?

Concurrency mode의 목표는 오직 사용자 경험의 개선이라고 말하고 있습니다. 브라우저의 UI 스레드는 CSS, 사용자의 입력, Javascript로 인한 화면의 변화를 책임지고 있는데, 최고의 사용자 경험을 지원하기 위해 최신 디바이스들은 1초에 60프레임(60fps)를 지원하고 있습니다. 그러기 위해 프레임당 16.67 ms 이하로 렌더링이 되어야 하며 브라우저가 다른 UI 작업도 실행한다는 것을 고려할 때 실질적으로는 그 이하인 10ms 정도로 렌더링이 되어야 합니다.

하지만 이전의 React에선 이제까지 React가 reconciliation(변경된 데이터와 현데이터의 diff를 파악하고 변경을 업데이트하는 단계) 단계에 들어서면 해당 단계가 끝난 후에야 메인 스레드에 대한 컨트롤을 반환하게 됩니다. 이때 브라우저의 메인 UI 스레드는 이 단계 동안 사용자의 입력 같은 다른 작업을 진행할 수 없게 됩니다. block 당한 유저는 사용자 경험이 떨어지게 될 테고 이는 Javascript의 한계 입니다.

React의 내부 diff 알고리즘이 아무리 효율적일지라도 웹앱 크기가 커지고 내부의 DOM 트리가 복잡해진다면 화면에 랜더링 되는 프레임 횟수가 줄어들고 jank나 응답 시간이 길어지게 됩니다.

보통의 개발자들은 이 시간에 스피너나, 스켈레톤 등을 보여 사용자에게 인터렉션이 되고 있다는 사실을 알리고 유저 이탈을 최대한 막아보고자 수많은 비용을 만들어 내고 있습니다.

그렇다면 React 팀은 이 block 되는 상황을 어떻게 극복하고자 했을까요?

React팀은 Concurrency mode를 통해 task를 나눠 작은 단위로 만들어 완료시 까지 상대적으로 짧은 작업 시간을 만들고, taskcontext 전환을 통해 여러 task를 동시에 진행하고 있다고 느끼게 만들었습니다. 이런 작업을 통해 React는 4가지의 이점을 얻었습니다.

  • 메인 스레드를 블럭하지 않음
  • 여러 작업을 동시에 진행하게 보일 수 있으며 우선순위에 따라 순서가 변경 가능함
  • 결과를 DOM에 커밋 하지 않고 트리의 일부 렌더만 가능함 (빠른 반응성)
  • 사용자의 입력 같은 중요한 작업은 우선순위를 부여하여 먼저 실행할 수 있음 (2번과 같은 맥락)

2. 기존 방식

React에서의 기존 렌더링 프로세스를 살펴 봅시다.

메인 스레드에서 작업을 시작하면 작업을 멈출 수 없기 때문에 단계별로 순서를 기다렸어야 했습니다. 여기서 문제는 만약 Run javascript(노란색)의 작업이 무거운 연산이라 긴 작업 시간이 걸린다면 Rendering 과정은 이전 작업이 완료될 때까지 기다려야 합니다.

3. 변경 방식

이제 동시성을 통해 작업을 작게 잘게 나누고 contenxt 전환을 통해 Run javascript 작업이 끝나면 완료된 부분에 한 해 랜더링 과정을 작게 진행하고, 다시 Run javascript의 작업을 진행하는 식으로 기다리지 않고 동시에 진행이 되는 것처럼 보일 수 있게 작동하게 됩니다.
(빨간 화살표는 run javascript의 작게 나눈 task, 파란 화살표는 Rendering의 작게 나눈 task 과정이라고 비유할 수 있겠습니다.)

이런 과정을 통해 react 팀은 Concurrency(동시성)을 해결(?개선) 했고 이 변화를 18+ 버전에서 공식 지원하게 되었습니다.

4. DeferredValue

여기서 추가로 React 18+ 에서 여러 가지 hooks들이 추가되었는데, 그중 DeferredValue를 살펴보면, UI를 일부 업데이트를 연기할 수 있는 hooks라고 소개가 되어 있습니다.

어떻게 업데이트를 연기할 수 있는지 감이 오시나요?

DeferredValue를 사용하게 되면 task를 잘게 나누고 여러 task queue중 우선순위를 부여하고 같은 레벨의 task중 우선순위에 따라 처리하는 과정으로 진행하면서 업데이트를 연기할 수 있습니다.

추가로 React 팀원인 Dan Abramov의 말에 따르면 useDeferredValue는 상태를 props로 받는 등 제어할 수 없을 때 사용하는 것을 권장하고 있습니다.

추가로 업데이트된 hooks의 내부 동작까지 유추가 가능해졌습니다.

5. 비교

Concurrency mode의 자료들을 보면 debouncethrottle 과 많이 비교들을 하고 있습니다.
Concurrency의 메커니즘을 제대로 이해했다면 두 방식과 완전히 다른 방식으로 작동하고 있다고 공감할 것입니다.

debounce는 시간 안에 일어나는 이벤트를 무시하고 가장 마지막의 이벤트만 시간이 되었을 때 실행시키는 방법이고,
throttle은 여러 번의 이벤트를 무시하고 시간마다 한 번만 실행하도록 하는 방법입니다.
concurrency modetask를 잘게 나눠 싱글스레드의 단점을 극복하고자 context 전환을 통해 동시에 작업을 진행시키게끔 보여지게 하기 때문에 내부 동작은 완전히 다른 방향이라고 이해할 수 있습니다.

6. HTTP/2.0, HTTP/3.0

그런데 concurrency mode를 알아보면서 저번에 스터디 했던 http 프로토콜이 어떤 방식으로
1.1을 거쳐 2, 3로 작업 방식을 개선하고 있었는지가 떠올랐습니다. Http3는 프로토콜을 TCP에서 UDP로 변경을 통해 이로 인한 이점과 기타 다른 방법으로 성능을 개선하였지만,

http 통신에 있어서 HOL 차단 해결HTTP/3 뿐만 아니라 HTTP/2의 주요 동기 중 하나라고 소개하고 HOL 대해 고민하고 이걸 기술적으로 극복했는지에 대한 소개에 대한 글이었습니다.

http3HOL을 해결하기 위해 선택한 프로토콜 Quic의 작동을 보면 TCP 프로토콜과 살짝 다른 점을 볼 수 있습니다. Quic 프로토콜 프로세싱을 잘 살펴보면 작업을 잘 개 쪼개고 stream id가 같은 작업들을 다른 작업과 작은 task 단위로 나눠 여러 작업을 번갈아 가며 데이터를 처리하고 있습니다.

React 업데이트 한 Concurrency mode가 개선했던 방식과 매우 유사하지 않은가요?

HTTP의 개선은 여러 전송을 병렬적으로 진행하게 보이게 task를 진행하고 완료된 작업은 먼저 알려줌으로써 HOL 문제를 우회적으로 처리하게 끔 보이게 구현하는 것. 이 과정을 멀티플렉싱이라고 소개하고 있습니다.

멀티 플렉싱의 장점은

  • 첫째, 점진적으로 처리/렌더링할 수 있는 일부 파일은 멀티플렉싱을 통해 이익을 얻습니다 (concurrency mode)
  • 둘째, 위에서 설명한 것처럼 파일 중 하나가 다른 파일보다 훨씬 작은 경우 다른 파일을 너무 지연시키지 않고 더 일찍 다운로드되므로 유용할 수 있습니다. (concurrency mode)
  • 셋째, 다중화를 통해 응답 순서를 변경하고 우선순위가 높은 응답에 대해 낮

7. 결론

React 18+의 Concurrency Mode는 웹 개발의 핵심 문제 중 하나인 사용자 경험의 지연을 극복하기 위한 큰 도약입니다. JavaScript의 싱글 스레드 특성은 사용자 입력, 애니메이션, 그리고 데이터 처리 등 다양한 작업을 동시에 처리해야 하는 웹 애플리케이션에서 병목 현상을 초래하곤 했습니다.

Concurrency Mode는 이러한 문제를 "동시성"의 개념을 통해 해결하려 합니다. 실제로는 여러 작업을 동시에 처리하는 것이 아니라, 작업을 작은 단위로 분할하고 필요에 따라 중단 및 재개하는 방식을 통해 사용자에게는 동시에 처리되는 것처럼 느껴지게 합니다.

이와 유사한 개념은 HTTP/2.0HTTP/3.0의 개선에서도 볼 수 있습니다. 특히 HTTP/3.0에서는 TCP를 대체하는 QUIC 프로토콜을 도입하여 "멀티플렉싱"을 통해 효율적인 데이터 전송을 가능하게 했습니다.

두 기술 모두 작은 작업 단위로 데이터를 분할하고 우선 순위를 정하여 효율적으로 처리하는 방식에 중점을 두고 있습니다. 이런 접근 방식은 웹의 복잡성이 계속 증가하는 현대에 있어서, 사용자 경험을 최적화하는 데 있어 필수적인 방향으로 보입니다.

[참고]
https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react
https://calendar.perfplanet.com/2020/head-of-line-blocking-in-quic-and-http-3-the-details/
https://react.dev/reference/react/useDeferredValue
https://deview.kr/data/deview/session/attach/1_Inside%20React%20(%E1%84%83%E1%85%A9%E1%86%BC%E1%84%89%E1%85%B5%E1%84%89%E1%85%A5%E1%86%BC%E1%84%8B%E1%85%B3%E1%86%AF%20%E1%84%80%E1%85%AE%E1%84%92%E1%85%A7%E1%86%AB%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB%20%E1%84%80%E1%85%B5%E1%84%89%E1%85%AE%E1%86%AF).pdf

profile
FrontEnd Developer
post-custom-banner

2개의 댓글

comment-user-thumbnail
2023년 9월 15일

읽기 좋았어요
감사합니다

1개의 답글