- nginx.conf 확인
파일 위치 : (Ubuntu 기준) /etc/nginx/nginx.conf
http {
...
include /etc/nginx/sites-enabled/*;
}위처럼 http { ... } 안에서 /etc/nginx/sites-enabled/*; 를 함으로써 sites-enabled 폴더 내에 있는 모든 파일을 include 시켜주도록 한다. (아무것도 건드리지 않았다면 위처럼 되어있을 것이다.)
- 로드밸런싱 설정 파일 추가
설정 파일 위치 : (Ubuntu 기준) /etc/nginx/sites-enabled/
설정 파일 이름 : (어느 것으로 해도 상관없다) loadbalance
전체 파일 경로 : /etc/nginx/sites-enabled/loadbalance
$ cd /etc/nginx/sites-enabled/
$ nano loadbalance파일을 추가하고 아래처럼 입력해준다.
upstream backend { // Upstream(로드밸런싱을 해주는 녀석)의 이름을 "backend"로 한다.
least_conn; // 로드밸런싱 알고리즘을 "최소 연결" 알고리즘으로 한다.
server localhost:5001; // 클라이언트가 Nginx로 요청 시 우회시켜줄 Server 정보
server 127.0.0.1:5002; // 클라이언트가 Nginx로 요청 시 우회시켜줄 Server 정보
server 135.9.5.21:5003; // 클라이언트가 Nginx로 요청 시 우회시켜줄 Server 정보
}
server {
listen 80; // 클라이언트가 80 포트로 요청할 경우 해당 구문 실행
location / { // 모든 요청 "/"
proxy_set_header Host $host; // Host 헤더에 Nginx 변수값 $host 설정 -> 클라이언트의 호스트가 설정됨
proxy_set_header Connection ""; // Upstream 서버를 사용하겠다는 설정
proxy_pass http://backend; // Upstream으로 설정한 "backend"로 요청을 보낸다.
}
}위 처럼 설정하면, 80번 포트로 요청이 들어올 경우 5001 ~ 5003 포트에 열려있는 3개 서버로 로드밸런싱을 알아서 해준다. 로드밸런싱을 해주는 기준은 "최소 연결" 알고리즘으로 한다. 알고리즘을 적지 않으면 Default 값으로 "라운드 로빈(round-robin)"으로 동작한다. 위 코드가 정상적으로 동작하려면, 5001 ~ 5003 포트에 서버가 열려있어야한다.
사실, 이렇게 설정하면 기본적인 설정은 끝난 것이다. 이제 Nginx를 restart 해주고 테스트 해보면 정상 동작할 것이다.
$ service nginx restart위의 설정을 기준으로, 80번 포트로 요청이 발생했을 때 3개의 Nodejs 서버 로그를 확인해보면 매 요청마다 번갈아가면서 다른 서버로 요청이 발생한다는 것을 알 수 있을 것이다.
추가적인 사용법이 궁금하다면 개인적인 구글링이나 아래 링크를 참고해보길 바란다.
Nginx로 로드밸런싱 하기
실제로 성능 테스트를 해보면서 "동시 접속자 수"를 늘려보면 상당히 많은 에러가 발생할 수 있다.
서버의 성능 테스트를 돕는 상당히 다양한 툴이 존재한다. 필자는 Apache JMeter를 사용한다. 구글링하면 사용법에 대한 다양한 레퍼런스가 존재하니 이는 Pass하겠다.
이제부터는 각자가 구축하는 개발 환경에 대한 이해가 필요하다. 예를 들어, 필자의 경우 클라우드 서버 스펙, Node.js, Nginx, MySQL 등에 대한 이해가 필요했다.
각자의 클라우드 컴퓨터 스펙은 상이할테니 이는 생략하겠다. 필자도 마찬가지로 더 많은 공부가 필요한 이유도 있다. 일단, 필자는 CPU Core 2개, RAM 4G의 컴퓨터를 서버로 사용하여 테스트한 연구 과정을 기록한 것이다.
API 서버에 대한 일반적인 접근은 DB의 정보를 클라이언트에게 응답해주는 것이다. 따라서, 모든 요청이 DB에 접근한다는 가정하에 설명하겠다. 물론 DB가 InmemoryDB인지 RDB인지 NoSQL인지에 따라 대응이 달라지겠으나, 복잡할 수록 이해가 어렵기에 기본적인 RDB를 기준으로 하겠다.
Node에서 DB에 접근할 때는 기본적으로 아래와 같이 설정한다.
필자는 MariaDB를 사용하지만, 근본은 MySQL과 동일하다.
위 코드에서 눈여겨보아야 할 곳은 DB의 설정 값이다.
connectionLimit: 500
해당 설정은 MariaDB에서 동시에 처리하고자 허용하는 요청의 개수라고 보면 될 것 같다. 즉, API서버(Nodejs) 에서 DB에 SQL 질의를 할 때마다 Connection이 1개씩 사용되는데, 이때 동시에 500개까지 사용할 수 있도록 하겠다는 것이다. Connection 개수는 개발자가 각 프로젝트 환경에 맞춰서 정해주면 된다.
다만, 이를 온전히 이해하기 위해서는 Node와 MariaDB가 연결되는 방식인 Pool에 대해서 이해하고 있어야한다. 간략하게 말하자면, Node와 MariaDB가 연결될 때 (위 코드에서는) Pool 을 생성하여 연결하고 있는데, Pool은 각 Connection을 담고있는 공간이라고 볼 수 있고, 즉, DB 요청을 위한 500개의 Connection이 Pool 안에 생성된다는 말이다. 자세한건 구글링.
각 Connection은 Node에서 DB에 SQL 질의를 할 때 사용되며, release()가 호출될 때 사용을 마친 Connection이 원래의 Pool 공간으로 반환이 된다. 여기서 release()는 아래 코드에서 주석으로 나타낸 부분을 얘기한다.
let conn;
try {
conn = await mariadb.getConnection();
const query = `SELECT * FROM tables`;
const result = await conn.query(query);
return result;
} catch (err) {
throw err;
} finally {
conn?.release(); // 이 부분을 의미한다.
}위 코드처럼, DB와 연결된 Connection을 release() 해주지 않으면, 사용된 Connection이 Pool로 반환이 되지 않으므로 사용가능한 Connection은 500개에서 499개로 줄어들게 될 것이다. 따라서, 모든 DB의 SQL 질의를 마치면 꼭 release()를 해주어야 동시 요청에 대한 트래픽을 감당해낼 수 있게 된다.
자, 그런데 위처럼 해도 500개 동시 트래픽을 감당해내지 못한다면 다른 부분을 추가 점검해주어야한다.
앞으로는 MySQL로 통일하여 설명하겠다. MySQL도 마찬가지로 클라이언트 요청에 대한 Connection을 설정할 수 있다.
Node.js에서 500개의 Connection을 허용했다 할지라도, MySQL에서 100개만 혀용했다면 100개만 될 뿐이다. 보통 위와 같은 경우 100개보다 많은 Connection을 만들고자 시도하기 때문에 MySQL에서 아래와 같은 에러를 반환한다.
Error: Too many connections설정 파일 위치 : (Ubuntu의 MariaDB 기준) /etc/mysql/mariadb.conf.d/
(MySQL의 경우는 mariadb.conf.d 폴더가 아닌 mysql.conf.d 이었던 것으로 기억하나, 아니라면 구글링.)
설정 파일 이름 : 50-server.cnf
전체 파일 경로 : /etc/mysql/mariadb.conf.d/50-server.cnf
$ nano /etc/mysql/mariadb.conf.d/50-server.cnf해당 파일을 열고, 주석 처리된 max_connections를 찾아서 원하는 값으로 바꿔준다.
필자는 1501로 하였다.
Node에서 connectionLimit을 500으로 설정하였고, 이후 Node 서버를 3개 띄운 후에 Nginx로 로드밸런싱을 해줄 것이기 때문에 총 connection은 1,500개가 될 것이다. 이를 고려하여 MySQL의 max_connections 값을 1,500보다 많은 1,501로 해준 것이다.
설정을 마쳤다면, MySQL을 restart 해주도록 한다.
$ service mysql restart이제 API 서버(Node.js)를 가동하고, MySQL에 생성되어 있는 Sleep 상태의 Connection 개수를 확인해보자. Node에서 connectionLimit을 500으로 설정했으니, Sleep 상태의 Connection이 500개일 것이다.
확인해보기 위해 MySQL로 접속해보자.
$ mysql -u root -pMySQL로 접속한 뒤, 아래 명령을 입력한다.
MariaDB [(none)]> show processlist;명령은 show processlist; 이다.
이를 입력하면, MySQL의 Pull 목록에 담겨있는 connection 목록이 쭈욱 출력될 것이다. 아래처럼.
위 사진을 보면, 총 connection의 갯수는 506개 인 것을 알 수 있다. 이는 Node.js 서버의 connectionLimit 갯수 500개와 MySQL의 Defulat Connection 갯수 6개인 것이다. 여기서 500개의 connection은 Sleep 상태임을 알 수 있는데, SQL 질의에 사용될 Connection을 의미하며 아직 사용되기 전이라 Sleep 상태인 것이다.
이제는 MySQL도 500개의 연결을 처리할 수 있는 조건이 충족되었다. 자, 그런데 위처럼 해도 500개 동시 트래픽을 감당해내지 못한다면 다른 부분을 추가 점검해주어야한다.
인프라 구조를 생각해보자. 사실은, 클라이언트의 모든 요청을 처음 마주치는 곳은 Nginx이다.
Client -> Nginx -> WAS(Node.js) -> DB(MySQL)
우리가 위에서 설정해 준 것은 Node.js와 MySQL이 500개의 요청을 처리할 수 있도록 한 것이다. 그러나, Nginx 단에서 클라이언트가 보낸 500개의 요청을 다음 단계인 Node.js로 보내주지 못한다면 Node.js는 애초에 처리해야 할 500개의 데이터를 받지도 못하는 것이다.
따라서, Nginx가 Connection 500개를 처리할 수 있도록 해주자.
설정 파일 위치 : (Ubuntu 기준) /etc/nginx/
설정 파일 이름 : nginx.conf
전체 파일 경로 : /etc/nginx/nginx.conf
$ nano /etc/nginx/nginx.conf해당 파일을 열고, events {...} 지시자 안에 있는 worker_connections를 찾아서 원하는 값으로 바꿔준다.
필자는 1024로 하였다. (1024가 일반적이라고 한다.)
- user : 계정명. 예를 들어, root 혹은 ubuntu 등의 계정을 말한다. 해당 계정으로 Nginx를 제어한다.
- www-data 계정은 루트권한으로 접근이 불가하므로 보안상 유리하다.
- root로 바꿔도 정상 동작할 것이나, 해커에게 털리면 루트권한으로 접근이 가능해지므로 보안상 매우 안좋다.
- worker_processes : nginx의 연결을 처리해 줄 녀석의 개수를 설정한다.
- auto로 설정할 경우 컴퓨터의 Core 갯수에 맞춰 자동 할당된다.
- 1로 설정하면 Core 하나를 사용하여 Worker 하나를 할당한다.
- worker_connections : Worker가 실제로 처리할 Connection의 갯수.
- 위에서는 Worker 하나당 1024개의 연결을 처리하겠다는 의미이다.
설정을 마치면, Nginx를 restart 해주도록 한다.
$ service nginx restart이제, 클라이언트의 동시 요청을 500개 보냈을 때 정상적으로 모든 요청을 처리할 수 있을 것이다. 만약 이렇게 해도 안된다면, 서버로 사용 중인 컴퓨터의 사양을 의심해보자. 필자는 CPU Core 2개, RAM 4G의 컴퓨터에서 정상 동작하였다. Core를 하나만 사용하였을 때(Worker가 1개만 할당됨)도 500개의 요청을 문제없이 처리하였다.
컴퓨터 사양에도 문제가 없다면, MySQL의 설정값 중 wait_timeout을 변경해보자.
wait_timeout은 기본적으로 s(초) 단위이며, 지나치게 짧은 단위로 설정할 경우 MySQL의 Pool 공간에 생성되어야 할 Connection이 빠른 주기로 교체되기 때문에 500개의 Connection이 담기지 않는 문제가 생긴다. 필자의 경험이다. 자세한 건 구글링.
필자는 3개의 Node.js 서버를 띄우고, Nginx가 Node.js로 부하를 분산시켜주도록 구현해보겠다.
- nginx.conf 확인
파일 위치 : (Ubuntu 기준) /etc/nginx/nginx.conf
http {
...
include /etc/nginx/sites-enabled/*;
}위처럼 http { ... } 안에서 /etc/nginx/sites-enabled/*; 를 함으로써 sites-enabled 폴더 내에 있는 모든 파일을 include 시켜주도록 한다. (아무것도 건드리지 않았다면 위처럼 되어있을 것이다.)
- 로드밸런싱 설정 파일 추가
설정 파일 위치 : (Ubuntu 기준) /etc/nginx/sites-enabled/
설정 파일 이름 : (어느 것으로 해도 상관없다) loadbalance
전체 파일 경로 : /etc/nginx/sites-enabled/loadbalance
$ cd /etc/nginx/sites-enabled/
$ nano loadbalance파일을 추가하고 아래처럼 입력해준다.
upstream backend { // Upstream(로드밸런싱을 해주는 녀석)의 이름을 "backend"로 한다.
least_conn; // 로드밸런싱 알고리즘을 "최소 연결" 알고리즘으로 한다.
server 127.0.0.1:9801; // 클라이언트가 Nginx로 요청 시 우회시켜줄 Server 정보
server 127.0.0.1:9802; // 클라이언트가 Nginx로 요청 시 우회시켜줄 Server 정보
server 127.0.0.1:9803; // 클라이언트가 Nginx로 요청 시 우회시켜줄 Server 정보
}
server {
listen 9800; // 클라이언트가 9800 포트로 요청할 경우 해당 구문 실행
location / { // 모든 요청 "/"
proxy_set_header Host $host; // Host 헤더에 Nginx 변수값 $host 설정 -> 클라이언트의 호스트가 설정됨
proxy_set_header Connection ""; // Upstream 서버를 사용하겠다는 설정
proxy_pass http://backend; // Upstream으로 설정한 "backend"로 요청을 보낸다.
}
}이렇게 설정하면 기본적인 설정은 끝났다.
이제 Nginx를 restart 해준다.
$ service nginx restart위의 설정은 해당 포스팅의 최상단 # Nginx를 활용한 로드밸런싱 구현 파트에서 구현한 부분과 90% 같다. Nginx의 listen 포트를 9800으로 바꿔주었고, Upstream 안에 각 server 정보는 9801 ~ 9803으로 설정해주었다.
이제 해주어야 하는 것은, 9801 ~ 9803 포트로 각 서버를 열어주는 일. 필자는 도커를 사용하여 서버를 연다. 각자 원하는 방식대로 서버를 오픈하도록 하자.
필자의 경우 아래와 같이 서버가 열려있다. Nginx는 9800 포트에서 열려있고, Node.js는 9801 ~ 9803 포트로 3개가 열려있는 것을 확인할 수 있다.
Node.js에서 connectionLimit을 500개로 제한을 두었던 것을 기억할 것이다.
때문에 MySQL과 Node.js가 연결되는 시점에 Pool 공간 내 Connection이 500개가 생성됐던 것이고, 지금은 Node.js를 3개 띄웠으므로 총 1,500개의 Connection이 Pool 내에 생성될 것이다.
MySQL에 접속하여 show processlist;를 확인해보면 알 수 있다.
이렇게 하여, 로드밸런싱까지 사용하여 총 1,500개의 요청을 정상적으로 처리할 수 있는 환경을 구축해보았다. 환경에 따라 미미한 차이는 있을 수 있음을 유의하자.
오류가 많이 발생한다면, Nginx의 work_processes 개수를 살펴보자.
work_processes * work_connections의 곱이 총 처리할 수 있는 동시 요청이라 보면 된다.
이 값이 1,500 미만이라면 1,500개의 동시 요청을 처리할 수 없는 것이다.
그 외에도, Nginx를 로드밸런싱으로서 사용하면서 Node.js를 여러개 띄웠을 때는 상황에 맞춰 설정 값들을 변경시켜주어야 한다. 이는 상당히 많은 시도가 필요하고, 고민과 경험이 필요하다.
필자도 계속해서 다양한 환경을 경험해보고, 테스트하는 과정에 있다. 새로운 정보가 생길 때 해당 포스팅을 업데이트 하도록 하겠다.

