JAVA와 다르게 NodeJS의 경우 싱글스레드이기 때문에 CPU의 모든 성능을 끌어다 쓴다고 할 수 없습니다.
이것을 해결하기 위해 Node의 cluster
를 사용 할 수 있지만 pm2
를 사용하면 더욱 간편합니다.
기존의 Node 서버에서 terminus
을 사용한 health 체크와 graceful shut down
기능을 추가하였습니다. 이정도로 충분할 수 있지만 종료가 진행되는 사이
완료가 되지않은 요청
을 제대로 처리 해주지 않았다는 것을 알게되었습니다.
//코드10. ready 이벤트 설정 변경
//ecosystem.config.js
module.exports = {
apps: [{
...
wait_ready: true, // ready 이벤트를 기다리라는 의미
listen_timeout: 50000 // ready 이벤트를 기다릴 시간
}]
}
//코드11. 클라이언트 요청 처리 설정
//ecosystem.config.js
module.exports = {
apps: [{
...
wait_ready: true,
listen_timeout: 50000,
kill_timeout: 5000 // 프로세스에 SIGINT 시그널이 전달되면,
//새로운 요청을 더 이상 받지 않고 연결되어 있는 요청이 완료된 후
// 해당 프로세스를 강제로 종료하도록 처리
}]
}
app.close를 이용해 새로운 요청을 거절하고 이미 연결되어 있는 건 유지할 때,
HTTP 1.1 Keep-Alive
를 사용하고 있었다면, 요청이 처리된 후에도 기존 연결이 계속 유지되기 때문에 앞의 방법만으로는 해결되지 않습니다.
따라서 ‘Connection: close’를 설정해 클라이언트 요청을 종료하는 방법을 활용, 타임아웃으로 서비스가 중단되는 문제를 해결할 수 있습니다.
//app.js
const express = require('express')
const app = express()
const port = 3000
//추가할 부분 1
let isDisableKeepAlive = false
app.use(function(req, res, next) {
if (isDisableKeepAlive) {
res.set(‘Connection’, ‘close’)
}
next()
})
app.get('/', function(req, res) {
res.send('Hello World!')
})
app.listen(port, function() {
process.send(‘ready’)
console.log(`application is listening on port ${port}...`)
})
process.on(‘SIGINT’, function () {
//추가할 부분
isDisableKeepAlive = true
app.close(function () {
console.log(‘server closed’)
process.exit(0)
})
})
대략적으로 위의 코드로 사용할 수 있지만
저는 terminus
를 사용하고 있기 때문에 조금 수정하여 사용하겠습니다.
// setOffKeepAlive.ts
// const emitter = Container.get(Emitter).getInstance();
export function setOffKeepAlive(app: Application) {
let isDisableKeepAlive = false;
emitter.on("offKeepAlive", () => {
isDisableKeepAlive = true;
logger.info("Set off 'keep-alive' header");
});
app.use((_, res, next) => {
if (isDisableKeepAlive) {
res.set("Connection", "close");
}
next();
});
}
//app.ts
...
setOffKeepAlive(app);
...
//terminus.ts
function onSignal() {
emitter.emit("offKeepAlive");
return Promise.all([mysql.closePool(), redis.closeRedis()]);
}
...
export const terminusOption: TerminusOptions = {
logger: logger.info,
signal: "SIGINT",
healthChecks: {
"/health": onHealthCheck,
__unsafeExposeStackTraces: true,
},
onShutdown,
onSignal,
timeout: 5000,
};
terminus에서 SIGINT
시그널을 받으면 onSignal
를 실행시키고
eventEmitter를 emit
하여 connection을 close로 수정합니다.
process.send('ready') 오류 : https://github.com/microsoft/TypeScript/issues/21183
위의 글을 참고하여 제 코드에 맞게 변형하여 작성하였습니다.