지난 포스트에서 언급한 성능 이슈의 원인이 되었던 Material UI 사용시 style tag가 늘어나는 현상에 대해 삽질한 내용을 적어보려고 합니다.

시간이 많지 않은데 같은 이슈를 가지신 분이 계신다면 결론 부분만 읽으시면 도움이 될 것 같습니다!

문제 상황 🤷‍♂️

Material UI를 사용하여 개발을 하던 중 원하는 Interaction이 처리되는데 1초 이상의 시간이 걸리는 것을 확인했습니다. 처음엔 렌더링과 관련된 이슈인가 하여 렌더링 최적화 관점에서 접근했으나 여전히 문제가 남아있었고 이런 저런 분석을 하던 중 head 태그 내부에 비어있는 style 태그가 굉장히 많이 존재하는 것을 확인했습니다.

data-meta 속성에 Box와 makeStyles라는 값을 가진 비어있는 style 태그들이 다수 존재하는 것으로 보아 해당 컴포넌트와 API를 제공하는 Material UI와 관련된 문제일 것이라 예상했습니다.

이후 Interaction을 하며 style 태그를 확인하니 Interaction이 발생할 때마다 style 태그의 값들이 늘어나고 있는 것을 확인했습니다. 또한 개발자 도구의 Performance를 활용하여 측정을 해보니 Scripting을 하는데 굉장히 많은 시간을 필요로 하는 것을 확인했습니다. 따라서 늘어나는 style 태그가 분명 성능에 영향을 끼치고 있으며 반드시 개선해야 할 문제라고 생각해 여러 방법과 조사를 통해 문제 해결을 시도했습니다.

1. 문제 상황 찾아내기 👀

Interaction이 발생할 때마다 새로운 컴포넌트가 추가되었기 때문에 새로운 컴포넌트가 추가 될 때마다 해당 이슈가 발생한다고 생각했습니다. 개발자도구의 Element 탭을 보며 분석해보니 다른 컴포넌트들에서는 발생하지 않지만 Box 컴포넌트와 makeStyles API와 관련된 컴포넌트에서만 해당 이슈가 발생하는 것을 확인했습니다.

글로는 굉장히 짧지만 정확히 원인을 몰랐기에 굉장히 많은 시도를 해봤습니다. 최신 기술 중 하나인 apollo client의 useReactiveVar hook이 원인인가 싶어 해당 hook을 다 제거해보기도 하고, 렌더링이 2번씩 되는건가 싶어 렌더링 횟수를 확인해보기도 하는 등 제가 코드를 잘못 짰다고 생각하고 생각하며 원인을 찾았던 것 같습니다. 불행인지 다행인지 제 문제는 아니었습니다..😅

2. 깃허브 이슈와 공식문서 파헤치기 🧐

언제나 그렇듯 처음엔 구글링이었습니다. 이런 저런 키워드를 합성하여 나와 같은 이슈를 가진 사람이 있나 찾기 시작했습니다. 굳게 믿었던 StackOverflow가 저를 외면했고 Box나 makeStyles에 대해 공식문서를 천천히 읽어보기 시작합니다. 역시 해당 이슈에 대한 특별한 설명을 찾지 못했습니다.

그 후 Material UI 깃허브의 이슈탭에 들어가 style 태그와 관련된 이슈들을 찾아봤습니다. 비어있는 수많은 style 태그들에 대한 이슈가 굉장히 많은 것을 확인하게 되지만 진행중인 프로젝트처럼 style 태그가 늘어나는 이슈를 가진 사람은 없었습니다. 정확히는 딱 한 명 존재하긴 했으나 그 분의 comment에는 아쉽게도 댓글이 달려있지 않았습니다😂

비어있는 태그들과 늘어나는 태그가 연관이 있을 것이라 생각했기에 비어있는 태그들에 대한 답변을 천천히 읽어보았습니다. Material UI팀은 대부분의 이슈에 비어있는 태그처럼 보이지만 의미가 있는 태그일 것이다 + 너가 이것이 불편하면 styled-components로 migration하는 것을 추천한다는 답변을 댓글로 남겼습니다.

아쉽게도 저희가 원하는 답변은 아니었습니다. 비어있는 태그들이 의미가 있는지 없는지는 잘 모르겠지만 styled-components로 migration 할 경우 style을 정의하기 위해 매번 새로운 컴포넌트를 생성해야 하는게 까다로웠고 현재 코드 형태보다 훨씬 가독성이 떨어질 것이라고 생각했기 때문입니다.

3. 원인 제거해보기 💀

위에서 얻은 정보들이 살짝 아쉽긴 하지만 어찌됐건 다른 사람들도 Box 컴포넌트와 makeStyles API에 대해 뭔가 문제를 겪고 있다는 것은 확실해졌습니다. 따라서 이슈를 일으키는 원인 자체를 제거하면 어떨까 하는 생각을 하게 됩니다.

역시나 글로는 굉장히 짧게 느껴지지만 약 10일이 넘는 시간동안 해당 이슈와 씨름을 하다보니 지쳐있던 상태였고, ‘Box와 makeStyles를 없애보자!’ 라는 결론에 이르게 됩니다. 먼저 Box를 제거해봅니다. Box의 경우 div 태그와 다를게 없었기에 전부 div 태그로 바꿔주었고 그 결과 비어있는 style 태그가 굉장히 많이 줄었으며 성능 역시 눈에 띄게 좋아지는 것을 확인합니다.

하지만 makeStyles가 남아있었습니다. 이전에 비해 성능이 좋아지긴 했으나 interaction이 여러번 반복되면 이전보다는 아니지만 여전히 성능이 점점 저하되는 현상이 보였습니다.

앞서 언급한 것과 같이 makeStyles는 포기하기 힘들었습니다. 직관적인, 가독성 높은 CSS 적용이 맘에 들었고 지금까지 해온 작업들을 모두 migration 하는 것은 솔직히 너무 귀찮았습니다…😅

4. 코드 파헤치기 🧐

결국 makeStyles 코드를 파헤치는 지경에 이릅니다. 사실 코드 자체가 문제라면 이를 수정해서 다시 배포할 수도 없는 노릇이기에 그냥 받아들이는 것이 맞다고 생각하여 코드를 파헤치고 싶지는 않았으나 답답한 마음에 이유라도 알자라는 느낌으로 확인해봅니다.

Material UI는 내부적으로 JSS 라이브러리를 사용하고 있습니다. 여기서 유심히 볼만한 것은 StaticStyleSheet와 DynamicStyleSheet이었습니다. JSS의 공식문서를 보면 DynamicStyle은 props가 전달되어 style이 정의되는 형태를 말합니다. makeStyles 코드를 살펴보면 기본적으로 StaticStyle이 우선적으로 생성이 되고 props가 있을 경우 DynamicStyle이 추가로 생성되는 듯 했습니다.

저희 프로젝트에서 style 태그가 늘어나는 특징은 다음과 같았습니다. 예를 들어 interaction이 발생했을 때 style 태그가 20개가 늘어난다면 제거를 하면 10개만 줄어듭니다. 즉 style 태그가 2배로 늘어나지만 제거는 하나의 컴포넌트만큼만 되는 상황이었습니다.

이를 검증하기 위해 props를 제거했더니 interaction이 발생해도 makeStyles와 관련된 style 태그가 늘어나지 않는 것을 확인했습니다. 이를 해결하기 위해서는 makeStyles에 전달되는 props를 전부 제거하고 static한 style에 대해서만 makeStyles를 사용하고 dynamic한 style은 inline이나 styled-components를 활용해야 하나 하는 생각이 들었습니다.

Material UI 에서는 style 태그를 추가할 때 attach와 detach라는 메소드를 사용합니다. 각 메소드의 코드에서 혹시 힌트를 얻을 수 있을까 하여 읽어보았지만 아쉽게도 힌트를 찾지 못했습니다.

5. 조금은 허무한 결론 🤣

코드까지 까봤지만 별다른 힌트를 얻지 못했기에 절망하고 있을때쯤 한 가지 사실을 발견합니다. 어찌보면 당연하지만 Material UI가 빌드될 때 development mode와 production mode에서 다르게 빌드가 된다는 것입니다. 사실 성능 이슈를 개발 단계에서 고려를 할 필요가 없다고 생각했기에 production mode로 빌드를 해본 후 다시 한 번 확인을 해봐야 한다는 생각이 들었습니다.

사실 함께 프로젝트를 진행하고 있는 도호님께서 프로젝트 초기에 travis를 적용시켜 이미 배포를 해주셨는데 localhost만 보며 개발을 해서 그런지 까마득하게 잊고 있었습니다. 스크럼에서 해당 내용들을 공유하자 도호님께서 이미 배포되어 있는데 확인해보시는게 어떠냐고 제안해주셨고 바로 확인을 해봤습니다.

불행인지 다행인지… 문제가 사라졌습니다. 네… 배포하면 문제가 되지 않을 이슈였는데 이를 몰랐기에 2주가 넘는 시간동안 해당 이슈로 열심히 고민하고, 삽질하고, 코드 수정하고 별 짓을 다하고 있었습니다.

Material UI에 저희와 같은 이슈를 가진 사람이 없을 때 눈치를 챘어야 했는데 눈치가 없었던 것 같습니다. 순간의 감정을 이야기 해본다면 문제가 사라져서 기쁜 마음 40%, 그동안 고생한 것때문에 화나는 마음 30%, 다른 것을 개발할 수 있어서 신나는 마음이 30% 정도 될 것 같습니다.

아쉬운 것이 있다면 개발 모드에서 왜 그런 현상이 발생하는지 결국은 알아내지 못했다는 것입니다. Material UI에 해당 이슈를 남겨볼까 생각했지만 이 문제가 저희 프로젝트에서만 나타나는 것인지 다른 사람들에게도 공통적으로 나타나는 것인지 확신이 없어 이슈를 남기지는 않았습니다.

마무리 😊

조금은 허무한, 경험이 많지 않았기에 겪었던 이슈였습니다. 굉장히 오랜 시간 고민한 것치곤 허무한 결론에 조금 실망한 것도 사실이지만 얻은 것도 굉장히 많았습니다.

  1. 리액트 컴포넌트의 생명주기와 리렌더링이 되는 시점에 대해 공부할 수 있었습니다.
  2. 리렌더링 관점에서 효율적인 컴포넌트의 구조에 대해 고민해볼 수 있었습니다.
  3. 실제로 좀 더 리렌더링 관점에서 더 효율적인 구조로 리팩토링을 해봤습니다.
  4. 성능 측정 도구들에 대해 공부할 수 있었습니다.
  5. 오픈소스가 어떻게 작동하는지 직접 코드를 까보며 공부해보는 시간을 가졌습니다.
  6. 직면한 문제에 대해 직접 하나하나 분석하며 조금씩 정답에 접근하는 경험이 생겼습니다.
  7. 성능에 대해 고려 하면서 코드를 짜되, 개선은 마지막에 해야 한다는 사실을 깨달았습니다(?)

문제를 해결하며 아무리 찾아봐도 답이 없다는 사실에 굉장히 스트레스를 많이 받았는데 역시 답이 없는 문제는 없는 것 같습니다. 다음에 또다른 문제에 직면한다면 그 때는 좀 더 발전한 모습으로 문제를 해결 할 수 있을거라 믿습니다. 😁

참고자료

도움을 주신 분

@wooojini님이 문제해결을 위해 함께 고민하고 분석해주셨습니다.