728x90

Node는 기본적으로 싱글스레드 기반이기 때문에

내부적으로 서버를 Fork해서 여러 스레드를 쓰는 방법을 지원해준다.

바로 cluster.

기본적으로 내장되어있으며 그 위력은 어마어마하다.

 

그런데 linux에서 cluster를 실행한 코드를 pm2로 실행하다보니

난관에 봉착했다.

 

에러가 발생한다.

cluster가 설정된 코드를 실행할수없었다.

 

그런데 node의 공식문서에서 방법을 찾았다.

문서의 일부 내용을 발췌했다.

 

======================================================

PM2 사용

애플리케이션을 PM2에 배치하면, 애플리케이션 코드를 수정하지 않고도 클러스터링을 활용할 수 있습니다. 먼저 여러분의 애플리케이션이 stateless인지 확실하게 해야합니다. 어떠한 로컬 데이터도 프로세스에 저장되지 않아야 합니다. (세션이나 웹소켓 같은 것들 말입니다).

PM2로 애플리케이션을 실행하고 있을 때, 특정한 수의 인스턴스에 실행하는 클러스터 모드를 켤 수 있습니다. 머신의 가용 CPU 수같은 것들이 특정한 수입니다. 애플리케이션을 끌 필요 없이 pm2 커맨드라인 명령을 이용해 클러스터에 있는 프로세스의 수를 직접 바꿀 수도 있습니다.

 

아래와 같은 방법으로 클러스터 모드를 킵니다.

# Start 4 worker processes

$ pm2 start app.js -i 4 # Auto-detect number of available CPUs and start that many worker processes

$ pm2 start app.js -i max

이 수는 PM2 프로세스 파일 (ecosystem.config.js나 그와 유사한 파일) 안의 exec_mode를 cluster나 instances를 설정해서 수정될 수 있습니다.

 

실행이 시작되면, app으로 이름지어진 애플리케이션을 아래와 같은 방법으로 스케일링 할 수 있습니다.

# Add 3 more workers

$ pm2 scale app +3 # Scale to a specific number of workers

$ pm2 scale app 2

PM2의 클러스터링에 관한 추가 정보는 PM2 문서의 Cluster Mode를 참고해주세요.

======================================================

 

 

728x90
반응형
728x90

사용자 기반 서비스를 만들다보니 Textarea에서 라인별 글자수 제한 및 총 라인 제한을 해야할 일이 생겼다

아래와 같은 코드로 해당 문제를 처리하였다.

 

필요한 사람들을 위해 공유한다.

const textareaLimitWordAndRow = (text, limitWordPerline, limeLines) => {
  let lines = text.split('\n');
  const charlimit = limitWordPerline; // 글자수 제한

  for (let i = 0; i < lines.length; i++) {
    if (lines[i].length <= charlimit) continue;
    let j = 0;
    let space = charlimit;
    while (j++ <= charlimit) {
      if (lines[i].charAt(j) === ' ') space = j;
    }
    
    lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || '');
    lines[i] = lines[i].substring(0, space);
  }

  return lines.slice(0, limeLines).join('\n');
};

 

 

728x90
반응형
728x90

구글에서 제공해주는 도구들을 이용해 체크해보니 운영중인 웹사이트 로딩속도가 어마어마하게 느렸다

모바일 기준으로 19.7

 

경악스러웠다.

당장 손볼수 있는것들을 손봤고 약 2배정도 빠른 속도를 얻어 방법을 공유한다.

 

이미 되어있던 퍼포먼스 관련 설정들

1. 서버 로드밸런스

2. 부분적으로 적용한 이미지 cloud front

 

 

추가한 퍼포먼스 관련 설정들

1. Nginx - gzip

2. Webpack - compression

3. Code spliting

4. 전체 이미지 cloud front 적용

 

이 중에서 가장 쉽고 큰 효과를 얻은것은 gzip이 아닌가싶다

주의할점은 클라이언트와 웹서의 서버가 분리되어있다면 양쪽 모두에 설정해주어야 한다는 점이다.

 

코드는 간결하다. nginx.conf 파일에 아래의 내용을 설정한다.

http {
...

gzip  on;

gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript;

...
}

 

코드 스플리팅은 react의 lazy/suspense를 이용해서 간단히 설정해주었다.

코드를 너무 작은단위로 나눠도 호출하는 시간에서 손해가 발생하기 때문에 webpack plugin 설정에 아래와같이

최소 크기를 설정해주었다

...,
plugins: [
  ...,
  new webpack.optimize.MinChunkSizePlugin({
    minChunkSize: 512000, // 50kb
  }),
  ...          
]

compression은 역시 plugin 에서 간단히 추가했다.

[
  new CompressionPlugin({
    filename: '[path][base].gz',
    algorithm: 'gzip',
    test: /\.js$|\.css$|\.html$/,
    threshold: 10240, // 10kb
    minRatio: 0.8,
  }),
]

 

728x90
반응형
728x90

Rest API서버와 DB서버가 각기 다른 서버에서 운영되는건 매우 흔한일이며

지극히 정상적인 일이라고 볼수있다.

 

서비스의 규모가 절대로 커질일이 없는것이 아니라면 일단은 분리하는게 좋다는 생각이다.

그럼에도 개발속도와 편의성 측면에서 한 서버에서 API서버와 DB를 운영하고는 하는데 (제가 그랬습니다...)

 

두 서버를 분리시키고 데이터 마이그레이션하는 방법에대해 소개하도록 하겠습니다.

 

 

1.  DB서버 생성 및 몽고 환경 셋업

이 부분에서 사실 제일많은 시간비용이 소모됩니다.

왜냐면 1~10까지 구구절절 설명해주는 자료가 많이 부족하기 때문이죠.

 

아래와 같은 작업을 해야합니다.

1) EC2환경설정

2) 몽고디비 설치

3) 몽고디비 실행

4) 몽고디비 관리자 계정 설정 및 readwrite계정 추가

5) .conf파일 수정

6) 몽고디비 재실행

7) DB서버에 연결 테스트

8) 데이터 덤프 및 마이그레이션

 

1) EC2환경설정

여기서 할것은 보안그룹설정인데요

몽고의 기본포트인 27017포트를 개방해줍니다.

 

 

2) 몽고디비 설치

각 OS마다 설치방법은 다르지만, 설치와 관련해서는 Mongo에서 매우 친절하게 안내해주고있으니 참고하시기 바랍니다

docs.mongodb.com/manual/installation/

 

 

3) 몽고디비 실행

실행, 정지, 재실행, 상태확인에 대해서 간단히 명령어를 알아보도록 하겠습니다.

두가지 버전이 있는데요, systemctl / service 두개의 키워드를 통해 가능합니다.

 

(실행) stystemctl start mongod

(중지) systemctl stop mongod

(재실행) systemctl restart mongod

(상태확인) systemctl status mongod

 

일단 설치했으면 실행해줍니다.

systemctl status mongod 를 입력해 정상적으로 동작중이며, mongo를 입력했을때 몽고디비로 진입된다면

잘 설치 및 실행되었다고 볼 수 있습니다

 

 

4) 몽고디비 관리자 계정 설정 및 readwrite계정추가

여기서 많이 헤매게됩니다.

기본적으로 로컬에 있는 몽고에서는 해주지않아도 되는데 (단, 하지않았을때 보안의 위험이있습니다)

리모트 서버의 DB에 연결하려면 반드시 계정설정을 해주어야됩니다.

외부에서 연결을 요청하면 몽고에서 사용자 권한을 확인하기 때문입니다.

 

먼저, 관리자 계정을 생성해주어야합니다.

관리자 계정이란 정말로 슈퍼어드민을 의미하며 DB를 드랍,추가 하는것까지 가능하니 계정관리에 주의해야합니다.

 

몽고에는 기본적으로 admin이라는 db가 생성되어있는데요. 다음과 같은 절차를 밟도록 하겠습니다.

가) admin db 사용: use admin

나) 어드민 계정 생성: db.createUser({ user: 'admin', pwd: '패스워드', roles: [{ role: 'root', db: 'admin' }] })

다) 생성된 계정 확인: db.getUser('admin')

라) 일반 readwrite계정 생성: db.createUser({ user: 'manager', pwd: '패스워드', roles: [{ role: 'readWrite', db: 'myFirstDb' }, { role: 'readWrite', db: 'mySecondDb' } ] })

마)  생성된 계정 확인: db.getUser('manager')

 

 

5) .conf 파일 설정

거의 다 왔습니다. 리모트에서 연결해주기 위해서는 보안설정을 셋업한 상태로 몽고를 실행해주어야만 합니다.

방법은 두가진데요. 제가 확실히 말씀드릴수 있는건 .conf 파일 설정이니 이것을 따르시길 추천드립니다.

 

mongod.conf 파일은 기본적으로 /etc/mongod.conf 경로에 생성됩니다.

몽고를 설치했지만 해당위치에 mongod.conf파일이 보이지 않는다면 찾아보시기 바랍니다.

 

mongod.conf 파일에서 아래의 파트를 정확하게, 아주 똑같이 작성합니다.

 

net:
  port: 27017
  bindIp: 0.0.0.0

 

security:
  authorization: enabled

 

(mongod --auth 라는 방법도 있다는데 제가 확신할수없어 아주 개인적으로 권장하지 않습니다.)

 

여기서 0.0.0.0은 모든 IP에서의 요청을 받겠다는 의미인데, 보안계정을 생성해주었으니 믿고 가도록 합니다.

API서버 주소인 13.x.x.x,192.x.x.x 등을 입력할수도 있다고 하지만 에러가 나는 경험을 했습니다.

관련하여 아시는 분이 있다면 댓글 부탁드립니다.

 

 

6) 몽고디비 재실행

systemctl restart mongo

 

재실행 했다면 반드시 상태도 확인해주세요

systemctl status mongo

 

여기까지 잘 되셨나요?

 

 

7) DB서버에 연결 테스트

자, 대망의 연결 테스트입니다.

로컬 서버에서 테스트하셔도 전혀 무방합니다.

 

연결 주소는 아래와 같이 작성합니다.

mongod://아이디:패스워드@도메인혹은아이피:27017/디비명

 

예제입니다.

mongod://manager:openpassword@cup.camil.com:27017/coffee

 

API서버에서 직접 연결을 체크해봐도 좋고

MongoBB Compass같은 툴을 통해 연결을 시도해 봐도 괜찮은 방법입니다.

후자의 경우 다른 코드에서 발생하는 에러와 분리하여 테스트할 수 있다는 장점이 있습니다.

 

 

8) 데이터 덤프 및 마이그레이션

여기는, 기존의 DB가 있는경우에만 진행해주시면 됩니다.

 

기존의 API서버에서 먼저 데이터를 덤프하는데요, 덤프란 기존의 데이터를 백업하기위해 카피한다고 생각하시면 됩니다.

BSON 타입으로 하는방법과 JSON 타입으로 하는방법 2가지가 있는데요.

여기서는 속도가 더 빠르다고하는 BSON 타입을 채택하여 진행하도록 하겠습니다.

 

아래의 명령어를 사용하면 내 현재 위치(경로)에 dump라는 폴더 내에 데이터가 덤프가 됩니다.

mongodump --host 127.0.0.1 --out /data/db/backup

 

덤프된 데이터를 FileZila와 같은 프로그램을 이용해 새 DB서버로 이전해줍니다.

이 때, 덤프된 파일들을 어디로 이전하는지 정확히 위치를 체크하도록 합니다.

 

* /home/root/  정도의 경로에 backups폴더를 생성하여 이전하는것을 추천드립니다.

 

데이터를 옮겼다면, 콘솔에서 해당 경로로 이동합니다.

위와 같이 했다면

cd /home/root/backups

가 되겠죠?

 

마지막입니다. 데이터 마이그레이션을 진행해주면 되는데요, 지금 디비가 텅텅 비어있으니 간단히 처리할 수 있습니다.

단, 진행 전 잠시 mongod.conf 파일의 security파트를 주석처리 하도록 합니다.

 

#security:
  #authorization: enabled

 

systemctl restart mongo

systemctl status mongo

 

몽고의 상태가 정상이라면 마이그레이션을 진행합니다.

mongorestore --host 127.0.0.1 --port 27017 --db 복구하려는 db명 덤프된 디렉터리 위치

 

예제입니다.

mongorestore --host 127.0.0.1 --port 27017 --db coffee ./coffee

 

*********** 수정***********

덤프될 디렉터리 위치를 잡을 필요없이 간단히 아래와 같이 복원가능합니다.

mongorestore -d db db

************************************

 

정상적으로 마이그레이션 됐는지를 먼저 체크해볼까요?

mongo

show databases

>> coffee DB가 존재하나요?

 

use coffee

show collections

>> collection들은 잘 이전되었나요?

 

잘 되었다면 다시 security설정을 해줍니다.

 

security:
  authorization: enabled

 

systemctl restart mongo

systemctl status mongo

 

 

============================

 

고생하셨습니다. 여기까지입니다.

이 글이 몽고 원격 연결 및 마이그레이션의 성지가 되기를 바라며

부족한 글 많은 분들의 댓글로 꽉꽉 채워지기를 바라겠습니다.

 

728x90
반응형
728x90

지금 운영중인 회사의 사이트가 모바일 가로모드에서 옆으로 돌아가는게 너무나도 싫어서 막는방법에 대해서 알아봤다.

 

1. meta tag설정

2. css 트릭

3. jquery 트릭

4. vanila javascript 설정

5. manifest 설정

 

결론부터 말하면 다 소용없었다.


MDN에 보면 screen orientaion과 device orientation이 다름을 명확히 구분하고있고

screen과 달리 device에 대해서는 별다른 방법이 없는것들만 나타나있다.

 

여기서 screen과 device의 가로모드가 어떻게 다르냐고 한다면

device가 landscape(가로)/portrait(세로)가 돌아감에 따라 screen이 함께 돌아가는데

 

screen은 그에맞게 css를 통해 어느정도 맞춰줄수있지만(@media쿼리 활용)

device가 돌아가는것 그 자체에 대해서는 아직까지는 대응할(막아버릴) 방법이 없어보인다.

 

앱이 내 소유물이라 하면 브라우저는 내 소유물이 아니라 이런 환경적인 부분의 설정에있어서는 한계가 있는걸까?

라는 아쉬움이 남는다.

 

브라우저가 돌아가는것을 막는 좋은 방법을 알고계신분이 있다면, 댓글 달아주시면 감사하겠습니다.

728x90
반응형
728x90

종종 캐시로인해 .gitignore의 untracking파일이 제대로 안먹힐때가 있다.

 

이때는 간단히 캐시를 제거해주는것 만으로 해결이 가능하다.

 

git rm -r --cached .

git add .

git commit -m "fix untracked files"

git push

 

commit까지 진행하고나면 untracking파일들이 삭제되는걸 볼수있다.

728x90
반응형
728x90

S3에 업로드된 파일의 이름변경, 언제 필요할까요?

특정한 이름 구조나 패턴을 갖춘 파일들이 필요할 때가 분명히 있습니다.

제가 이번에 해당 경험을 하게되어 간단히 정보를 공유해보고자 합니다.

 

 

 

S3에서 제공하는 API를 활용하면 제법 다양한 일들을 할 수 있습니다.

그중에서 이번엔 버킷에서 항목들의 목록을 가져오고, 항목들의 이름을 변경하는 방법에 대해서 알아보고자 합니다.

먼저, S3 API 중에 항목의 이름을 변경하는 API는 존재하지 않습니다.

따라서, 항목을 카피하는 copyObject를 이용해  변경된 이름의 Object를 생성하고 원본을 지우는 방식을 채택해야합니다.

 

항목들을 가져올때는 listObjectsV2를 사용하는데요 간단한 예시를 들어보겠습니다.

const config = {
  accessKeyId: process.env.AWS_KEY,
  secretAccessKey: process.env.AWS_SECRET_KEY,
  region: 'ap-northeast-2',
  signatureVersion: 'v4',
};

AWS.config.update(config);
const s3 = new AWS.S3();

s3.listObjectsV2({ Bucket: `pdf.pitchit`, MaxKeys: 1000 }, async (err, data) => {
  const { Contents } = data;
  const keys = Contents.map(info => info.Key);
  
  // 키를 활용해 다양한 액션을 취할 수 있습니다.
  // 그중에서 객체 복사는 다음과 같이 간단히 할 수 있습니다.
  
  const info = {
    Bucket: 'origin_bucket',
    CopySource: `/target_bucket/${key}`,
    Key: new_name, // 변경할 이름입니다.
  };
  
  s3.copyObject(info, async (err, data) => {
    ...
  });
});

 

 

 

728x90
반응형
728x90

프로젝트를 오래 진행하다보면

commit  / tag / branch등으로 인해 .git/objects폴더가 굉장히 거대해진다.

로컬에서는 문제없지만, 스토리지 사이즈가 작은 Linux서버에서는 제법 치명적이다.

그래서 해당 폴더를 관리하기위한 방법을 찾았다.

 

=============================================

git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.mov' -- --all

rm -Rf .git/refs/original

rm -Rf .git/logs/

git gc --aggressive --prune=now

==========================================================================================

 

 

나는 마지막 한줄만을 사용중이다.

728x90
반응형
728x90

웹개발을 하다보면 브라우저 호환성으로 인해 스트레스를 많이 받습니다.

 

하지만, 종종 특수한 경우에 모든 브라우저를 지원하기위해 노력할 필요가 없을때가 있습니다.

예를들어, 개발 초기부터 "IE는 배제하고 다른 사용자 브라우저에 집중하기로 한다"하고 미리 약속을 할 때가 있죠.

 

그런 조금은 행복한 경우를 위해,

"ancient"브라우저들을 nginx수준에서 막아버릴 방법을 제시해드리도록 하겠습니다.

(의외로 잘 정리된 자료가 없더군요)

이 방법은 Nginx가 늘 사용자 request의 브라우저를 식별하고 있음을 이용한 방법입니다.

 

# /etc/nginx/nginx.conf

...
http {
    ...
    map $http_user_agent $outdated {
        default                                 0;
        "~MSIE [1-9]\."                         1;
        "~MSIE [0-9][0-9]\."                    1;
        "~Trident/.*"                           1;
        "~Edge/[0-9]\."                         1;
        "~Edge/[0-9][0-9]\."                    1;
        "~Edge/.*"                              1;
        "~Mozilla.*Firefox/[1-9]\."             1;
        "~Mozilla.*Firefox/[0-2][0-9]\."        1;
        "~Mozilla.*Firefox/3[0-1]\."            1;
        "~Opera.*Version/[0-9]\."               1;
        "~Opera.*Version/[0-1][0-9]\."          1;
        "~Opera.*Version/2[0-1]\."              1;
         "~AppleWebKit.*Version/[0-6]\..*Safari" 1;
         "~Chrome/[0-9]\."                       1;
        "~Chrome/[0-2][0-9]\."                  1;
        "~Chrome/3[0-3]\."                      1;
    }
    ...
}
...

 

다음은, /etc/nginx/sites-enabled (혹은, /etc/nginx/sites-available/default)를 수정할건데요.

여러분이 설정해둔 server중 적용하고 싶은 파트에 적절히 배치하면됩니다.

예시를 통해 확인하고 응용해보세요.

# /etc/nginx/sites-enabled/default 혹은 /etc/nginx/sites-available/default

...

server {
    root /home/ubuntu/hello_world
    index index.html
    server_name www.helloworld.com
    
    location / {
        if ($outdated = 1) {
            root /home/ubuntu/unsupported;
            rewrite ^ /index.html break;
        }

        try_files $uri $uri/ /index.html;
    }
    ...
}

...

보시는 바와 같이 1번 블럭 코드의 브라우저(및 버전)에 해당될경우

/home/ubuntu/unsupported 경로의 index.html 파일을 response합니다.

unsupported경로에 index.html 파일만 적절히 만들어두면 되겠죠?

 

코드를 통해 알수있듯이, IE브라우저 외에도 오래된 브라우저들은 모두 redirect 시켜버립니다.

확인해보고 필요한 부분의 코드만 (제외하고싶은 브라우저만) 남겨서 사용하시면 될것같습니다.

 

# 참고자료

https://gist.github.com/rosskevin/5cfd78c73a10ca1989696350d76ea3c1

728x90
반응형

'개발, 코딩 > Nginx' 카테고리의 다른 글

웹사이트 속도 개선방법  (0) 2020.09.25
웹소켓, 400에러해결 / node가 아니라 nginx!?  (0) 2020.03.06
728x90

몽고DB에서 어떤 Array 필드에 아무런 값도 존재하지 않는 경우에는 필터링하는 방법에 대해 알아보겠습니다.

 

일반적으로 필드에 값이 존재하는지 판단할때는 $exists를 사용합니다.

...,
{
  $match: {
    name: { $exists: true }
  }
},
...

이와 같이 아주 간단히 처리할수 있습니다.

 

그치만,  필드 타입이  Array인 경우 또 다른 조치를 취해주어야합니다.

빈 값이라도 []가 들어가있다면 true로 인식하기 때문입니다.

 

따라서, 다음과 같이 처리할수 있습니다.

// 첫번째 방법
...,
{
  $match: {
    foods: {
      $exists: true,
      $ne: []
    }
  },
},
...

// 두번째 방법
...,
{
  $match: {
    foods: {
      $exists: true,
      $not: { $size: 0 }
    }
  },
},
...

두 방법다 유효하며, 저는 두번째 방법을 즐겨씁니다.

두 방법의 연산속도의 차이나 이런 부분에 대해서는 아직 잘 모르지만,

일반적으로 단순값을 통한 비교(여기서는 [] )보다는 아래와 같은 방식이 더 정확하다고 생각하기 때문입니다.

728x90
반응형