현재 운영 중인 프로젝트는 Kafka Consumer
로 구성되어 있어, 다른 회사(Producer)로 부터 주기적으로 메세지를 전달받고 있다.
꽤나 방대한 양의 메세지를 받는 중이다🧚♀️✨
현재 서버 마이그레이션 작업이 진행 중이라 모니터링하는 과정에서 서버의 메모리 이슈가 발생했다.
꽤나 중요한 문제였기때문에 그동안 삽질하면서 해결했던 과정에 대해서 정리하려고 한다.
.pm2 로그 관리를 통한 서버 메모리 이슈 해결 내용이 필요한 분들은 가장 하단으로 이동해주길 바란다.
node.js 애플리케이션의 상용 프로세스를 관리하는 도구로 현 프로젝트는 pm2를 사용하고 있다.
pm2를 이용해서 애플리케이션이 종료되지 않도록 유지시켜주며, 다운타임 없이 재시작할 수 있게 된다.
git pull
or build
이슈테스트 중 소스 코드 변경이 필요해서 WAS 서버에서 git pull
을 시도했으나, 서버 용량 부족으로 인해 git pull
이 되지 않았다.
git pull
로 당겨서 업데이트 된 소스코드를 받았지만, build
가 안되는 문제 또한 발생했다.
$ npm run build
npm ERR! code ENOSPC
npm ERR! syscall write
npm ERR! errno -28
npm ERR! nospc ENOSPC: no space left on device, write
npm ERR! nospc There appears to be insufficient space on your system to finish.
npm ERR! nospc Clear up some disk space and try again.
npm ERR! A complete log of this run can be found in:
npm ERR! .../.log
급하게 소스 코드를 업데이트해서 빌드해야했으므로 임시 방편으로 임시 파일들을 지우는 방법으로 조치하고 작업을 수행했다.
급하게 처리한 것이라 정리가 안되었다.
순서가 바뀔 수 있을텐데 그래도 필요한 분이 있다면 참고가 되었으면 하는 마음에 사용했던 방법들을 아래에 정리해놨다.
rm -rf .git/objects/pack/*.pack
rm -rf .git/objects/pack/*.idx
rm -f .git/objects/pack/._pack-*
rm -rf .git
git init
git remote add origin https://github.com/was.git
git pull
git remote show origin
git branch
git branch --set-upstream-to=origin/main master
git pull
git checkout -b main origin/main
// 문제 발생
$ git branch --set-upstream-to=origin/main master
fatal: branch 'master' does not exist
$ git pull origin main
From https://github.com/was
* branch main -> FETCH_HEAD
error: Untracked working tree file '.dockerignore' would be overwritten by merge.
git clean -f .dockerignore
git pull origin main
git status
git diff origin/main
git branch -avv
git remote -v
git branch -m master main
git branch --set-upstream-to=origin/main main
git ls-remote origin
git remote show origin
git fetch origin --prune
git reset HEAD .
git checkout .
git fetch --all
git reset --hard origin/main
이후에는 git pull과 build를 잘해서 새로운 소스코드를 서버에 잘 반영했다.
Kafka
메세지 수신 이슈Kafka Broker IP를 변경 후 TO-BE Kafka 트랜잭선이 보이지 않는 문제가 발생했다.
WAS log를 모니터링하면 JS stacktrace
에러가 발생한 뒤에 더 이상 Kafka 메세지를 받아오지 못했다.
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
해당 문제는 JavaScript heap out of memory
로 heap 메모리가 부족해서 발생한 것이다.
Cron
작업 주기 조정Cron
을 통해서 작업해야했는데 테스트 때문에 Cron 주기를 너무 빈번하게 했다.
cron 작업은 새로운 프로세스를 생성하고 메모리를 할당받기 때문에 너무 빈번한 실행은 결국 메모리 부족을 발생시킬 수 있다.
이에 따라 cron 주기를 한달에 한 번으로 늘렸다.
해결되는 듯했으나, 일시적이였고 곧 메모리 할당 문제로 동일한 현상이 재발했다.
Kafka 메세지가 들어올 때마다 로그가 너무 빈번하게 기록되는 문제를 발견했다.
로그가 과도하게 쌓일 경우, 결국 디스크 공간이 부족할 수 있기 때문에, Kafka 메세지에 대해 한 번만 로그를 기록하도록 수정했다.
물론 어느정도 해결은 되었으나, 동일한 메모리 문제가 발생했다.
다시 에러를 확인했는데, Heap 메모리가 너무 부족한 것이 계속 눈에 밟혔다.
현재 전체 용량이 어느정도인지 확인했다.
$ node -e 'console.log(v8.getHeapStatistics().heap_size_limit/(1024*1024))'
// 2096
2096이니까 약 2배전도 용량을 늘려 메모리 할당을 증가시켰다.
용량을 늘리는 것은 간단하다. ecosystem.config에서 하당의 코드를 그대로 추가했다.
module.exports = {
apps: [
{
name: 'nest',
script: './dist/main.js',
node_args: "--max-old-space-size=4096",,
...
}
]
}
한 동안 에러 없이 Kafka 메세지가 제대로 들어오는 것을 확인했다.
계속 되는 용량 문제때문에 분석하던 중, Kafka 메세지 수신 시 비정상적으로 많은 DB쿼리가 실행되는 것을 발견했다.
문제가 된 쿼리들은 아래와 같았다.
1. 레거시 코드에 남아있는 미사용 쿼리들
2. 불 필요한 중복 조회 쿼리들
3. 최적화 되지 않은 쿼리문
위의 쿼리들을 제거하자 Kafka 메세지가 정상적으로 수신되었다.
하지만, 이것 역시 임시적인 해결책에 불과했다.
이틀 후 또 다시 메세지가 중단되어 디스크 사용량을 확인해봤다.
$ df -h
확인 결과 .pm2
디렉토리의 로그 파일이 과도하게 누적되어 있었다.
장기간 운영하던 프로젝트이기 때문에, 급격히 늘어난 Kafka 메세지 양으로 인해 로그가 지속적으로 축척되었다.
따라서 로그 파일을 백업한 후 삭제하자, 지금까지도 Kafka 메세지가 안정적으로 수신되는 것을 확인했다.
또 문제가 발생하면 '6차 시도'에 관해 글을 업데이트하겠지만, 현재까지는 로그 파일이 위의 모든 에러들의 근본적인 원인임을 파악하고,
로그 파일을 관리하는 것을 아래에 작성할 것이다.
기본적으로 pm2는 로그를 /home/user/.pm2
에 log 폴더가 존재한다.
// .pm2/logs
$ ls
nest-error.log nest-out.log
특별한 설정을 해두지 않았기 때문에, nest-error.log
와 nest-out.log
에 로그가 쌓여있는 것을 확인할 수 있다.
이미 로그를 한 번 삭제했기 때문에, 현재 많은 데이터가 쌓여있지 않지만, 분명 며칠이 지나면 또 가득 찰 것으로 예상된다.
[user@ip logs]$ ls
nest-error.log nest-out.log
[user@ip logs]$ ls -alh
total 5.4G
drwxr-xr-x. 2 user user 4.0K Dec 18 13:37 .
drwxr-xr-x. 5 user user 4.0K Dec 19 11:16 ..
-rw-r--r--. 1 user user 4.7G Dec 20 06:55 nest-error.log
-rw-r--r--. 1 user user 643M Dec 20 06:55 nest-out.log
앞서 지금까지 상황을 미루어보아 위의 로그 파일들은 현 프로젝트가 계속 운영될수록 점점 더 쌓여서 또 서버 용량을 가득 차게 만들 것이다.
그럼 분명 디스크 용량이 부족하게되면서 git
, build
, kafka 메세지 수신
등 많은 문제를 일으킬 수 있다.
이를 위해 pm2 로그 파일을 관리하는 pm2 logrotate
모듈을 사용해보기로 했다.
pm2를 실행할때마다 로그 경로에 저장되는 로그들은 직접 삭제해주지 않는다면, 계속 쌓이게 된다. 이 모듈을 설치하게 되면 로그 자동 삭제, 파일 리사이즈 등등 다양한 로그 관리 기능을 이용할 수 있게 된다.
pm2 logrotate 파일은 아래 명령어를 통해 설치할 수 있다.
pm2 install pm2-logrotate
pm2 conf
pm2 conf
를 통해 logrotate에 기본적으로 설정된 옵션을 확인할 수 있다.
기본 설정을 보면, 로그가 10MB 넘길때마다 새로운 로그 파일이 생성되며, 30개 이상 생성되지 않도록 한다. => 300MB 까지만 로그가 기록된다.
현재 꽤나 용량이 크기 때문에 설정을 아래와 같이 바꿨다.
pm2 set pm2-logrotate:rotateInterval "0 0 * * *"
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD
pm2 set pm2-logrotate:workerInterval 30
pm2 set pm2-logrotate:max_size 2000M
잘 설정되었는지 확인하려면 pm2 conf
로 확인할 수 있다.
이렇게 되면 자동으로 ./pm2/logs
폴더 안에 nest-out.log
, nest-error.log
파일들 뒤에 날짜가 붙은채로 압축된 파일이 매일 하나씩 생성될 것이다.
(현재 테스트 중)
이렇게되면 가독성이 없다고 판단해서 폴더를 따로 만들어서 거기다가 압축파일을 옮기고 싶었는데, 하루종일 삽질한 결과 pm2-logrotate
에는 그런 기능은 없는듯하다..
궁금한 사람들을 위한 내 삽질 기록..
나는 댕멍청하게 config파일에 error_file
과 out_file
을 설정하면 될것이라고 판단했다.
module.exports = {
apps: [
{
name: 'nest',
script: './dist/main.js',
cwd: '/home/user/was',
watch: true,
log_date_format: 'YYYY-MM-DD',
error_file: '/home/user/was/.pm2/logs/logError/nest-error.log',
out_file: '/home/user/was/.pm2/logs/logOut/nest-out.log',
env: {
...
이렇게 설정해두고서 로그를 찍어봤는데, pm2 logs nest 0
을 하면 실시간으로 로그를 확인할 수 있는데,
.pm2/logs/
폴더에서는 로그가 저장되지 않았다.
이미 .pm2/logs
폴더 안에 nest-out.log
와 nest-error.log
가 존재하기 때문에 해당 파일을 찾지 못해서 발생하는 문제인 듯 하다.
그래서 해당 설정을 지우니까 다시 log가 잘 저장되었다.
(왜 글이 사라졌냐... 왜....)
지금 정리하면서 든 생각은 이미 있는 nest-out.log와 nest-error.log의 파일 위치를 각각 logOut과 logError 폴더로 이동시키면 되지 않을까..? 라는 생각 ??
출근해서 테스트 해보고 다시 수정해보겠다. =>cron과 script파일로 수정할 예정.
테스트해본 결과 pm2-logrotate-out___2024.12.22.log.gz
이런 식으로 pm2-logrotate 로그파일이 계속 생성되었다.
이렇게 될 경우, 또 로그파일이 쌓이면서 디스크 용량이 더 커질 것으로 생각되었다.
따라서 해당 로그파일은 이틀에 한 번씩 삭제하는 걸로 수정하기로 했다.
또한, nest-error
와 nest-out
파일이 압축되면서 디렉토리 구조가 너무 지저분하게 관리되는 문제가 있었다.
한 두개의 압축파일은 괜찮겠지만, 한 달동안 둬야할 경우에는 분명 파일 찾아보는 것이 힘들 것이라 판단했다.
이 경우에도 script로 관리하는 것으로 해결했다.
pm2-logrotate-out___log.gz
파일 삭제우선, script 파일들을 저장할 script
폴더를 만들었다.
cd .pm2/logs
mkdir -p scripts
이후 pm2-logrotate와 관련된 로그를 지우는 스크립트를 작성했다.
error
는 당분간 모니터링을 위해서 지우지 않는 것으로 했다.
vi clean_logrotate_log.sh
#!/bin/bash
LOG_PATH="/home/user/.pm2/logs"
find "$LOG_PATH" -type f -name "pm2-logrotate-out__*.log.gz" -mtime +2 -exec rm -f {} \;
esc
-> :wq!
로 저장.
만약 스크립트가 잘 작동되는지 확인하려면, ./clean_logrotate_log.sh
를 입력해서 해당 로그파일들이 삭제되는지 확인하면 된다.
삭제가 잘 된다면, 스크립트가 실행될 수 있도록 권한을 부여한다.
chmod +x /home/user/.pm2/logs/scripts/clean_logrotate_logs.sh
nest-out
& nest-error
파일 이동
.pm2/logs/script
폴더 안에 스크립트 파일 생성
우선, logs 폴더 안에 errorLog와 outLog 폴더를 생성한다.
mkdir -p errorLog
mkdir -p outLog
./pm2/logs/script
폴더로 이동해서 move_nest_logs.sh
스크립트를 생성한다.
vi move_nest_logs.sh
#!/bin/bash
SOURCE_DIR="/home/user/.pm2/logs"
ERR_DEST="$SOURCE_DIR/errorLog"
OUT_DEST="$SOURCE_DIR/outLog"
find "$SOURCE_DIR" -type f -name "nest-error__*.log.gz" -exec mv {} "$ERR_DEST" \;
find "$SOURCE_DIR" -type f -name "nest-out__*.log.gz" -exec mv {} "$OUT_DEST" \;
매일 자정 위에서 만든 스크립트가 실행되도록 cron을 설정한다.
대신 로그가 찍히는 것을 막기 위해서 /dev/null 2>&1
를 설정해줬다.
crontab -e
0 0 * * * /home/user/.pm2/logs/scripts/clean_logoutrotate_logs.sh > /dev/null 2>&1 && /home/user/.pm2/logs/scripts/move_nest_logs.sh > /dev/null 2>&1
npm 네트워크 문제로 설치가 안될 경우
[user@user ~]$ pm2 install pm2-logrotate
[PM2][Module] Installing NPM pm2-logrotate module
[PM2][Module] Calling [NPM] to install pm2-logrotate ...
npm ERR! code ECONNRESET
npm ERR! syscall read
npm ERR! errno ECONNRESET
npm ERR! network request to https://registry.npmjs.org/pm2-logrotate failed, reason: read ECONNRESET
npm ERR! network This is a problem related to network connectivity.
npm ERR! network In most cases you are behind a proxy or have bad network settings.
npm ERR! network
npm ERR! network If you are behind a proxy, please make sure that the
npm ERR! network 'proxy' config is set properly. See: 'npm help config'
npm ERR! A complete log of this run can be found in:
npm ERR! /home/user/.npm/_logs/2024-12-23T07_14_47_792Z-debug-0.log
[PM2][ERROR] Installation failed via NPM, module has been restored to prev version
자꾸 pm2-logrotate를 하는 과정에서 에러가 발생했다.
그래서 직접 수동으로 pm2-logrotate
소스코드를 직접 다운받기로 했다.
git clone https://github.com/pm2-hive/pm2-logrotate.git
이 경우, npm 으로 설치할때 글로벌로 설치해야한다.
# sudo로 글로벌 설치
sudo npm install -g
# 또는 현재 위치에서 pm2에 직접 설치
cd pm2-logrotate
pm2 install .
나의 경우, pm2-logrotate
폴더에서 직접 pm2에 설치했다.
설치 후 pm2 conf
를 했을 경우, default로 설정되어있는 것들이 설치가 안되어있어서 다시 설치를 해줬다.
pm2 set pm2-logrotate:rotateInterval "0 0 * * *"
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD
pm2 set pm2-logrotate:workerInterval 30
pm2 set pm2-logrotate:max_size 2000M
pm2 set pm2-logrotate:rotateModule true
안됨. 망할 방화벽..,
안되어서... 쉘 스크립트 또 만들었음..
#!/bin/bash
LOG_DIR="/home/user/.pm2/logs"
RETAIN_DAYS=30
DATE_FMT=$(date '+%Y-%m-%d')
TARGET_FILES=("name-out.log" "name-error.log")
for logfile in "${TARGET_FILES[@]}"
do
FULLPATH="$LOG_DIR/$logfile"
if [ -f "$FULLPATH" ]; then
base_name="${logfile%.log}"
new_name="${base_name}__${DATE_FMT}.log"
mv "$FULLPATH" "$LOG_DIR/$new_name"
gzip -f "$LOG_DIR/$new_name"
fi
done
pm2 reloadLogs
find "$LOG_DIR" -type f -name "*.gz" -mtime +$RETAIN_DAYS -exec rm -f {} \;
이렇게 만들고 이 파일도 crontab에 추가해주면 된다.
pm2-logrotate를 사용하니까 또 pm2.log가 쌓여가서..
결국은 모든 서버에 scrtips, cron으로 셋팅하고 log-rotate를 제거했다 ^^..,