connection pool 개념 (with nodejs, mysql)
난 이미 백단에 풀을 생성해서 관리를 하고있었는데
내가 알고있는 connection pool개념과 내 db서버 작동방식이 좀 다르더라..
프론트에서 db데이터 접근하는 요청을 날릴때마다 커넥션이 여러개 생성이 되던데
백단에서 connection pool로 db접근처리를 해놨다면 쿼리문 날릴때마다
노는 커넥션 하나 가져와서 쓰는거라 커넥션이 늘어나지 말아야할텐데?
로컬에서 작업하는거니 당연히 그래야하는데.. 계속 커넥션이 늘더라..
뭐가 문제인지 원인을 찾았다. 그래서 connection pool의 개념을 다시 정리해보겠다.
# 목적 : connection pool의 개념정리
# 기본 프론트 백 db서버 동작 구조
위 그림처럼 front에서 api요청으로(axios ajax같은거) 백단에 요청을 한다.
그리고 백단에선 요청을 받았으니 이게 db에 접근하는 요청이면 db서버에 요청을 한다.
보통 쿼리문을 날린다. select * from~머시기 이런걸로..
그럼 db에서는 백단이 요청한 데이터를 응답하고 이걸 백단이 가공을 하든 뭐하든해서
front에 json형식으로 응답을 해주는 구조다.
# connection?
저 위 그림에서 백단에서 db서버에 요청을 보낼때 connection 한개 생성이라고 보이듯이,
백단에서 db접근을 할때마다 커넥션을 하나씩 생성한다.
보통 생쿼리로 날리면 쿼리하나당 커넥션이 한개씩 생기는데
이 커넥션은 갯수제한이 있다.
SHOW VARIABLES LIKE '%max_connection%';
db에 이 쿼리 날려봐라. 그럼 이렇게 max connections의 갯수가 나온다.
로컬서버라면 이 최대치를 정할 수 있어서 뭐 커넥션 많이생성되도 상관없다.
그러나 이게 배포환경, 클라우드서비스를 이용하면 말이 달라진다.
클라우드서비스는 db인스턴스에따라 max connection이 제한되는데,
이걸 넘기면 db서버에서 커넥션이 끝날때까지 아무리 요청을 보내도 응답을 안준다ㅡㅡ
too many connection 에러가 뜬다.
그럼 매번 커넥션 연결하고 데이터 응답받으면 커넥션을 끊으면 되지 않나?
이것도 당연 맞는방법이고 옛날엔 많이 사용되었단다..
근데 이 커넥션을 하나생성하는 작업이 생각보다 무거운 작업이다.
그래서 사용자가 api요청을 보내고 응답받는시간에 커넥션이 연결되는 시간도 포함되니
시간이 더 오래걸리고 서버도 부담되는 작업일것이다.(사용자가 조온나 많을경우)
# pool개념
예를들어 프론트에서 db데이터를 가져오는 요청 두개가 있다고 가정해보자.
이렇게 두번의 요청으로 인해 커넥션이 두개 생성되었을 것이다.
만약 요청이 100개들어오면 커넥션은 100개쌓이는거고 내 db인스턴스는
too many connection에러를 뿜어대며 사표를 제출할것이다..
여기서 pool이란 개념이 나온다.
위 그림처럼 api요청 1이 들어왔을때 connection pool에선 커넥션 하나를 생성한다.
그리고 db에 데이터 요청하고 데이터응답 1을 해준다.
근데 여기서 중요한건 connection을 끊는게 아니라 '반납'을 한다.
그리고 api요청 2가 들어왔을때, 새로 커넥션을 생성하는게 아니라,
아까 사용했던 connection1을 재사용한다.
만약 사용자가 많거나 이런 등등 이유로 connection1이 사용중이라면?
그때 connection2를 생성해서 데이터작업후 다시 반납한다.
# db에서 커넥션 확인하는 방법
SHOW FULL PROCESSLIST;
이 쿼리문은 지금 프로세스리스트를 가져오는건데 커넥션리스트도 가져온다.
db컬럼에서 sleep상태인게 connection pool에 담긴 커넥션인데 지금 놀고있다는 뜻임.
show global status like 'threads_connected';
커넥션이 4개 지금 연결되어있다는 뜻이다.
# node js에서 pool사용하기 (mysql)
const mysql = require("mysql");
먼저 db작업을 위해 모듈을 불러옵시다.
const pool = mysql.createPool({
host:
process.env.NODE_ENV === "production"
? process.env.host_production
: process.env.host,
user:
process.env.NODE_ENV === "production"
? process.env.user_production
: process.env.user,
password:
process.env.NODE_ENV === "production"
? process.env.password_production
: process.env.password,
database:
process.env.NODE_ENV === "production"
? process.env.database_production
: process.env.database,
connectionLimit: 30,
});
그리고 pool을 만들어주자.
pool에 담긴 db접근 정보로 커넥션을 생성하니 db접근정보를 저렇게 넣어주어야
커넥션 생성할때마다 저 정보로 db에 접근하는것이다.
app.get("/api/userLists", (req, res) => {
const user_info = {}
pool.getConnection((err, connection) => {
if (err) {
res.json('에러야!!');
} else {
connection.query(
"select name from users_table where id = req.body.user_id",
(error, results, fields) => {
if (error) {
console.error('얘도 에러야1!');
connection.release();
} else {
user_info.id = results;
connection.query(
"select address from users_table where id = req.body.user_id",
(error, results, fields) => {
if (error) {
console.error('얘도 에러야2!');
connection.release();
} else {
user_info.address = results;
res.json(user_info);
connection.release();
}
}
);
}
}
);
}
});
});
api요청을 할때 코드다
먼저 pool.getConnection메서드로 커넥션을 가져온다.
그다음 connection.query메서드로 쿼리를 날린다.
# 주의할점!
connection.release()
이 메서드는 커넥션을 반납하는 용도다.
위 코드에서 많이 쓰였는데, 만약 커넥션을 반납하지 않으면 그 커넥션은 계속 사용중인 상태인것이다.
그렇다면 api요청할때마다 계속 커넥션이 쌓일것이다.
그럼 또 max connection넘어서 db가 사표처리해달라고 에러뿜을것임.
# 내가 실수 헀던거.
pool.getConnection((err, connection) => {
머라머라머라~~;
pool.getConnection((err, connection) => {
};
머라머라머라~~;
}
이런식으로 커넥션가져오는걸 같은 요청내에서 중첩되게 쓰면
커넥션이 요청받을때마다 계속 생성된다.
그래서 프론트에서 db접근하는 api요청받을떄마다 커넥션이 생성되길래
응? 뭐지? 쓰던 커넥션 안쓰고 요청때마다 커넥션 받아오네? 이러고
wait timeout을 짧게 설정하려고 헀다.
이상적으로 한 api요청당 getConnection 메서드는 한번쓰고 안에서 쿼리 많이 날려서
데이터작업하는게 좋다.
# 팁 : connection limit
const pool = mysql.createPool({
host:
process.env.NODE_ENV === "production"
? process.env.host_production
: process.env.host,
user:
process.env.NODE_ENV === "production"
? process.env.user_production
: process.env.user,
password:
process.env.NODE_ENV === "production"
? process.env.password_production
: process.env.password,
database:
process.env.NODE_ENV === "production"
? process.env.database_production
: process.env.database,
connectionLimit: 30,
});
아까 풀만드는코드에서 프로퍼티에 connectionLimit이 설정되어있는데
이건 최대 커넥션생성수를 의미한다.
만약에 이 숫자가 넘으면 db는 요청을 대기하고 다른 커넥션이 반납될때까지 기다리게된다.
보통 이 숫자는백단에서 생성되는 커넥션 최대치인데 db서버의 max connection보다 -1~2정도로 설정해주는게
이상적이다.
로컬같은데서 db접근할수도 있으니까..
#팁 2 : connection.release()는 getConnection에러떄는 안써도 된다
쿼리문 날렸을때 db에서 에러를 뱉으면 이미 커넥션을 하나 가져온상태에서 에러가 난거니
connection.release()로 커넥션을 반납해줘야한다.
하지만
app.get("/api/userLists", (req, res) => {
pool.getConnection((err, connection) => {
if (err) {
connection.release() //여기서 커넥션반납은 하지말자!
}
})
})
pool.getConnection함수내에선 안써도된다.
커넥션을 가져오는데 에러가 터진것이므로 반납할 커넥션이 없기떄문이다.
만약 쓰게된다면 백단에서 에러터지고 pm2가 서버껐다킬거임..