티스토리 뷰

출처: https://www.atatus.com/blog/what-is-swap-space/

 

 

 

백엔드 CI/CD 파이프라인을 구축하고 워크플로우를 실행 하던 중에 특이한 점을 발견했다. 

 

일반적으로 모든 과정이 성공적으로 진행 된 경우, "Done in n초(s)" 와 같은 완료 메시지가 표시되어야 하는데, 이번에는 Killed pnpm install이라는 메시지가 나타났기 때문이다.

 

더 흥미로웠던 점은 메시지가 표시된 후에도 파이프라인이 계속 진행되어 배포가 성공적으로 완료되었다는 것이다.

 

패키지 설치 단계가 실패했다면 전체 워크플로우도 실패로 표시되어야 할 것 같은데 왜 이런 현상이 발생했을까? 😲

 

 

OOM(Out Of Memory)


먼저 Killed pnpm install 에러가 발생한 이유에 대해 알아보았다.

보통 Killed ~ 메시지는 Linux의 OOM(Out of Memory) Killer에 의해 발생한다.

 

그렇다면 OOM(Out of Memory)이 발생하는 이유가 무엇일까?

1. Memory Commit 와 Over Commit

Linux에서는 프로세스에게 메모리를 할당할 때, 주소값만 넘겨주고 실제 물리 메모리는 주지 않는 Memory Commit 방식을 사용한다.

 

따라서 요구된 메모리의 총합이 실제 OS에서 가지고 있는 메모리의 총량보다 많더라도 실제로 메모리가 부족해지기 전까지는 괜찮다고 판단한다. (실제 모든 메모리가 동시에 사용되지 않을 가능성이 높기 때문에 프로그램의 동작을 계속 허가하는 것이다.)

 

또 Linux는 Over Commit을 허용하는데, Over Commit이란 이렇게 커밋된 메모리가 실제 가용 메모리보다 더 큰 경우를 말한다. 따라서 Over Commit 덕분에 메모리 사용량이 큰 프로세스가 있어도 실행될 수가 있다.

2.  OOM Killer (Out Of Memory killer)

하지만 Over Commit인 상태에서 동작 중인 프로세스에게 실제 메모리 할당이 되면 가용 메모리보다 더 큰 메모리를 할당할 수도 있는데, 이 때 OOM이 발생한다. 따라서 OverCommit은 OOM의 주요 원인으로 꼽힌다.

 

이 경우 OOM Killer가 실행되어 필요없는 프로세스를 Kill 해서 그 프로세스가 사용하던 메모리를 확보한다.

종료될 프로세스는 OOM Score에 따라서 결정되며, 값이 클 수록 종료 우선순위가 높아진다. 또한 OOM Score은 0 ~ 2의 값으로 따로 설정할 수 있다.

 

 

 

OOM Killer 로그


 

OOM Killer의 로그를 확인하기 위해 명령어를 작성했더니 아래와 같이 확인할 수 있었다.

sudo dmesg | grep -i "killed process"
Out of memory: Killed process 2926 (node) total-vm:2267544kB, anon-rss:597324kB, file-rss:2432kB, shmem-rss:0kB, UID:1000 pgtables:3868kB oom_score_adj:0

 

위 로그에서 total-vm:2267544kB, anon-rss:597324kB, file-rss:2432kB, shmem-rss:0kB, UID:1000 pgtables:3868kB oom_score_adj:0 은 종료된 프로세스의 메모리 사용 상세 정보를 말한다.

  • total-vm: 전체 가상 메모리 사용량
  • anon-rss: 익명 페이지(실제 메모리) 사용량
  • file-rss: 파일 기반 페이지 사용량
  • shmem-rss: 공유 메모리 페이지 사용량
  • pgtables: 페이지 테이블 메모리 사용량

따라서 Node.js 프로세스가 약 2.2GB의 가상 메모리와 580MB의 실제 메모리를 사용하고 있었는데, 시스템의 사용 가능 메모리가 부족해서 OOM Killer에 의해 강제로 종료되었음을 나타낸다.

 

 

 

Swap Memory


앞서 OOM Killer가 작동되었을 때 OOM Score의 값을 설정해주면 OOM Killer가 발생하는 것을 막을 수 있다고 했는데 개별 프로세스마다 관리해야 하기 때문에 번거로울 것 같았다.

 

반면 스왑 메모리는 한 번 설정하면 모든 프로세스에 적용되기 때문에 스왑 메모리를 할당해보기로 했다.

 

Swap Memory란?

주 메모리(RAM)이 부족할 때 사용하는 보조 저장 공간이다.

RAM이 가득 찼을 때 시스템이 중단되는 것을 방지하기 위해 메모리에 올라가 있는 프로세스 중에서 일부를 HDD나 SSD의 스왑 영역으로 옮겨서 다른 프로세스가 더 많은 메모리를 사용할 수 있게 한다.

 

이외에도 오랫동안 사용하지 않는 프로세스의 메모리 페이지를 Swap으로 이동시켜 활성 프로세스를 위한 RAM 공간을 확보할 수 있다.

 

Linux에서 Swap Memory 확인하기

swap memory를 확인하려면 아래 명령어를 이용해 확인할 수 있다.

free -h

 

 

각 항목에 대한 설명은 다음과 같다.

  • total: 전체 설치된 메모리 (957Mi)
  • used: 사용 중인 메모리 (321Mi)
  • free: 사용 가능한 메모리 (76Mi)
  • shared: 공유 메모리 (0Ki) - 하나의 프로세스에서 다른 프로세스의 메모리를 사용하고 싶을 때 활용
  • buff/cache: 버퍼와 캐시를 위해 사용하는 메모리 (559Mi) - 커널이 성능 향상을 위해 캐시 영역으로 사용
  • available: Swap 메모리 사용 없이 새로운 프로세스에서 할당 가능한 메모리의 예상 크기 (464Mi)
  • Swap: 할당된 Swap 메모리 (2.0Gi)

 

Swap Memory 추가하기

1)  디스크 용량 확인

먼저 swap memory를 할당하는 것은 하드디스크를 이용하기 때문에 디스크 용량을 확인해보아야 한다.

df -h

 

 

2) Swap Memory 설정

디스크 용량을 확인했다면 얼마 정도의 swap 메모리를 할당 할 지 정한 뒤 아래의 명령어를 차례로 입력한다.

# 2GB 크기의 swap 파일을 생성한다.
sudo fallocate -l 2G /swapfile

# swap 파일의 권한을 변경한다. (보안을 위해 root만 읽기/쓰기 가능하도록 설정)
chmod 600 /swapfile

# 생성된 파일을 swap 파일 형식으로 포맷한다.
sudo mkswap /swapfile

# swap 파일을 활성화한다. 이제 시스템이 이 파일을 swap 공간으로 사용할 수 있다.
sudo swapon /swapfile

# 아래부터는 시스템 재부팅 후에도 swap 파일이 자동으로 활성화되도록 하기 위한 단계이다.
# 파일 시스템 테이블을 편집하기 위해 vi 편집기를 연다.
vi /etc/fstab

# 에디터가 열렸다면, 해당 파일의 마지막 라인에 아래의 명령어를 입력하고 저장하고 나온다.
/swapfile none swap sw 0 0

 

위와 같이 Swap 메모리를 할당해주면 "Killed" 에러는 더 이상 발생하지 않게 된다.

* 명령어 정리
1. sudo
유닉스 계열 OS에서 관리자 권한으로 실행할 수 있게 하는 명령어이다.
Shut Up and Do ❓

1. chmod 600
- Linux에서 파일 권한은 소유자(Owner), 그룹(Group), 다른 사용자(Others) 세 가지 범주로 나뉜다.
- 각 범주별로 읽기(Read=4), 쓰기(Write=2), 실행(Execute=1) 권한이 있다.
- chmod 600에서 600의 의미는 다음과 같다.
    첫 번째 6: 소유자 권한 (4+2=6) → 읽기/쓰기 가능
    두 번째 0: 그룹 권한 (0) → 아무 권한 없음
    세 번째 0: 다른 사용자 권한 (0) → 아무 권한 없음
- Swap 파일은 보안을 위해 root 사용자만 접근할 수 있도록 600 권한을 설정한다.

2. mkswap
생성된 파일에 Swap 메모리로 사용할 수 있는 특별한 형식을 부여하는 명령어이다.

 

+) 참고로 권장하는 Swap Memory 크기는 다음 표와 같다.

설치된 RAM 용량
권장되는 스왑 용량
최대절전을 허용할때 권장되는 스왑용량
2GB or less
RAM 용량의 2배
RAM 용량의 3배
2GB - 8GB
RAM 용량
RAM 용량의 2배
8GB - 64GB
적어도 4GB ~ RAM 용량의 1 / 2 배
RAM 용량의 1.5배
64GB or more
적어도 4GB
최대절전이 권장되지 않음

(출처: Red Hat Enterprise)

 

 

 

프로세스가 강제 종료되어도 파이프라인이 성공적으로 완료된 이유


OOM로 인해 프로세스가 강제 종료 되었는데도 파이프라인이 성공적으로 완료된 이유는 쉘 스크립트는 실행 중 오류가 발생해도 멈추지 않고 다음 작업을 진행하기 때문이다.

 

이러한 문제를 해결하기 위해 쉘 스크립트에서 오류를 감지하고 처리하는 방법이 필요하다고 생각해서 스크립트 처리 중 에러를 만나면 즉시 종료되게 할 수 있는 옵션인 set -e을 추가해주었다.

ssh -o StrictHostKeyChecking=no "$AWS_EC2_USER@$AWS_EC2_HOST" << 'EOF'
  set -e
  cd /home/ubuntu/codeit-resources
  git pull origin develop
  pnpm install
  pnpm --filter=api build
  pm2 restart codeit-server
EOF

 

이후에 ssh로 github actions가 원격으로 서버에 접속해서 작성한 script를 실행할 수 있게 해주는 appleboy/ssh-action 액션을 알게되었다.

 

이 액션을 이용하면 코드를 좀 더 깔끔하게 작성할 수 있을 것 같아 전체 파이프라인을 수정했고 수정된 코드는 아래와 같다.

name: express-deployment

on:
  push:
    branches: [develop]

jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to EC2
        uses: appleboy/ssh-action@v1.2.2
        with:
          host: ${{ secrets.AWS_EC2_HOST }}
          username: ${{ secrets.AWS_EC2_USER }}
          key: ${{ secrets.AWS_PRIVATE_KEY }}
          port: ${{secrets.SERVER_PORT}}
          script: |
            set -e
            cd /home/ubuntu/codeit-resources
            git pull origin develop
            pnpm install
            pnpm --filter=api build
            pm2 restart codeit-server

 

 

 

마치며


주 메모리가 부족할 때 하드디스크의 메모리 공간을 사용할 수 있다는 것을 처음 알게되었다. 

그럼 주 메모리 부족할 때 계속 공간 빌려쓰면 되는거 아닐까..? 생각도 해봤는데 메모리를 스와핑하게 되면 속도가 느려진다.

 

이유는 메모리는 나노초 단위로 수행되는 반면, 디스크는 밀리초단위로 수행되기 때문이다. 따라서 메모리에서 해당 데이터를 접근하는 것에 비해 상대적으로 느리게 되므로 퍼포먼스 저하가 일어난다.

 

스왑 메모리를 주메모리에 따라 제한하는 기준을 잘 몰랐는데 이러한 이유 때문이었구나 😲

 

 

 

 

참고

https://jw910911.tistory.com/122

https://ssue-dev.tistory.com/2#Swap%20%EB%A9%94%EB%AA%A8%EB%A6%AC%20%ED%95%A0%EB%8B%B9%20%EB%B0%A9%EB%B2%95-1

https://blog.naver.com/happy_jhyo/70146791303

https://bconfiden2.github.io/2023/10/05/error-handling/

https://hi-ai0913.tistory.com/14

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함