회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
FEConf2023에서 발표된 ‘몇 쳔 페이지의 유저 가이드를 새로 만들며’ / AB180 이찬희 프론트엔드 팀 엔지니어링 매니저
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
FEConf2023에서 발표된 ‘몇 쳔 페이지의 유저 가이드를 새로 만들며’ / AB180 이찬희 프론트엔드 팀 엔지니어링 매니저
저는 사용자에게 데이터를 정보로 전달하는 일에 관심이 많으며, AB180이라는 데이터 스타트업에서 프론트엔드 팀 엔지니어링 매니저를 맡고 있는 이찬희입니다. FEConf2023에서 ‘몇 천 페이지의 유저 가이드를 새로 만들며’라는 제목으로 제 경험을 소개했는데요, 발표 내용을 글로 옮기면서 분량이 길어져 둘로 나누어 소개하게 됐습니다. 앞서 1편에서는 유저 가이드가 무엇이고 왜, 어떻게 갈아엎었는지, 그리고 개발하면서 부딪쳤던 첫 번째 문제를 이야기했습니다. 이번 글에서는 개발하면서 부딪쳤던 두 번째 문제를 소개하도록 하겠습니다. 1편을 먼저 읽고 이 글을 읽으시기를 권합니다.
두 번째 문제는 가이드를 이전하면서 발생했습니다. 여러분도 저도 새로운 프로젝트를 시작할 때 보통 보일러 플레이트를 많이 활용하실 겁니다. 저도 유저 가이드를 만들며 CMS 예시를 담은 보일러 플레이트(Boiler Plate)를 많이 참고했는데요. 보일러 플레이트 대부분이 정적 사이트 생성(Static Site Generation, SSG)을 사용하고 있었습니다. 아마 콘텐츠의 변경이 자주 있지 않고, 초기 로딩 속도나 SEO 관련 지표 향상을 위해서 SSG로 설정했을 겁니다. 저도 그 부분에 있어서 별로 깊게 생각하지는 않았습니다. 당연히 유저 가이드는 변경이 적으니 정적 사이트고, 만약 수정이 잦아진다면 Next.js에 있는 변경되는 페이지만 재생성하는 ISR(Incremental Static Regeneration)을 적용하면 된다고 생각했습니다.
그렇게 별문제가 없다고 생각했는데, 여기서도 제가 한 가지 간과한 게 있었습니다. 바로 사람은 한 번에 이사를 바로 가지 않는다는 점이었습니다.
이렇게 이사 가면 큰일 나죠. 이제 도로가 카트라이더가 되고 막 난장판이 됩니다.
저도 제 짐을 잃겠죠.
PW팀에서는 몇 천 개의 기존 가이드를 하나하나 검수하고 수정한 뒤에 하나씩 새로운 CMS로 이 가이드 콘텐츠를 이전했습니다. 그래서 그에 따라 다음과 같이 시간에 따라 이전되는 가이드의 양이 늘어났죠. 그리고 양이 늘어날수록 빌드 속도 역시 같이 늘어나서, 처음에 1분 정도 걸렸던 빌드가 5월 중순을 보시면 7 8분을 넘는 것을 확인하실 수 있을 겁니다.
이것 때문에 업데이트가 느린 것 같다는 PW 분들의 리포트 역시 많이 늘어나기 시작했고요. 이때부터 가이드의 배포와 반영까지 걸리는 시간을 줄일 수는 없을까 그런 고민을 해보기 시작했습니다.
우선 제가 앞서 보일러 플레이트를 활용했을 때 기본적으로 SSG를 하고 있었고, 기본적으로 이제 CMS에서 가이드 생성, 수정, 삭제가 일어나면 무조건 웹훅을 보내 새로 빌드를 돌리는 구조를 취하고 있었습니다.
아까 말씀드렸던 것처럼 ISR을 활성화하여 특정 페이지만 재생성하도록 변경하여 빌드 횟수 자체는 줄일 수 있었습니다. 하지만 여전히 빌드 속도는 느렸고 무엇보다 아직 해결하지 못한 문제가 있었습니다.
제가 저희 유저 가이드에서 콘텐츠 변경이 일어나는 곳을 한번 색으로 칠해보았는데요.
이 문제를 해결하지 못한 이유는 조금 어이없게도 노랗게 칠한 왼쪽에 사이드 바 때문입니다. ‘사이드 바가 무슨 잘못을 했길래 이게 영향을 주냐’ 생각하실 수도 있는데요. 만약 가이드가 삭제되거나 제목이 바뀌면 혹은 가이드 간의 순서가 바뀌면, 유저 가이드의 모든 페이지에 동일한 사이드 바를 노출하기 위해 업데이트가 필요해집니다.
그렇다면 모든 페이지에 사이드 바를 업데이트하려면 어떻게 해야 할까요? 우선 가이드 숫자 곱하기 지원 언어 수만큼 재생성 요청을 보낼 수 있을 겁니다. 저희 가이드는 대략 한 1천 개가 넘고 지원하는 언어로는 한국어 영어, 일본어, 중국어가 있습니다. 그럼 천 곱하기 4를 해 서버에 요청을 보내면 서버가 버틸 수 있을까요? 당연히 못 버팁니다. 서버가 과부하로 정상적으로 처리할 수 없습니다.
그렇기 때문에 저는 눈물을 흘리면서 느리지만 확실한 새로운 빌드 돌리기를 할 수밖에 없는 상황이었고요. 이게 굉장히 좀 마음이 그랬습니다. 이게 만약에 유저 가이드 이전 과정에서만 발생하는 문제라면 그냥 넘어갈 수도 있었을 것 같습니다. 하지만 저희가 기존 유저 가이드를 관리하던 젠데스크의 로그 데이터를 확인해 보니, 못 해도 하루에 3번은 수정이 이루어지고 4일마다 최소 2개의 가이드가 추가되었습니다.
물론 참을 수는 있겠지만 ‘조금 더 좋은 방법이 있지 않을까’ 이런 생각이 들어서 좀 깊게 고민을 하고 있었는데요. 그런 고민을 하다가 우연히 하나의 영상을 보게 됩니다.
Nexg.js App Router가 Pages Router보다 느리다는 것에 반박하는 영상인데요. 참고로 이 썸네일에 나오는 이 분은 테오 브라운(Theo Browne)이라는 분인데요. 트위치에서 시니어 프론트엔드 엔지니어로 일하다가 지금은 스타트업 CEO 겸 개발 관련 유튜버를 하는 분입니다. 영상의 내용 중에서 중복된 요청을 처리하는 부분이 있었습니다.
잘 보이실지 모르겠지만, 포켓몬 포켓몬 1, 포켓몬 2와 같이 중복된 요청이 있을 때 앱 라우터는 데이터 캐시를 이용하기에 성능적으로 더 빠르다는 내용을 이야기했습니다. 그러면서 실제로 oha라는 것으로 벤치마크를 돌려봤을 때, 아래 보시는 것처럼 페이지스 라우터 대비 앱 라우터가 3배 이상 빠르다고 주장했죠.
저는 이 부분을 보고, 내용이 옳고 그름을 떠나서 제가 생각하는 랜더링의 단위를 바꿔볼 필요가 있다고 생각했습니다. 저는 SSR, SSG, ISR 같이 페이지 단위를 중심으로 렌더링을 생각했습니다. 하지만 페이지의 구성 요소를 하나하나씩 뜯어보면 분명 동적인 요소도 있을 것이고 정적인 요소도 있을 것입니다.
동적인 것과 정적인 것의 기준을 나누는 것은 좀 다양하겠지만 만약 이를 네트워크 요청 단위 같은 걸로 바꿔보면 어떻게 될까요? 그렇다면 기존의 관점이 다음과 같이 바뀔 것입니다.
태그와 그루핑, 캐싱 그리고 스트리밍으로요. 실제로 최근 Next.js의 공식 문서에서는 SSG, SSR, ISR 같은 용어들보다는 스테팅 랜더링, 다이나믹 렌더링 같은 용어를 많이 사용하고 있었고요. ISR의 경우에는 네트워크 요청에 적용된 캐시, 리밸리데이트, 태그 옵션을 사용한 static 렌더링의 하나로 설명하는 것을 확인할 수 있었습니다.
그러면 한번 생각해보면 좋을 것 같은데요. 우리가 보통 개발하는 서비스는 정적인 페이지라도 동적인 요소가 있고 그 반대도 있습니다. 마치 스펙트럼처럼요.
그리고 유저 가이드는 CMS에 작성된 콘텐츠를 네트워크로 가져오는 만큼 네트워크 요청이 차지하는 비중이 생각보다 큽니다.
그렇다면 하나의 서비스에서 상대적으로 정적인 부분과 동적인 부분을 나누고, 정적인 부분이 데이터 캐시를 더 적극적으로 활용하도록 바꾼다면, 초기 빌드 시간은 줄어들면서 업데이트가 빠르게 반영되는 환경을 만들어볼 수 있지 않을까요?
그래서 한번 테스트해봤습니다. 눈썰미가 있으신 분들은 좀 눈치채셨겠지만, 제가 동적인 것은 노란색으로, 정적인 것은 보라색으로 표시하고 있었습니다.
기존의 정적 생성 로직을 모두 다 지웠습니다. 그리고 사이드 바에서 사용하는 요청은 한번 매번 새로 받아오게끔 데이터 캐시를 꺾었죠.
반대로, 콘텐츠를 불러오는 요청은 재생성 주기를 1개월로 설정해보았습니다. 그렇게 한번 돌려본 결과, 다음은 두 환경에서의 페이지 스피드 인사이트 결과입니다.
왼쪽이 기존 SSG를 했을 때, 그리고 오른쪽이 데이터 캐시를 적용한 다이내믹 렌더링 상태인데요. 보시다시피 큰 차이가 없고요. 심지어 데이터 캐시를 적용한 부분이 조금 더 성능상으로 높게 나오긴 하는데, 이 부분은 가이드 페이지마다 일부 편차는 있을 것 같습니다.
하지만 사용자 경험 관련 지표에 큰 차이는 없는 상황에서, 빌드 속도를 비교해보면 빌드 속도는 8분에서 1분 20초대로 줄어든 것을 볼 수 있었습니다.
만약 콘텐츠 업데이트가 필요하면 태그를 활용해 묶여 있는 요청들의 캐시를 초기화해주면 됩니다.
여기서 또 한 가지 질문을 해볼 수도 있습니다. ‘캐시가 동작을 하려면 캐시히트가 필요하지 않나요? 그러면 최소한 한 번은 페이지에 접속해야 되지 않나요?’ 좋은 질문입니다.
캐시히트를 위해서 한 번은 접속이 필요합니다. 하지만 저는 이를 위한 별도의 작업은 하지 않았는데요. 이 부분은 제품의 특성에 따라 다를 수 있지만 유저 가이드는 일종의 문서이며, 그렇기 때문에 사이트 맵이 존재합니다. sitemap.xml 이런 것들 아마 들어보셨을 거예요. 그리고 우리는 사이트 맵을 주면 모든 모든 페이지에 1회 이상 접속하는 친구를 알고 있습니다. 바로 ‘웹 크롤러’입니다.
이건 언라이트 하우스라고, 사이트 맵을 읽어서 모든 페이지에 라이트하우스를 측정해주는 도구가 있는데 제가 그걸 사용해보면서 아이디어를 얻었어요. 어차피 웹 크롤러가 모든 페이지를 방문할 것이고 이 방문 시점에 데이터 캐시가 자동으로 활성화될 것 같다는 생각을 했죠. 그렇다면 저희가 해야 될 거는 별도의 크론잡(CronJob)을 만들지 않더라도 단순히 사이트 맵을 구글 서치 콘솔에 등록하고 웹 바이탈을 확인하며 모니터링하는 정도로만 해도 괜찮을 것입니다.
이렇게 해서 제가 만났던 문제들 중에 기억에 남은 문제 두 가지를 꼽아서 이야기를 드렸습니다. 사실 이외에도 유저 가이드 개발 과정에서 많은 이야기가 있었는데요. 하지만 오늘은 무엇보다도 앞서 말씀드렸던 두 가지 문제를 여러분들께 좀 전해드리고 싶었습니다. 오늘 못다 한 이야기는 언젠가 다른 형태로 또 이야기를 드릴 수 있을 것 같고 그랬으면 좋겠습니다.
오늘 제가 말씀드린 문제의 해결 방법은 냉정하게 돌아보면 굉장히 단순하고, 심지어는 흑마법 같은 부분들이 많았습니다. 아코디언에서 hidden=”until-found” 속성을 쓸 수 없던 문제는 속성을 대문자로 주어서, 가이드의 양 때문에 정적 페이지 생성이 느려졌던 문제는 ‘SSG 옵션을 끄고 매뉴얼하게 캐시를 제어한다’가 끝이었으니까요.
하지만 저는 각 문제들이 함의하는 바가 생각보다 크다고 생각합니다. 우리는 아코디언 문제를 통해 프레임워크나 라이브러리가 기능의 개발 혹은 접근성의 구현을 제한할 수 있음을 확인했습니다. 사실 이러한 문제는 리액트만의 문제는 아닙니다. vue같이 속성값을 검증하는 프레임워크 라이브러리에서도 동일한 문제가 있으며, 반대로 앵귤러 스벨트, 솔리드, 퀵 같이 검증 로직이 없는 프레임워크들은 보안과 충돌 관련 고민을 개발 단계에서 좀 더 해야 될 수도 있습니다.
당장 생각나는 것은, 악의적인 라이브러리 개발자가 라이브러리에 악의적인 속성을 주입하지는 않았는지를 확인하는 등의 방법이 있을 것 같습니다. 어쨌든 이러한 상황에서 개발자는 이것을 구현을 할 것인지, 한다면 어떻게, 어디까지 할 것인지를 정해야 합니다. 그리고 만약에 개발을 하는 방향으로 정했다면, 문제에 관여하는 주체와 동작, 영향을 나누어 판단함으로써 원하는 결과를 만들어내실 수 있을 겁니다.
아코디언 문제의 경우에는 렌더링 단계를 나누어 이 문제에 리액트 도움이 영향을 주는 것을 확인하였고, HTML 태그 및 속성이 대소문자를 가리지 않는다는 특성을 활용, 문제를 우회함으로써 원하는 결과를 얻어낼 수 있었습니다. 또한 정적 페이지 생성이 느려졌던 문제를 통해 우리는 단순한 문제라도 양, 즉 스케일이 커지며 엔지니어링의 복잡도가 늘어날 수 있음을 확인했습니다.
이렇게 스케일로 인해 복잡도가 늘어난 상황에서는 기존의 방법론은 더 이상 유효하지 않을 수도 있습니다. 그렇기에 개발자는 여전히 해당 방법이 유효한지 항상 판단하고 또 항상 검증해야 합니다.
이번 문제 상황에서는 렌더링의 관점을 페이지 단위에서 네트워크 요청 단위로 바꿔서 생각했습니다. 이를 통해 유저 가이드는 SSG가 아니라 스태틱 렌더링(Static Rendering)이 적합하다는 것을 확인하고 적용할 수 있었습니다.
여담이지만 이제 이런 페이지 단위를 다른 단위로 바라보는 움직임은 파셜 하이드레이션(Partial Hydration)이라는 개념으로 여러 프레임워크들에서 구현하고 있는 것들을 확인해보실 수 있을 겁니다. 아스트로나 솔리드 스타트나 이런 것들 말이죠. 거기에 있는 아일랜드 아키텍처 같은 것들을 확인하시면 좀 더 인사이트를 얻으실 수도 있을 것 같습니다.
이렇게 해서 유저 가이드를 만들며 겪은 이런저런 이야기를 여러분들께 전해드렸습니다. 마치면서 마지막으로 여러분들께 이 말씀을 드리고 싶습니다. 제가 항상 생각해오던 건데요.
저는 제품의 특성을 파악하고 문제에 영향을 주는 요인을 찾아 복잡도를 제어함으로써 문제를 해결하는 단순하고 간단한 해답에 가까워질 수 있다고 믿습니다. 그리고 그 과정 속에서 각자 엔지니어로서 더 넓은 관점을 갖고 다양한 경험을 해볼 수 있을 것입니다. 오늘 저의 이야기가 여러분의 기억 한편에 조금이라도 남아 있을 수 있다면 굉장히 기쁠 것 같습니다.
요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.