Optimization React rendering

랜더링 최적화는 어떻게 하나?

1차적으로 불필요한 랜더링을 먼저 막습니다. 불필요한 랜더링이란 화면에 반영되는 데이터가 동일하거나 다른 화면에 가려져 랜더링이 아예 필요 없는 경우를 말합니다.

Props로 넘겨지는 데이터가 동일 할때 React.memo 를 사용하는 방법은 두 가지가 있습니다.

1. 기본 탑재된 비교 알고리즘을 쓰는 경우

React.memo(컴포넌트)

기본 알고리즘을 쓰는 경우는 Props로 넘어오는 값들이 이뮤터블임을 전제로 합니다. 뮤터뷸 객체가 넘어오면 당연히 이 전략은 통하지 않습니다. 여기서 신경써야하는 것들이 바로 핸들러나 콜백 함수들인데, JS에서 함수는 뮤터뷸한 객체라서 useCallback 으로 일일이 감싸서 캐시(메모이제이)하지 않으면 효과가 없습니다.

그렇다고 항상 useCallback 쓸 필요도 없습니다. 캐시 비용도 비용이지만 매번 콜백 함수를 감싸는 귀차니즘도 만만치 않기 때문에 그 비용이 오히려 비효율일 경우가 많습니다. 이럴때는 비교 로직을 커스텀 하는게 더 효과적 입니다.

2. 비교 알고리즘을 커스텀 하는 경우

React.memo(컴포넌트, 비교로직함수)

비교로직을 커스텀 할경우엔 콜백함수를 useCallback 으로 넘기든 넘기지 않든 랜더링에 필요한 요소들만 선택적으로 찾아서 랜더링을 결정합니다. 따라서 메모된 값을 쓰고 싶을 경우엔 true, 새로 랜더링을 할 경우엔 false를 비교 함수에서 반환합니다.

최적화 우선순위

모든 컴포넌트를 다 useCallback 으로 감싸거나 React.memo로 감쌀 필요는 없습니다. 병목 지점을 먼저 찾고 필요한 곳 위주로 처리하면 됩니다.

예를들어 아래와 같은 구조에서 최상위 Root 컴포넌트가 랜더링되면 나머지 A~F 컴포넌트가 줄줄이 랜더링 됩니다. 이때 A가 한번 랜더링 되면 F 같은 리스트 아이템들은 수없이 많이 랜더링 됩니다.

  ---Root
      |--- A
           |-- B ( List )
               |-- F
               |-- ... N 개의 F 
               |-- F
           |-- C
      |-- D

즉, 위와 같은 구조에서 F 컴포넌트가 제일 먼저 최적화 대상이 됩니다.

극단적인 랜더링 최적화 (한번만 그리기)

가끔은 컴포넌트를 딱 한번만 그리는 경우도 있습니다. 예를 들면 고정형 네비게이션바 같은 경우엔 Props를 주입받지만 화면을 그리는 요소에 영향을 주지 않기 때문에 이런경우엔 극단적인 선택을 할수있습니다.

export default memo(NavigationBar, () => true);

클로저 변수와 함께 콜백 함수 메모하기

   1번
   const 핸들러함수 = useCallback(()=>{
         const currentItem = items[currentIndex];
      //  currentItem 처리
   },[currentIndex])

    2번 
    const 핸들러함수 = (currentItem) => {
         // currentItem 처리....
    }   

    <List>
        {for item in items 
            return <ListItem item={item} onPress={핸들러함수}>
         }
     <List>

1번처럼 useCallback 함수를 사용해 클로저 변수를 함께 패키징 할 경우 핸들러 함수는 currentIndex 값에 따라 매번 다른 함수가 만들어집니다.

그리고 이것을 <ListItem> 컴포넌트의 프로퍼티로 넘기기 때문에 <ListItem> 컴포넌트의 메모 전략은 매우 단순하게 가져갈 수 있습니다.

export default memo(ListItem);

물론 currentIndex 갯수 만큼 메모하는건 그만큼 약점입니다.

반대로 useCallback 을 쓰지 않을 경우에는 당연히 ListItem의 최적화 전략이 달라져야합니다.

export default memo(ListItem, (prev: Props, next: Props) => {
  return prev.language === next.language;
});

그리고 2번처럼 핸들러 함수에 넘겨받은 값을 그대로 같이 반환해야합니다.

function ListItem(props){
  const { item } = props;

  return( 
     <TouchableOpacity onPress={() => onPress(item)}>
       // 블라블라...
     </TouchableOpacity>
  )
}

주의! 최적화 전략은 케바케라서 상황에 따라 적절히 취사 선택하려면 잘 알아야한다. 암튼 간만에 회사 일 하다가 긴 PR을 올려서 그대로 블로그에 옮겨 왔음.

2019년 회고

회고 시즌이다. 최근 전전 회사 송년회를 갔다가 현재의 나를 되돌아보는 개기가 됐다. 도무지 나는 개발 외에 다른 대화 주제에 끼가 어려웠다. 나와 같은 공간에서 일 하는 사람들이 절반인 이 그룹에서 도대체 이들은 어떻게 이런 관심사를 유지하고 있는거지? 되돌아가는 길에 생각이 많아졌다. 바쁘다는 핑계로 나를 놓아 버린건 아닌지 덜컥 겁이났다.

어제도 옆지기와 한해를 마무리하며 물었다. 나는 변한거 같니? “응! 좀 더 나은 방향으로 변했어!” 그랬다. 나는 변했고 성장했다. 돌이켜보면 “바빴음”은 핑계가 아니라 나의 선택이었다.

올 한해는 일에 파묻혀 살았다. 그간 자잤던 이직탓에 이번엔 오래 다닐수 있을까 싶었는데 순식간에 1년 3개월이 지났다. 그동안 심어 놓은 잔디가 나를 증명한다. 10월에는 그동안 달려온 나를 위해 긴 휴식을 줬다. 휴가 기간엔 사실 회사일만 놓았지 코드는 여전히 놓지 못했다.

스쿼시 머지 정책에 내 커밋들이 하나로 퉁쳐진게 이 정도지 사실 코드를 놓은 날이 거의 없었다. 주말에도 간간히 로그가 찍힌 걸 보니 참 열심히도 살았다.

회사일
회사일 + 개인 프로젝트

작년 6월 부터는 사실상 매니저 역할에도 손을 뗐다. 조직을 좀 더 나은 방향으로 움직이기 위해서 권한이 필요했고 매니저의 역할이 중요하다고 생각 했는데 막상 디렉터라는 타이틀을 달고보니 내가 원하는 권한은 없고 그냥 일만 더 많아졌다.

그래도 자초한 일이라 잘해보려고 이런저런 시도를 했다. 그 중에서 개인 인터뷰를 했던 두달의 시간이 그나마 즐거웠다. 바쁘다보니 9명을 인터뷰해서 전체 공개하는데 두달이 걸렸다. 참고로 인터뷰 내용은 PR 과정을 통해 보정되고 전체 공개된다. 물론 사전에 공개를 꺼리는 답변은 둘만의 비밀로 간직하고 있는데 사실 기록하지 않은 내용이라 금새 다 잊어먹었다. ㅎㅎㅎ

앱 개발

내가 매니징을 포기하게 된 원인은 바로 이 앱개발 때문이었다. 그동한 혼자서 고군 분투 하던 멤버가 안쓰러워 백업 하러 갔다가 박제가 되버렸다. 당시 내가 앱 개발을 백업 해야겠다는 생각은 의심할 여지없이 너무 자연스러운 결정이었다. 만약 내가 매니징만 하는 매니저 였다면 아마 다른 선택을 했었을것 같다. (못해! 불가능해!)

어쨌든 이 결정으로 인해 나는 10년이 넘는 내 커리어에서 두번째 번아웃을 겪었다. 건강에 적신호가 켜지고 알러지를 얻게 됐는데 다행히 지금은 나아지고 있다. 이 일로 지난 시절 열심히 운동했던 나에게 큰 감사를 하게 됐다.

반면 얻은 것도 많다. 먼저 사람을 얻게됐고 기술에 대한 자신감도 얻었다. Swift 는 원래 좋아했던 언어였고, Kotlin도 새롭게 알게됐다. Java는 좋아하는 언어는 아니지만 RN 모듈을 보다보면 어쩔수없이 Java와 Object-C도 알게되더라… ㅎㅎㅎ 여기에 TypeScript를 주언어로 쓰게되면서 그동안 type에 대한 거부감도 사라졌다.

자세한 앱개발 후기는 조금씩 정리해서 올해는 글로 써야겠다. 어쨋든 앱을 다시 만든다면 주저없이 나는 RN을 선택하게 될것 같다. 실제로 지금 개인 프로젝트로도 RN을 쓰고 있다.

34일의 휴가와 여행

휴가는 작년에 총 34일을 썼는데 숫자만 놓고 보면 많이 썼다. 회사가 무제한 휴가인건 분명 좋은 점 같다. 휴가를 면밀히 들여다보면 연초에 잡힌 강의로 10일과 번아웃 회복에 쓴 13일을 빼면 개인 휴가는 11일 밖에 안된다. 숫자만 많아보이지 왠지 진 기분이든다.

1월 충주 수안보

너무 추워서 짧은 일정에 온천을 다녀왔다. 이때만해도 차가 없어서 대중교통으로 다녀왔는데 날씨가 추워서 인지 시골이 원래 그런거인지 참 맴이 좀 그런 여행이었다.

5월 발리 2주

아내의 강력한 의지로 발리행 비행기에 몸을 싣고 2주간 원격근무를 하게 됐다. 원격근무가 일상인 나에게 발리라고 크게 다를쏘냐 싶었는데 확실히 발리에서 생활은 한국보다 쾌적하다. 인터넷이 간간히 끈겨서 불안하긴한데 무엇보다 규칙적인 생활을 하게 되는게 가장 크다.

9월 가족여행

부모님과 동생네까지 동반 세 가족이 매년 300만원이 모이면 떠나자고 부은 여행계가 첫 결실을 맺고 어렵게 일정을 잡아서 떠났다. 때마침 엄청난 태풍의 여파로 한가했다. 하지만 역시나 여행은 날씨가 절반! 다음엔 좀더 좋은 날에 갔으면 좋겠다.

여름 손님과 사랑방 손님

재작년 이사하는 날에 캐나다 친구가 우리집에 놀러온걸 시작으로 우리집은 게스트하우스처럼 많은 손님들이 왔다 갔다. 지인의 지인 친구인 호르헤(칠레계 미국인)를 시작으로 발리에서 알게된 폴란드 친구 막다와 인도 친구 프리야. 사실 나보다는 아내 지인이라고 봐야지. 암튼 그리고 호랑이보다 무섭다는 여름 손님 민재가 두달간 우리집에서 머물게 됐다! 뚜뚱~!

신기한건 이들 모두 발리에서 만난 친구들이고 이들 모두 다녀가고 난 뒤에야 그토록 기다리던 우리 아들 베베가 찾아왔다.

PC 게임 VS 보드 게임

라이엇게임즈에 잠깐 몸 담았던 계기로 리그 오브 레전드를 심심치않게 하게됐는데 민재가 우리집에 온 날을 시작으로 다시 롤을 시작했다. 역시 게임은 친구가 있어야 제맛! 최근 회사 스트레스를 빌미로 게임에 한창 빠져있었다. 이리 살면 안되겠다 싶어서 올해만 삭제를 세번째!!

이번에는 아내에게 절대 다시 게임을 설치하지 않겠다고 다짐하고 게임하고 싶을땐 게임방 가겠다고 선언했다. 아무튼 그 이후로 나는 아직까지 롤을 하지 않고 있다. 가끔 하고 싶다는 생각이 드는데 그때마다 예전처럼 다시 코딩을 한다.

보드 게임도 어쩌면 민재덕이다. 발리에서 같이 재밌게 한 경험이 쌓이다보니 나도 아이 생기면 보드게임을 해야겠다 싶어서 마트를 갈때마다 보드게임 코너를 유심히 본다. 그렇게 사온 보드 게임이 벌써 5개쯤 되는거 같다. 언제 베베랑 같이 하려나… 🙂

원격 근무와 알러지

올해 나를 괴롭히는 최대 적으로 떠올랐다. 바빠서 자연스레 재택근무가 많아지다보니 건강에 적신호가 켜졌다. 눈뜨면 씻지도 않고 의자에 앉아서 아침 8시부터 그대로 저녁 9시 까지 앉아 있는날이 수도 없이 많다보니 다리가 퇴화될 지경에 이르렀다.

가끔 회의 때문에 어쩔수없이 출근하는 날엔 다리가 엄청 쑤시고 아팠다. 그러다가 지난 8월부터 알러지가 올라오기 시작하더니 최근까지 한참 고생했다. 건강 검진에는 무려 160여가지 알러지 테스트에 절반 이상에 알러지 반응이 나타났다.

예전에도 가끔 와인 마시다가 팔에 잠깐 올라왔던 적은 있었는데 이번처럼 온몸에 급속도로 퍼지는 속도가 약을 먹지 않고는 견디기 힘든 지경에 이르러 사태에 심각성을 알게됐다. 건강엔 나름 자신있던더라 진짜 자만했다. 역시 나이는 못 속인다. 최근에 페이스북에 알러지에대한 심경을 토로 했더니 페친들이 염려와 격려에 눈물이… ㅜㅜ

그 이후에 의식적으로 일을 줄이고 있다. 일에 과 몰입하는 습관을 없애기위해 일부러 출근도 하고 집보다는 가까운 카페에서 일 하려고 노력하고 있다. 다행이 이런 노력이 헛되지 않았고 최근 한달간 약없이 잘 지내고 있다. 알러지가 올라와도 전신으로 퍼지지 않고 이내 자자들고 있다. 리모트 하려고 이곳으로 왔는데 다시 출근하는 회사로 이직할까 싶다.

개인 프로젝트와 신년계획

오프라인 강의

강의는 늘 부담이지만 부담감을 통해서 내가 알고 있는 지식들을 정리하게 되고 수강생들의 긍정 피드백을 받으면 보람도 느낀다. 매번 강의 할때마다 이번이 마지막이야 했지만 의뢰가 오면 또 고민하다가 하게되겠지. 물론 거절한 강의도 무려 3건이나 있다. ㅎㅎㅎ

올해는 Javascript, React 와 더불어 React Native, NodeJS 강의 가능합니다. 메일(miconblog@gmail)로 문의주세요.

개발자 방송: TV 플루토

작년초까지 나름 취미 생활로 잘 이어가다가 바빠서 흐름을 한번 놓치니까 더이상 방송할수 없는 주제가 되어버렸다. 한창 만들던 코딩 프로젝트가 Nextjs5 버전이었는데 벌써 9 버전이라니 ㅎㄷㄷ 몇주전부터 Nextjs로 다시 개인 프로젝트를 시작했는데 방송 분량이 확보되면 다시 시작해야겠다.

가족계획

정말 거짓말처럼 마음을 비우니까 찾아왔다! 지금은 엄마 뱃속에서 잘 크고 있다. 올해가 더욱 기대된다. 지인들이 육아휴직 쓰라던데 우리회사가 육아휴직 제도가 있나 모르겠다. ㅎㅎㅎ

겁쟁이 사자

아내와 마트에 가면 내가 꼭 들르는 코스가 있는데 맥주 코너와 의류코너 그리고 문구점 코너다. 그러다가 최근 의류 코너에서 내 눈을 사로 잡는 옷을 발견했다. 개인적으로 사자를 좋아해서 영어 이름도 레오를 쓴다. 참고로 스페인어로 레오는 사자다. 아무튼 마트에서 발견한 사자 그림이 너무 귀여워서 아내를 꼬셔왔더니 “이거 오즈의 마법사 시리즌데?”

그래서 깡통나무꾼과 겁쟁이 사자를 집으로 들였다. ㅋㅋㅋ 그리고 깃헙 ORG에 겁쟁이 사자를 선점했다. 앞으로 개인 프로젝트는 “겁쟁이 사자”라는 이름으로 출시 해야지!

아내는 “멋쟁이 사자처럼” 아류라고 하지만 절대 아니다. 이건 달라! 무려 겁쟁이 사자라고! 나도 용감해지는 알약먹으면 용감해진다고!!

Nginx로 AWS ELB 대체하기

몇달 전 기존에 쓰고 있던 AWS EC2(t2.micro) 인스턴스 2개를 비용 문제로 예약 인스턴스로 바꿨다. 그런데 비용을 좀 아끼자고 결정한 일이 오히려 비용을 몇배로 키웠다. 

예약 인스턴스를 구매하고 ELB 사용하면서 비용이 더 증가했다.

처음엔 예약 인스턴스 기간이 제대로 설정이 안됐나 싶었지만 상세 내역을 보니 문제는 바로 ELB 였다. AWS에서 이녀석을 어떻게 구현했는지 모르겠지만 ELB 한대당 t2.micro 서버 한대와 맞먹는 요금이 나오는걸로 봐서는 ELB를 EC2 위에 올려서 서비스하는건가? 하는 생각도 든다.

ELB는 왜 썼나?

비용 문제를 제외하면 라우터(Route53)와 도커 컨테이너 사이에 로드발란서(ELB)를 두고 포트를 맵핑하는 방법은 정말 간편하다. 특히 SSL 인증서를 ELB에 넣는 것도 클릭 몇번이면 다 해결되지 이걸 안써야할 이유를 못찾을 정도 였는데… 이제서야 그 이유를 찾은것 같다. 쉬운 만큼 비싸다! 그럼 각설하고 ELB 대신에 nginx 를 설정해보자.

ELB를 대신할 nginx 의 위치

도커가 설치된 호스트 서버를 최전방에 위치시키면 출처를 알 수 없는 수많은 트래픽을 받아야하기 때문에 최대한 ELB 뒤에 숨기고 싶었으나 이제는 선택의 여지가 없다. 그래도 EC2 서버 자체가 털리지 않도록 AWS 보안 그룹 설정에서 80과 443 포트만 와이드 오픈으로 열어두자. 

어찌됐든 도커가 설치된 EC2 서버에 nginx를 설치해야되는데, 이 녀석을 도커 컨테이너로 띄울지 EC2 서버에 직접 서비스로 올릴지는 잠깐 고민했다. ELB를 대신할 nginx의 역할은 부하 분산의 목적보다는 한 서버위에 여러 컨테이너로 올려지는 서비스에 포트를 바인딩하기 위한 멀티 호스팅이 주 목적이기 때문에 그냥 EC2 서버에 직접 올리기로 했다.

nginx를 설치하고 멀티 호스팅 설정은 /etc/nginx/conf.d/ 폴더 하위에 호스트 별로 설정을 추가한다. 먼저 이 블로그는 도커 컨테이너의 8888번 포트와 맵핑되어 있다. 

## /etc/nginx/conf.d/miconblog.com.conf 파일
server {
	listen 80;
        server_name miconblog.com;

        location / {
                proxy_pass http://127.0.0.1:8888;
        	proxy_set_header X-Forwarded-For $remote_addr;
	        proxy_set_header Host $http_host;
        }

}

마찬가지로 운영중인 서비스도 같은 방식으로 맵핑한다.  

## /etc/nginx/conf.d/rlibro.com.conf 파일
server {
        server_name rlibro.com;

        location / {
                proxy_pass http://127.0.0.1:8080;
        	proxy_set_header X-Forwarded-For $remote_addr;
	        proxy_set_header Host $http_host;
        }
}

EC2 서버에 certbot 설치하기

이제 ELB를 선택한 또다른 이유중 하나인 SSL 인증서 문제를 해결해보자. SSL 인증서는 직접 구매해도 되지만 도메인을 가지고 있다면 Let’s Encrypt를 이용해 무료로 쓸수도 있다. 

Let’s Encrypt로 인증서를 발급받으려면 certbot 이라는 프로그램을 이용해야한다. 일단 certbot 패키지를 설치해야하는데, 내가 쓰고 있는 CentOS7 버전은 yum을 이용해 설치할수있다는데 몇몇 라이브러리의 파이썬 버전이 맞지 않아서 직접 다운로드 받아 설치했다.  설치는 아래 링크를 참고하자.

https://certbot.eff.org/lets-encrypt/centos6-nginx

Nginx 가 설치된 서버에서 certbot을 설치하고 아래 명령을 입력하면 certbot이 알아서 nginx 설정을 읽어와 원하는 도메인에 인증서를 설치해준다. 

$> ./certbot-auto --nginx

위 명령을 입력하면 앞에서 nginx에 2개의 멀티 호스트를 설정했기 때문에 certbot은 두 도메인 중 어떤 녀석을 HTTPS로 서비스하고 싶은지 물어봐준다. 당연히 나는 2번을 선택!

Requesting to rerun ./certbot-auto with root privileges...
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
1: miconblog.com
2: rlibro.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 2

... 중략 ... 

그럼 인증서를 /etc/letsencrpyt/live/도메인명/ 하위에 설치해준다. 그리고 이어서 http 요청이 들어오면 https로 강제 redirect 할지 말지를 묻는다. 나는 역시 강제 리다이랙션 옵션인 2번을 선택!

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2

.... 중략 ...

자 이제 인증서 설정이 끝났다. nginx를 재시작하면 miconblog.com 과 rlibro.com 두 서비스가 모두 정상 동작한다. 

그리고 SSL 인증서를 설치한 rlibro.com.conf 파일을 열어보면 certbot 직접 추가한 설정을 볼수있다. 

server {
    server_name rlibro.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
      	proxy_set_header X-Forwarded-For $remote_addr;
	proxy_set_header Host $http_host;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/rlibro.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/rlibro.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = rlibro.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name rlibro.com;
    return 404; # managed by Certbot
}

와~ 세상 정말 좋아졌다!