서버의 메모리가 부족할 때
저번 배포 장애의 원인은 물리적인 서버 메모리가 부족했던 것으로 잠정 결론 지어졌다. 그렇다면 서버 메모리가 부족하면 어떤 일이 발생하고, 어떤 지표들로 감지할 수 있는지 한 번 알아봤다.
가상 메모리
가상 메모리는 ‘앱이 실제 물리 메모리 주소를 직접 다루지 않게 해주는’ 기술이다. 인터넷에 많이 올라와 있는 설명들 중 “가상 메모리 = 디스크 스왑”처럼 작성한 케이스가 많은 것 같은데, 정확한 설명은 아닌 것 같다. 조금 더 좁혀서 말하면, 가상 메모리는 메모리 관리에 대한 추상화 계층이고 디스크 스왑은 가상 메모리를 구현하는 수단 중 하나라고 보면 될 것 같다.
가상 메모리에 대해 간략하게 정리해보면,
- 앱은 자신만의 가상 주소를 통해 메모리에 접근한다.
- OS는 MMU로 이 가상 주소를 실제 물리 메모리 주소에 매핑한다.
- 앱은 물리 메모리가 어떻게 구성되었는지, 어디에 무엇이 있는지 신경 쓸 필요가 없다.
- 메모리가 부족하다면 OS는 데이터를 디스크로 잠깐 옮기고(=디스크 스왑) 필요할 때 메인 메모리에 다시 적재한다.
이런 방식 덕분에 누릴 수 있는 여러 장점들이 있다.
- 포인터 같은걸로 잘못된 메모리 접근을 방지할 수 있다
- 연속된 주소 공간 요청도 자유롭게 할 수 있다. (물리 메모리 조각 여부는 앱이 몰라도 되니께)
- 앱이 비정상으로 죽더라도 OS가 메모리를 회수, 정리할 수 있다.
결국 가상 메모리는 앱과 물리 메모리 사이에 중재자가 존재하는 시스템의 이점을 누리고 있다. 이런 추상화 덕분에 시스템 안정성과 유연성이 크게 올라간다.
스레싱
메모리가 부족해지면 스레싱이 발생한다. 스레싱은 OS가 페이지 교체 작업, IO에 리소스를 모두 소모해 실제 작업이 이루어지지 않는 상황이다. CPU는 대부분의 시간을 페이지를 디스크에 저장하거나 다시 읽어오는 데 쓰고, 유저가 요청한 작업은 거의 실행되지 않는다. 결과적으로 시스템은 거의 멈춘 것마냥 느려지고, 아무리 기다려도 프로그램이 응답하지 않거나 서버 전체가 마비된다.
실험
간단하게 메모리 터지기 전까지 도커 컨테이너를 계속 띄워봤다. 그리고 터지는 순간 어떤 지표들이 튀었나 확인해보았다.
의도대로 서버가 잘 터졌다. 이제 그 순간의 다른 지표들을 한 번 확인해보자.
cpu iowait 지표와 디스크의 읽기 지표가 튀는 것을 확인할 수 있었다. 페이지 폴트도 계속 발생하고 있었다.
그런데 스왑 used가 0이라서 이게 뭘까 생각해봤는데, 리눅스 서버 세팅할 때 스왑 영역을 따로 마운트 해줘야 하는 경우도 있다고 하더라. 그렇다면 이번 실험에서의 시나리오는
- 메모리 부족, 데이터 제거
- 그런데 필요한 데이터, 다시 읽어와서 페칭
- 메모리 부족, 데이터 내림
- 그런데 필요한 데이터, 다시 읽어와서 페칭
이 악의 순환을 돌고 있던 게 아닐까 싶다. 결국 데이터를 다시 읽어오나, 디스크에 옮겼던 일부 메모리 데이터를 다시 페칭하는 거나 갸가 갸 아닌가 싶긴 하다. 가설 세운게 맞겠지??