비트코인 모의 투자를 구현하는 와중 실시간으로 데이터를 업비트 api 를 통해 가져와 Oracle Cloud DB 에 저장하고 해당 데이터를 꺼내 실시간으로 가격을 비교해서 출력해야 하는 과정이 필요했다.
문제 발생 : DB 안에 1초마다 쌓이는 각 코인들의 값을 select 쿼리로 1초마다 가져올때 ( 자바스크립트의 setInterval 메소드를 사용하여 Oracle Cloud DB 로 1초마다 데이터를 요청 ) Error: NJS-500: connection to the Oracle Database was broken 문구가 발생되면서 값을 제대로 가져오지 못하는 현상이 발생, 로그인 시도 시에도 동일한 현상이 발생
위 구문을 실행하면 서버에서 DB 에 접근하여 데이터를 받아오게끔 설계해 놨는데 아래가 해당 dao 의 코드이다
const oracledb = require("oracledb");
const dbConfig = require("../../../config/database/db_config");
oracledb.outFormat = oracledb.OBJECT;
oracledb.autoCommit = true;
const get = {
have_KRW : async (member_id) => {
const con = await oracledb.getConnection(dbConfig);
let result = await con.execute(`select money from member where member_id='${member_id}'`);
return result;
},
// 매수했던 각 코인의 갯수를 Object 형태로 담는 함수
had_coin_num : async (member_id) => {
let had_coin_num = {
btc : 0,
eth : 0,
shib : 0,
bch : 0,
etc : 0,
btg : 0,
sol : 0,
doge : 0,
xrp : 0,
id : 0,
pundix : 0,
stx : 0,
aave : 0,
dot : 0,
avax : 0,
gas : 0,
sbd : 0,
ong : 0,
sei : 0,
ont : 0
};
let coin_list = ["btc","eth","shib","bch","etc","btg","sol","doge","xrp","id","pundix","stx","aave","dot","avax","gas","sbd","ong","sei","ont"];
const con = await oracledb.getConnection(dbConfig);
for(let i=0; i<coin_list.length; i++){
// 구매 코인의 총 갯수를 가진 DB 결과 Object
let coin_num_object = await con.execute(`select coin_num from trade where coin_name='${coin_list[i]}' and member_id = '${member_id}' and status = 1`);
// 코인의 구매 이력이 없는 경우
if(coin_num_object.rows[0] === undefined){
had_coin_num[coin_list[i]] = 0;
continue;
}
let each_coin_num = 0;
coin_num_object.rows.forEach(coin => {
if(coin.COIN_NUM == null){
coin.COIN_NUM = 0;
}
each_coin_num += coin.COIN_NUM;
})
had_coin_num[coin_list[i]] = each_coin_num
}
return had_coin_num;
},
// 매수했던 각 코인의 가격의 합을 Object 형태로 담는 함수
had_coin_cost : async (member_id) => {
let had_coin_cost = {
btc : 0,
eth : 0,
shib : 0,
bch : 0,
etc : 0,
btg : 0,
sol : 0,
doge : 0,
xrp : 0,
id : 0,
pundix : 0,
stx : 0,
aave : 0,
dot : 0,
avax : 0,
gas : 0,
sbd : 0,
ong : 0,
sei : 0,
ont : 0
};
let coin_list = ["btc","eth","shib","bch","etc","btg","sol","doge","xrp","id","pundix","stx","aave","dot","avax","gas","sbd","ong","sei","ont"];
const con = await oracledb.getConnection(dbConfig);
for(let i=0; i<coin_list.length; i++){
// 구매 코인의 총 갯수를 가진 DB 결과 Object
let coin_cost_object = await con.execute(`select buy_cost from trade where coin_name='${coin_list[i]}' and member_id = '${member_id}' and status = 1`);
// 코인의 구매 이력이 없는 경우
if(coin_cost_object.rows[0] === undefined){
had_coin_cost[coin_list[i]] = 0;
continue;
}
let each_coin_cost = 0;
coin_cost_object.rows.forEach(coin => {
if(coin.BUY_COST == null){
coin.BUY_COST = 0;
}
each_coin_cost += coin.BUY_COST;
})
had_coin_cost[coin_list[i]] = each_coin_cost
}
return had_coin_cost;
},
// 현재 보유 중인 코인의 갯수를 구하는 함수
have_coin_num : async (member_id) => {
const con = await oracledb.getConnection(dbConfig);
let have_coin_num = await con.execute(`select btc,eth,shib,bch,etc,btg,sol,doge,xrp,id,pundix,stx,aave,dot,avax,gas,sbd,ong,sei,ont from member_account where member_id = '${member_id}'`);
return have_coin_num.rows[0];
},
// 현재 코인의 가격 20개를 구하는 함수
now_coin_cost : async () => {
const con = await oracledb.getConnection(dbConfig);
let now_coin_cost_object = await con.execute(`select * from (select * from coin_info order by order_column desc) where rownum <= 20`);
return now_coin_cost_object.rows;
}
}
module.exports = {get}
위의 코드 중 평가 금액은 현재 보유 중인 코인의 갯수를 구하는 함수와 현재 코인의 가격 20 개를 구하는 함수를 사용하는데, 이 부분을 반복적으로 돌리면 계속 DB의 연결이 끊기는 오류가 발생....
웹에서 NJS-500 오류 코드와 관련해서 내용을 찾아보다가 문득 DB 에 데이터를 넣는 부분과 DB 의 데이터를 꺼내오는 부분이 동시에 작동하여 문제가 생기지 않을까 하는 생각이 들었다.
( Oracle Cloud DB 와 연결이 끊어질때 1회성 쿼리가 발생됨으로 세션 타임아웃 문제는 아닐 것으로 예상, 고로 최대 연결 갯수의 초과이거나 커넥션의 과부하로 인한 연결 끊김으로 예상 )
▼
이 부분을 해결할 수 있는 방법을 찾다 보니 커넥션 풀(DBCP) 에 대한 부분이 나왔고 이를 사용해보기로 했다.
- 커넥션 풀(DataBase Connection Pool)이란? -
웹 컨테이너(WAS)가 실행되면서 미리 DB 와 연결(connection)된 객체를 pool 에 저장해 두었다가 클라이언트에게 요청이 오면 connection 을 빌려주고 처리가 끝나면 다시 connection 을 반납받아 pool 에 저장하는 방식을 말한다.
- 커넥션 풀의 특징 -
1. 웹 컨테이너(WAS)가 실행되면서 connection 객체를 미리 pool 에 생성해 둔다.
2. HTTP 요청이 들어오면 pool 에 저장된 connection 객체를 가져다 쓰고 반환한다.
3. 위 방식으로 물리적인 데이터베이스 connection 부하를 줄이고 연결을 관리한다
( 다중 접속의 경우 커넥션 풀을 사용하게 되면 이미 connection 을 다른 곳에서 사용 중인 경우 사용자는 pool 이 connection 을 반환받아 제공해줄때 까지 대기 상태로 기다린다. )
4. pool 에 미리 connection 이 생성되어 있기 때문에 DB 에 접근할 때마다 connection 을 생성하지 않아 연결 시간이 소비되지 않는다.
▼
커넥션 풀(DBCP)를 사용한 코드
const oracledb = require("oracledb");
const dbConfig = require("../../../config/database/db_config");
oracledb.outFormat = oracledb.OBJECT;
oracledb.autoCommit = true;
let pool;
async function initialize() {
try {
pool = await oracledb.createPool(dbConfig);
console.log("Connection pool 생성됨");
} catch (error) {
console.error("Connection pool 생성 중 오류:", error);
}
}
initialize();
const get = {
have_KRW : async (member_id) => {
let connection;
try{
connection = await pool.getConnection();
let result = await connection.execute(`select money from member where member_id='${member_id}'`);
await connection.close();
return result;
}catch(err){
console.log(err);
}
},
// 매수했던 각 코인의 갯수를 Object 형태로 담는 함수
had_coin_num : async (member_id) => {
let connection;
try{
let had_coin_num = {
btc : 0,
eth : 0,
shib : 0,
bch : 0,
etc : 0,
btg : 0,
sol : 0,
doge : 0,
xrp : 0,
id : 0,
pundix : 0,
stx : 0,
aave : 0,
dot : 0,
avax : 0,
gas : 0,
sbd : 0,
ong : 0,
sei : 0,
ont : 0
};
let coin_list = ["btc","eth","shib","bch","etc","btg","sol","doge","xrp","id","pundix","stx","aave","dot","avax","gas","sbd","ong","sei","ont"];
connection = await pool.getConnection();
for(let i=0; i<coin_list.length; i++){
// 구매 코인의 총 갯수를 가진 DB 결과 Object
let coin_num_object = await connection.execute(`select coin_num from trade where coin_name='${coin_list[i]}' and member_id = '${member_id}' and status = 1`);
// 코인의 구매 이력이 없는 경우
if(coin_num_object.rows[0] === undefined){
had_coin_num[coin_list[i]] = 0;
continue;
}
let each_coin_num = 0;
coin_num_object.rows.forEach(coin => {
if(coin.COIN_NUM == null){
coin.COIN_NUM = 0;
}
each_coin_num += coin.COIN_NUM;
})
had_coin_num[coin_list[i]] = each_coin_num
}
await connection.close();
return had_coin_num;
}catch(err){
console.log(err);
}
},
// 매수했던 각 코인의 가격의 합을 Object 형태로 담는 함수
had_coin_cost : async (member_id) => {
let connection;
try{
let had_coin_cost = {
btc : 0,
eth : 0,
shib : 0,
bch : 0,
etc : 0,
btg : 0,
sol : 0,
doge : 0,
xrp : 0,
id : 0,
pundix : 0,
stx : 0,
aave : 0,
dot : 0,
avax : 0,
gas : 0,
sbd : 0,
ong : 0,
sei : 0,
ont : 0
};
let coin_list = ["btc","eth","shib","bch","etc","btg","sol","doge","xrp","id","pundix","stx","aave","dot","avax","gas","sbd","ong","sei","ont"];
connection = await pool.getConnection();
for(let i=0; i<coin_list.length; i++){
// 구매 코인의 총 갯수를 가진 DB 결과 Object
let coin_cost_object = await connection.execute(`select buy_cost from trade where coin_name='${coin_list[i]}' and member_id = '${member_id}' and status = 1`);
// 코인의 구매 이력이 없는 경우
if(coin_cost_object.rows[0] === undefined){
had_coin_cost[coin_list[i]] = 0;
continue;
}
let each_coin_cost = 0;
coin_cost_object.rows.forEach(coin => {
if(coin.BUY_COST == null){
coin.BUY_COST = 0;
}
each_coin_cost += coin.BUY_COST;
})
had_coin_cost[coin_list[i]] = each_coin_cost
}
await connection.close();
return had_coin_cost;
}catch(err){
console.log(err)
}
},
// 현재 보유 중인 코인의 갯수를 구하는 함수
have_coin_num : async (member_id) => {
let connection;
try{
connection = await pool.getConnection();
let have_coin_num = await connection.execute(`select btc,eth,shib,bch,etc,btg,sol,doge,xrp,id,pundix,stx,aave,dot,avax,gas,sbd,ong,sei,ont from member_account where member_id = '${member_id}'`);
await connection.close();
return have_coin_num.rows[0];
}catch(err){
console.log(err)
}
},
// 현재 코인의 가격 20개를 구하는 함수
now_coin_cost : async () => {
let connection;
try{
connection = await pool.getConnection();
let now_coin_cost_object = await connection.execute(`select * from (select * from coin_info order by order_column desc) where rownum <= 20`);
await connection.close();
return now_coin_cost_object.rows;
}catch(err){
console.log(err);
}
}
}
module.exports = {get}
initiallize() 함수를 사용하여 애플리케이션 시작 시 connection 정보가 담긴 pool 을 미리 변수로 저장해 놓은 뒤 사용
▼
결론 : DB 에 지속적으로 접근하여 데이터를 받아와야 하는 경우, 혹은 다량의 DB 접속이 발생할 경우에는 connection pool 을 사용하자
보완할 점 : Connection pool 을 생성하여 사용할때 유휴 Connection 을 확인하여 커넥션의 갯수를 확보하고, 최대 Connection pool 의 갯수를 지정하여 연결의 수를 제한하여 사용할 것
'프로젝트' 카테고리의 다른 글
[GitHub] Fork Repository 최신화하기 (0) | 2024.07.17 |
---|---|
[이력서] (0) | 2024.06.03 |
Oracle Cloud 속도 이슈 ( 해결 완료 ) (0) | 2024.04.16 |
fetch() 를 사용했는데 응답 값을 받지 못한 이슈 ( 해결 완료 ) (1) | 2024.04.12 |
Node.js 로 Oracle Cloud DB 접속하여 쿼리문 실행하기 (0) | 2024.04.05 |