2023 feconf

2023 feconf를 보고, 챕터별 간단하게 요약본, 그리고 관련 링크를 남기고자 한다. 모든 영상들은 유튜브에 잘 게시되어있으니, 솔직히..! 직접 보는게 제일 좋을것 같다..!

vue+express > next

첫번재 안: 페이지 url 별 점진적 변경

어떤 url은 next기반, 어떤 url 은 vue 기반 어떻게 서빙해주었을까

ALB === AWS application loadbalancer

Page url 정보가 담긴 http 헤더는 네트워크 7계층 중, 처음의 응용계층임

네트워크 계층(layer) 별 사용하는 loadbalancer들이 다른것 같음. 각 계층별 갖고있는 정보를 기준으로 서버들에게 분산시켜줄 수 있는것같음

http 헤더에 들어있는 url 정보를 확인하고, L7 로드밸런서에서 next 서버로 연결하는 방식

간혹 배포 후 next 페이지에서 404가 반환되었었는데, 각각 ALB, next, vue가 따로 빌드되다보니 next 빌드순서를 ALB가 기다려주지 않아 next 빌드가 완료되지 않았음에도 ALB에서 next 페이지로 연결시켜버림. 이를 위해, next 빌드의 순서를 보장시켜줬다 함

Vue 레거시, next 모두 개발이 한동안 필요하여 일부 사용자 환경만 컨버팅된 next 로 연결시키는 카나리 배포전략을 하지는 않았다는것 같음.

도메인 분리도 고민하였지만, 서브도메인 하나만 추가되어도 seo봇은 이전의 도메인은 더이상 사용하지 않는다고 판단하기 때문에 부정적인 영향을 고려하여 하지 않았음

하지만, 페이지별 컨버팅을 하다보니 기존 seo 최적화를 위한 첫페이지 이후는 csr로, 페이지전환등 사용자에게 바른속도로 보여줄 수 있었지만, url 마다 다른 프레임워크를 사용할 경우 해당 url 접근 시 그 기술과 관련된 스트립트를 새로 받아와야 했기에 장점을 잃게되었음.

이 이유로, url 기반 컨버팅 된 페이지들의 적용이 무기한 연기되었었음

두번째 안: iframe

마지막의 빠른 페이지 전환이 가능한 특징을 유지할 수 있음

페이지단위가 아니기 때문에, 각각 기능별로 더 작은 부품으로 컨버팅, 개발 후 적용이 가능하였음

단, iframe 내부, 외부 통신방식 설계가 필요하였음 또한, iframe에 대해 seo 관련된 정보가 부족했음

이것도 마지막의 seo에 대한 불확실로 보류

결국 선택은?

Product 팀과 협의 하여 정책을 변경하기로 함. 사용자가 가장 많이 경험하고있는 퍼널을 확인하고, 해당 퍼널의 최 하단부터 마이그레이션 진행. 마이그레이션 된 페이지의 lcp 시간은 2.5초 이내여야 함.

vue기반의 lcp (가장 큰 컨텐츠 렌더링 시간)이 길었기에, next > vue는 오래걸리지만 vue > next 로의 페이지전환은 그렇게 오래걸리지 않았음.

퍼널ui에서 가장 마지막 단계를 먼저 컨버팅하기

결국, lcp도 많이 단축하고, cls(화면 내 레이어들이 하나하나 생성되면서 ui 상 아래로 밀리는 시간)도 거의 줄일 수 있었다 함

크로스 플랫폼 디자인 시스템, 1.5년의 기록

디자인시스템 또한 각각 추구하는 가치에 따라 개발방식이 달라짐

유연함을 강조하였다면, 더 많은것을 코드로 작성해줘야 하지만 자유롭고 chakra —> 범용성 디자인

간단하고 일관성을 강조했다면 적은코드로만도 가능하지만 유연하지 않음 spectrum —> 제품언어: 디자인 의사결정의 집합임.

단순 위의 범용성 디자인의 ui 라이브러리는 회사 내부의 의사결정이 필요없거나, 유사하거나일 뿐

대부분의 회사에서는 회사 자체의 브랜드 이미지가 있고, 이러한 의사결정이 반영된 디자인을 함. 덜 자유롭지만, 일관성을 강조

회사의 브랜드이미지는 동일하기 때문에, 내부의 모든 제품을 하나의 범용 디자인 시스템을 고려하였지만, 사실 불가능하더라

  • 제품언어를 새로 만들어내는것보다 범용 디자인을 관리하는것이 더 어렵더라

디자인 토큰

  • 컬러셋, 버튼의 종류 모두 디자인 의사결정이 포함된 것

이러한 의사결정은 시간이 지남에 다라 충분히 변경될 수 있고, 빠르게 모든 제품에 반영하는것이 좋음

이러한 의사결정을 기계가 읽을 수 있는 방법으로 인코딩하는것은 어떨까

디자인 토큰은 디자인 의사결정을 인코딩하는것.

carrot-500: #ff6f0f 우리는 제품에 이 색상을 사용할꺼고, 그것의 이름은 carrot-500이다

이 뜻은, 색상선택과같은 디자인이 자유롭지 못할 수 있다는 것. 디자인 값은 무한하지만, 우리는 정해둔 유한한 디자인 토큰으로만 소통함.

피그마를 절대적인 규칙으로 사용하고, 피그마에서 생성된 디자인토큰을 컴포넌트 배포 시 web hook액션을 추가하여 각 플랫폼의 코드를 생성함.

스타일시트나, 컬러셋 변수 파일 등등

의사결정이 포함된 제품언어로 디자인시스템을 개발하여 90%를 해결할 수 있더라도, 외의 10%의 유연한 개발이 필요한 경우로 인해 그 이상의 시간이 소비될 수 있음.

유연성, 일관성 모두 제공하는 디자인시스템을 만들면 어떨까

  • 모든것이 결합된 컴포넌트 하나(얘만 써도 됨)
  • 순수한 js 기반의 core 로직 하나
    • input이 있다면, input의 기본 속성들을 결정해주는 함수가 있고, 추가 커스텀 가능한 값들은 받아서 이를 통해 만들어진 속성들을 DOM에 스프레드오퍼레어터로 속성으로 넣어줌.
    • react를 사용한다면, 해당 core 로직만 가져와서 useSyncStore로 감싸주는등의 상태동기화 처리는 필요할수도..?
  • 스타일 형태 를 결정해주는 로직 하나

Zag-js를 보았는데, 하나의 코어로직에 이 컴포넌트에서 사용중인 태그들에 대한 속성들을 모두 관리하고(기본적으로 들어있어야 할 것들은 모두 들어있음) 추가적인 상태 disabled, selecetd 이런것들은 우리가 전달할 수 있도록 되어있음 이 컴포넌트에서 작동되는 포인터 다운 등의 이벤트들의 기본 비즈니스로직도 들어있음

Use hook

비동기로직을 처리하기위해 포함되었던 useEffect, 혹은 useQuery없이 use만으로 해결

Promise 객체를 반환하며, 내부에서 suspense를 트리거하는것 또한 동일

반환문, 조건문, 반복문 내부에서 사용할 수 있기에 일반 hook과 차별점을 가짐

컴포넌트 내부에 use 를 바로 사용하여 패칭할 경우, 기존과 동일하게 리렌더로 인해 무한한 요청 전달.

이는, 패칭해오는 함수 앞에 react에서 제공하는 cache 구문을 통해 해결 가능. 아직은 실험모드이고 서버컴포넌트에서만 사용 가능

일종의 해당 패칭에 대한 응답값 캐싱을 하는것 뿐 메모와 유사함

단, map이나 foreach와 같이 callback으로 전달되는 경우, 이 경우에는 안됨 호출부가 hook이 아니라면 에러를 반환함

에러 호출부는 리액트 컴파일러 에러임.

현재 리액트는 컴파일러 최적화라는 목표로 생성된 값들에 대해 useCallback, useMemo가 없어도 메모가 되도록 개발을 하고 있음

이는 사실, 함수와 같은 참조형 데이터가 useEffect의 의존성으로 들어가게 될 때, 무한히 실행되는것을 막기 위해 강제적으로 useCallback과 같은 메모를 모두 해주어야 함으로서 그 과정없이 가능하도록 useEffectEvent였나 얘들이 소개되었었는데, 이 프로젝트는 무산되고 아얘 개발자들이 이 고민이 필요없도록 컴파일 최적화라는 프로젝트로 전환됨

use를 쓰다보니 생긴 문제들관 관련된 내용이였음

영상과 별개로 React19에서 useFormState와 같은 form과 관련된 hook들도 생긴다고 하니 같이 봐도 좋을것 같음

웹 기반 그래픽 편집기의 구조와 7가지 디자인 패턴

진득하게 다시보자

대략적으로 에디터에서 발생한 이벤트, 캔버스에 변경을 mvc 디자인패턴을 기준으로 개발한것을 설명하는듯

SSR 환경 메모리 누수 디버깅 가이드

힙메모리는 지역변수등의 지역값이 비교적 고정적으로 저장되는 스택과 다르게 메모리가 동적으로 할당되거나, 빠질 수 있음. 동적으로 할당될 수 있기 때문에 메모리 누수가 발생할 경우가 있음

Node 환경 memory leack 디버깅

우리가 뭐부터 봐야 좋을까..?

대표적인 메모리 누수를 일으키는 요인

  1. 전역변수
  2. 해제되지 않는 타이머
  3. 클로저
    1. 컨텍스트 내부에 어떠한 참조자료형을 무한히 사용하고, 그 용량을 늘리고있는 경우

종종 발생하는 서비스의 OOM (out of memory) -> 필요하지(사용되지) 않은데 메모리를 계속 차지하고있는 현상. 불필요하게 차지하고있는 것들 때문에, 효율적으로 쓸 수 없음

  • 필요하지 않은 메모리에 대한 청소를 위해 GC가 자주 돌수록 cpu 사용량이 늘어남
  • cpu 작업이 늘어나면, 이벤트루프가 중단되며 연산이 느려짐
  • 노드서버가 죽을 수 있음
  • 로드밸런서가 502 에러를 뱉을 수 있음

성능도 나쁘고, 애플리케이션도 자꾸 죽음

힙 메모리를 늘려주거나, 누수의 범인을 찾자.

내가 띄운 노드서버라면, 메모리 누수 모니터링 툴을 달아놓을 수 있다는것 같음.

Grafna, datadog, aws cloudwatch …

클라이언트사이드라면..?

메모리누수가 발생하는 코드는 모니터링 도구에서 어떻게 보일까

메모리누수가 있는 그래프의 경우 힙메모리 용량이 커지다보니 사용량 그래프가 쭉 상승하다가, 한번 뚝 떨어지고 다시오르다가 뚝 떨어지고를 반복함

힙메모리를 늘려준다면?

v8엔진은 힙메모리 관리를 위해 사용한다면 마크, 그렇지 않으면 치운다는 의미로 Mark and Sweep 알고리즘을 씀

원시자료형이 아닌 참조자료형의 경우 때에 따라서 힙메모리에 할당된 값을 참조하고있을것임

gc는 이 자료들이 사용중인지 아닌지 체크하여 위 알고리즘을 돌리는데, 숨겨진 어딘가에서 사용되고 있다면, 살아남은 참조자료형들은 마크되어있어서 계속하여 남아있음

v8의 gc는 각각의 메모리에 대해 단계를 갖고 처리를 하는데

  1. 최초 생성된 메모리는 nursery (new space)
  2. Gc가 한번 수행되고 살아남으면 intermediate (new space)
  3. 또 한번 Gc가 수행되고 살아남으면 old space으로 분리됨 (사실 여기까지 살아남는경우는 거의 없음)

만약 마지막까지 살아남은 메모리가 많아지면 old space이 꽉 차게 될 것임 > 이 때, 힙 메모리 초과로 서버 사망

결국 그냥 언젠가 되면 원점이기 때문에 힙메모리를 늘려주는건 근본해결이 아님.

근데 구글링을 하면 max-old-space-size를 늘리라는 말이 많은데, 그냥 old space의 공간을 늘려줄 뿐이라 근본해결이 아님

메모리 누수가 발생하는 애를 직접 찾아본다면?

Node —inspect index.js 해당 cli를 통해 체크 가능 개발자도구의 녹색 아이콘이 있음

개발자 도구의 memory 탭에서 프로파일링 녹화버튼을 통해 특정 구간의 힙메모리의 용량 변화를 볼 수 있음

쓰레기통은 수동 GC돌리기

보통 Allocation instrumentation on timeline이라는 특정 시간을 기준으로 힙메모리의 용량변화 그래프를 확인하고, 어떤 객체가 얼마나 많은 용량을 사용하고있는지 확인하여 찾는다 함

  • 그래프에서 회색은 GC가 수거한 힙메모리 양, 파란색은 수거하지 못한 힙메모리 양임
  • 파란색 그래프가 있는 부분이 메모리 누수가 있는 것

근데 사실 그래프가 누가 범인인지는 안알려준다고 함

  • Shallow size: 오브젝트 자신의 크기
  • Retained size: 나 자신 + 참조하고 있는 오브젝트들의 크기

찾을 때, Shallow Size 크기에 비해서 Retained Size가 큰 경우를 의심해보기

나 자체는 용량이 크지 않은데, 참조하고있는 오브젝트의 용량이 엄청 큰 상황임

Ts 5.2 부터 let, const, var 처럼 using 키워드를 사용해 변수를 생성하였다면, using으로 선언한 값의 인스턴스 내의 Symbol.dispose를 통해 cleanup을 해줄 수 있음. 이 때, 메모리를 초기화시켜주는 코드를 넣어주면 어떨까.

React 할 때 timer를 useEffect return 내부에서 cancel 시켜주는것처럼.

using으로 선언한 값이 포함된 스코프 종료 시, 위의 clean up이 호출됨

React Native, Metro를 넘어서

React native는 metro와 짝꿍이라고 함

일종의 webpack과 같은 번들러인가봄

  1. —reset-cache와 같이 수동으로 캐시를 날려줘야 함
  2. 빌드시간이 엄청 길음
  3. 모던 번들러와 다르게 tree shaking 지원 안되어서 크기가 좀 큼

위와 같은 문제(?) 아쉬움(?) 있는데, esbuild 번들러로 교체할 수는 없을까?

번들러의 근본을 보자

React native든 뭐.. 어떠한 환경이든 여러개의 모듈로 쪼개진 파일이 아닌 하나의 파일만 볼 수 있어서 그것을 합쳐주는 역할을 함

Resolution

우리가 작성하는 ./app 과 같은 경우 이게 어떤 외부 라이브러리의 모듈인지, 어떠한 확장자인지 정확히 찾아주는 역할을 함 번들러가 이를 수행해주기 때문에, 우리가 크게 건드리지는 않음.

tsv5의 moduleResolution Bundler가 이 기능에 역할을 온전히 의지하는것 비슷한 느낌..?

metro에서는 resolveRequest를 통해 위의 경로를 해석하는데 설정을 줄 수 있음. 물론, esbuild도 가능 함. 이 Resolution 기능을 통해, 애매하게 축약되어있던 경로들이 정확히 어떤 위치를 보고있는지 알 수 있게됨

Load

번들러는 브라우저들이 이해할 수 있는 스크립트 버전, 타입스크립트라면 스크립트로 변환을 해주기도 함. React-native는 javascript가 아닌 flow라는 언어를 사용하는데, 이를 metro가 javascript로 변환을 해줌.

즉. 자바스크립트가 아닌것을 자바스크립트로 변환시켜주고자 함

생각해보면 metro, esbuid 같은 기능인데, 바꿀 수 있지 않을까?

resolveExtensions 필드에, iOS와 같은 react-native 내부의 확장자도 추론할 수 있도록 후보에 넣음

몇 천 페이지의 유저 가이드를 새로 만들며

비교적 최신의 크롬문법인 hidden=“until-found” 라는 속성을 태그요소에 전달하였는데, react-dom은 이 속성을 올바르게 dom으로 변환시켜주지 못함. 비교적 최신의 문법이기 때문

  1. react-dom은 소문자나, on 이 붙은 속성을 변환시켜줌.
  2. HTML은 속성의 대소문자를 구분하지 않음

대문자로 작성하여 React를 속여보자

모두가 한번에 이전되지 않음.

시간이 지날수록 PW팀에서 확인 완료되는 가이드페이지가 증가되면서 빌드속도가 점점 느려짐

Next의 isr을 사용하여 변경이 있는 부분만 새롭게 빌드를 하도록 처리하긴 했지만, 근본적으로 해결되지는 않았음.

확인해보니, lnb에 모든 가이드들에 대한 진입점들이 작성되어있는데, 가이드들의 순서가 변경되거나, 삭제되거나 할 때 모든 가이드페이지의 lnb또한 순서, 제거등의 변경 이유로 그 페이지 전체가 빌드대상이였음.

하나의 콘텐츠가 변경되는것은 그냥 그 페이지만 빌드되면 되었지만, lnb가 변경되는것은 모든 페이지가 쓰기 때문에 모든 페이지가 재 빌드가 됨. 4개국어*1000개 이상의 가이드페이지가 매번 빌드되는것

해결

  1. 동적으로 생성이 되는 lnb는 캐시가 아닌 매번 새롭게 받아오도록 변경함. 페이지에서 lnb는 변경 체크의 대상이 아님
  2. 컨텐츠영역은 캐시의 유지시간을 한달로 늘려놓음. 각 서버컴포넌트의 캐시검증, 시간을 커스텀하게 변경함
  3. 사용자가 진입하여 콘텐츠를 접근하는데 시간 등의 성능차이는 별로 없음. 근데 빌드시간이 8분대에서 1분대로 뚝 떨어짐. 데이터 재검증의 태그들을 잘 조합하여 필요에 따른 부분만 새롭게 빌드되고, 새로운 컨텐츠로 만듬

한가지 의문

새로운 컨텐츠가 빌드되면 해당 캐시가 생성되고 조회하기 위해서는 한번은 접속이 되어야 그 페이지의 캐시가 생길텐데 모든 페이지들이 캐싱된 페이지를 잘 조회할까?

sitemap이 잘 구성된 경우, 모든 페이지에 한번 접근하는애가 있음. 웹 크롤러는 sitemap에 정의된 페이지들에 1회 접근할 것임. 이 때 자동 캐시가 생성되고, 이후는 캐시히트가 발생하지 않을까 로 접근하면 괜찮아보임

대형 웹 애플리케이션 micro frontends 전환기

문제 발생

  • 하나의 flex 모노레포 내부에 인사, 휴가 등등 여러개의 기능이 별도의 하위프로젝트로 관리됨 총 17개의 프로젝트
  • 페이지 묶음별로 flex 앱이 나뉘어짐
  • 앱 하위에 페이지가 아니라, 페이지 하나하나가 다 앱이였음.
  • 동일한 레이아웃이더라도, 페이지전환이 별도의 도메인의 앱으로 가는것이기 때문에, 모든것들을 새롭게 받아와야 했음.

즉, 사용자가 하나의 flex 앱을 사용하는 경험을 못줌

배포단위는 더 작게 페이지 이하의 단위로 각각 따로 빌드하는것은 동일하게, 단 하나의 앱으로 느껴지게끔 런타임때에는 배포 단위들을 통합 이를 위해 module Federation 개념을 도입하고자 했지만, 현재의 next 프로젝트에서는 불가능하여 next를 떼어네기로 함

module Federation?? 함 알아보자..

moduleFederation으로 해당 앱이 런타임에 로드할 다른 앱을 설정해줄 수도, 해당 앱이 런타임 대에 다른 앱에게 불릴 수 있는(?) 코드도 지정 가능하다는것 같음

다른 앱을 불러오는 하나의 앱을 host, 불려지는 앱을 remote라고 함. 모든 앱들은 host와 remote 두가지 모두 될 수 있음. 이를 전방향적인 특성이라 함.

  • 이 경우, 서로간의 구조를 예상하기가 어려움
  • 하나의 host만 있고, remote를 불러오도록 고정함

런타임에 앱이 합성되다보니, 런타임에만 확인할 수 있는 에러가 발생할 수 있었음

  • 에러가 발생하였을 때, 다른 remote 앱은 사용할 수 있도록 에러를 격리처리해야함

이런 구조의 변경은 오랜시간이 걸림. 어떻게 기능개발과, 구조변경을 함께 진행했을 까

모노레포 내부에서 프레임워크에 영향받지 않는 순수한 react 컴포넌트로 개발을 지속하며, 두가지 환경을 모두 배포하였음

내부에서 프레임워크와 관련된 기술 기반으로 wrapping된 기술을 import 하여 쓰는 경우에는 위처럼 분리하여 쓰기가 조금 어려움

빌드 시, import 해오는 경로를 해당 프레임워크에 맞는 경로로 변경함 (React -> Next)

점진적으로 환경을 배포해가며 전환함

dev —> qa —> 사내prod …

이벤트 기반 웹뷰 프레임워크 설계와 플러그인 생태계 만들기

Daangn/stackflow

당근에서는 웹뷰에서 페이지간 전환을 보다 앱 스럽게 하기 위해 carrotFrame이라는 라이브러리를 사용하고 있었다 함.

History api 의 스택을 직접 참고하고싶었지만, 쌓인 스택에서 특정 부분을 찾아 조회하는것은 어렵기 때문에, react 내부에 history stack과 동일한 구조의 스택을 쌓고 관리하는 방식으로 구현을 했었음.

그러다보니, react-router-dom에 강하게 의존하고 있는 문제와, react 상태와 history stack의 동기화문제가 항상 있었다 함.

새롭게 개발을 시작하여 다른 transition 전환효과, navigation을 핵심이라고 생각했지만 그런것들이 아닌 목표는 애니메이션이 존재하는 stack이였기 때문에, 해당 라이브러리에서는 history api, react-router-dom 이라는것을 배제하게 되었음

첫 아이디어는 이벤트 기반으로 설계를 하는 것

  1. 유저의 행동을 이벤트로 정의하고, 이벤트에 따라 상태가 어떻게 변경될지를 구현
  2. 이벤트가 여러개일 수 있고, 이 여러개의 이벤트를 로직이 판단하여 상태를 변경함

화면의 진입, 새 페이지 추가, 이전으로 돌아가기 이벤트가 있을것 같음 init, push, pop 위의 이벤트들을 통해 상태를 갱신하고, 사용하는곳에서 이해할 수 있는 상태로 변경

react 라면, useSyncExternalstore같은?

라이브러리를 사용하는 사용자가 쉽게 확장할 수 있도록 해보자

~~를 했을 때, ~~ 를 수행한다. —> callback이 생각난다.

이벤트가 추가되기 전, 이벤트로 상태가 변경되기 전에 실행될 함수를 전달받을 수 있도록 해주자

onBeforePush, onBeforePop과 같은

근데 만약 이렇게 많은 옵션을 받을 수 있다면, 학습비용이 높아질 수 있음.

확장성은 유지하면서 유저가 쉽게 느낄 수 있도록 개발할 수 없을까??

  • 위처럼 각각 옵션으로전달하는것이 아닌 각각 플로그인으로 넣도록 함. 각 기능을 플러그인으로 분리하였기 때문에, core로직과 분리되어있어 개발에 용이
  • 내부에 key를 기반으로, 플러그인을 해석하기 때문에, 목적에 맞는 key만 잘 할당해주면 커스텀 플러그인을 만들어 쓰는데도 용이

위 두가지를 기대한다 함.

이 내용은 깃 레포를 함 보며 jsdoc으로 정리해보고있다..


@SangMin
👆 H'e'story

🚀GitHub