AWS EC2, EBS: 스왑 메모리에 대한 실험과 모범 사례
이 글을 작성한 이유
리픽 프로젝트에서 Github Actions와 Docker을 통해 CICD를 진행합니다. 그런데 개발 서버가 CICD시 두번 중 한번은 죽는 문제가 발생했습니다. 원인을 조사해본 결과 리소스 고갈이었고, 해결 방안으로는 수직적 확장(스펙업)과 스왑 메모리 할당이 있었습니다.
AWS re:Post 게시글인데요, 재미있는 문장을 발견했습니다.
참고: 모범 사례는 임시 스토리지 인스턴스 스토어 볼륨에만 스왑 공간을 생성하는 것입니다.
이것에 대한 근거는 해당 게시글에 명시되어 있지 않았습니다. 그랬기 때문에 저는 이 근거에 대해 깊게 조사해보고자 합니다.
AWS EC2 프리티어의 메모리는 너무 작다!
저는 서버를 AWS의 t2.micro 인스턴스를 띄워서 사용하고 있습니다. 프리티어니까요. 프리티어가 없다면 월에 대체 얼마를 지불해야 할지 감도 잡히지 않습니다. 그만큼 강력한 프리티어인 만큼, 돈을 지불하고 싶지 않은 입장에서 선택지는 줄어들 수 밖에 없습니다. 아마 저를 비롯한 많은 대학생 분들이 프로젝트를 진행 시 t2.micro 인스턴스를 사용하실겁니다.
t2.micro의 스펙을 한 번 살펴보겠습니다.
1GiB의 RAM.. 스펙은 정말 귀엽습니다. 무료로 제공해주는데 이것도 감사히 사용해야 하는 것이 맞습니다. Amazon EC2 T2 인스턴스는 기본 수준의 CPU 성능과 더불어 기본 수준을 넘어 버스트할 수 있는 기능을 제공하는 버스트 가능 성능 인스턴스입니다. 하지만 크레딧이 고갈되면 서버 장애가 발생하죠. 저스펙 인스턴스를 사용하면 리소스 고갈 이슈가 빈번하게 발생합니다. 많은 분들이 스왑 메모리 할당을 해결 방안으로 도입하죠.
스왑 메모리
스왑 메모리는 주 메모리가 부족할 때 하드디스크와 같은 공간을 메모리로 사용하기 위한 가상 메모리입니다. t2.micro 인스턴스를 사용하는 보통의 소규모 프로젝트를 기준으로 말씀드리자면, EC2와 연결된 저장 장치인 EBS(Elastic Block Storage) 볼륨에 스왑 메모리를 할당할 수 있습니다.
그림으로 표현하면 이런 느낌이겠군요. 그림을 보시면 아시겠지만 EC2와 EBS는 붙어있는 하나의 리소스가 아닙니다. EC2와 EBS는 AWS 내부 네트워크를 통해 통신하며, 엄연히 분리되어 있는 리소스입니다. 저는 이것을 근거로 가설을 세울 수 있었습니다.
가설: EBS로의 I/O는 네트워크 성능에 영향을 줄 것이다
메모리 스왑이 자주 일어나면 I/O 성능에 부담을 줄 것으로 예상됩니다. 또한, EC2와 EBS는 네트워크로 연결되어 있기 때문에, 결과적으로 네트워크 성능에도 영향을 주지 않을까 했고, 그렇기 때문에 EBS에 스왑 메모리 할당은 모범 사례가 아니라고 언급하셨다고 생각했습니다. 이제 가설을 세웠으니, 실험을 통해 검증을 하도록 하겠습니다.
결론부터 말씀드리면 실험은 (엄청난 삽질 후) 실패했습니다. 🥲
실험 1: 인스턴스에 부하 테스트를 통해 네트워크 메트릭에 변화가 생기는지 확인하기
1. 스왑 메모리 할당 없이 부하 테스트 후 지표 확인하기
우선 인스턴스를 생성합니다. ap-northeast-2 (서울) 지역에 Amazon Linux 2023 AMI를 가진 t2.micro 인스턴스를 생성하였습니다. 메모리는 1GiB이며, 추가적으로 특별한 설정은 하지 않았습니다. EBS 볼륨은 자동으로 할당됩니다.
stress --vm 1 --vm-bytes 512M --timeout 10m
이 명령어를 통해 10분간 메모리 512M를 사용하도록 합니다.
명령어를 실행하고 메트릭이 올라올때까지 기다렸습니다.
- 스트레스 테스트로 CPU Utilization 지표가 상승했습니다.
- Network in / out 은 상승하지 않았습니다. I/O작업을 수행하지 않았기에 예상된 결과입니다. (중간에 상승한 것은 접속하여 stress 설치할 때 사용한 리소스로 추정됩니다.)
다음 명령어로 I/O작업을 수행하여 메트릭을 다시 뽑아보도록 하겠습니다.
stress --hdd 4 --hdd-bytes 1G -v --timeout 1h
4개의 프로세스가 각각 1GB 크기의 파일을 디스크에 쓰고 지우는 작업을 반복하는 테스트입니다. 제 추측이 맞다면 EC2와 EBS는 네트워크 상에서 연결되어 있는 서로 다른 리소스이기 때문에, 해당 작업 수행시 Network in / out 지표가 상승할 겁니다.
하지만 지표는 제 예상대로 나타나지 않았습니다. Network in과 Network out이 폭발적으로 증가하는 것을 기대하였으나 그렇지 않았고, 실험 방법이 잘못되었나 하는 의심에 무의미한 테스트 반복을 하였는데요, 원하는 대로 지표가 나오지 않은 이유는 GPT 선생님이 답변해 주셨습니다.
Amazon EC2 인스턴스와 Amazon EBS(Elastic Block Store) 볼륨은 AWS의 내부 네트워크를 통해 연결됩니다. EC2 인스턴스에서 EBS 볼륨으로 데이터를 읽고 쓸 때의 데이터 전송은 내부적으로 처리되기 때문에, 사용자의 네트워크 사용량이나 비용에 영향을 주지 않습니다. 따라서 이러한 데이터 전송은 CloudWatch의 NetworkIn 또는 NetworkOut 메트릭에 나타나지 않습니다.
EC2 인스턴스와 EBS 볼륨 사이의 내부 네트워크 트래픽의 정확한 송수신량을 직접적으로 측정하는 것은 AWS 사용자가 접근할 수 있는 CloudWatch 메트릭을 통해서는 제공되지 않습니다. AWS는 내부 네트워크의 세부 사항을 추상화하여 관리하므로, 사용자가 EC2와 EBS 간의 내부 네트워크 트래픽을 직접적으로 모니터링하거나 확인하는 것은 제한됩니다.
EBS 볼륨 성능 메트릭 사용: AWS CloudWatch에서 제공하는 VolumeReadBytes, VolumeWriteBytes 메트릭은 EBS 볼륨으로 전송된 총 바이트수를 나타냅니다. 이 메트릭은 EC2에서 EBS로의 실제 데이터 전송량에 대한 간접적인 정보를 제공할 수 있습니다. 전송된 데이터의 양을 통해 네트워크 트래픽 사용량의 대략적인 추정이 가능합니다.
실제로 I/O 테스트는 정상적으로 진행되었습니다. 허나 EC2 - EBS 연결은 AWS 내부 네트워크 망을 통해 이루어지기 때문에 Network in/out에 나타나지 않은겁니다. 그도 그럴 것이 제가 CW 메트릭을 뽑는데 디스크 I/O가 네트워크 송수신 메트릭에 영향을 준다면 분석이 엄청 불편해질 것 같다는 생각이 드네요.
검증을 위해선 다른 접근 방법이 필요했습니다. 외부로부터의 트래픽을 이용한 성능 테스트가 필요했습니다. Jmeter가 필요한 시점이군요.
2. Jmeter를 이용하여 네트워크 성능 지표 만들기
I/O작업을 수행하는 인스턴스와 그렇지 않은 인스턴스의 network throughput 지표를 만들어 보도록 하겠습니다. 앞선 실험에서 디스크 I/O 스트레스는 CPU를 6%정도로 조금만 점유했는데요, 그렇다면 스트레스 명령이 실행 중인 인스턴스라고 하더라도 성능상 크게 차이를 보이지 않아야 할 것입니다. 따라서 제 가설이 맞다면 I/O 작업 중인 인스턴스의 네트워크 성능이 떨어지겠죠.
사실 고려 사항이 더 있습니다. 외부 트래픽 테스트는 Internet bandwidth를 사용한다는 점입니다. EC2-EBS는 AWS 내부 네트워크를 사용하지만, 제 컴퓨터에서 Jmeter를 통해 진행하는 테스트는 Internet을 거친 테스트죠. 그래서 제가 원하는대로 지표가 나오지 않을 수 있습니다. (그럼 내부 인스턴스를 하나 더 만들고 거기서 부하를 보내는 테스트를 또 해야겠죠?..)
두 가지 환경에 대해 테스트를 진행합니다:
- 스트레스 테스트를 진행중이지 않은 인스턴스에 Jmeter를 통한 테스트
- 디스크 I/O 부하를 준 인스턴스에 Jmeter를 통한 테스트
CPU 부하의 경우 디스크 I/O의 CPU 사용량보다 많이 점유하도록 설정하도록 하겠습니다.
1. 스트레스 테스트를 진행중이지 않은 인스턴스에 Jmeter를 통한 테스트
시나리오는 다음과 같이 작성합니다. 10분간 테스트가 지속되며, Ramp-up 기간은 3분입니다.
Throughput : 2567.4/sec의 지표를 확인할 수 있었습니다.
평균 응답 속도는 21ms 입니다.
CW 메트릭은 다음과 같습니다.
2. 디스크 I/O 부하를 준 인스턴스에 Jmeter를 통한 테스트
stress --hdd 4 --hdd-bytes 1G -v --timeout 1h
아까 사용했던 명령어를 다시 사용하여 I/O 작업을 진행시키고, 동시에 Jmeter 부하 테스트를 실행했습니다.
Throughput : 4024.9/sec의 지표를 확인할 수 있었습니다.
평균 응답 속도는 20ms 입니다.
I/O 실행하면서 동시에 했는데도 Throughput은 더 높네요. 제 예상을 빗나갔습니다. 사실 I/O테스트가 CPU를 조금이라도 점유하기 때문에 네트워크와 관련이 없다고 하더라도 Throughput은 더 낮게 나올 줄 알았습니다. 근데 심지어 더 빠르다고 실험은 실패라고 볼 수 있겠습니다. 여기서 고민을 더 많이 해보았고 결국 나름의 근거를 찾을 수 있었습니다.
Jmeter로 EC2에 요청을 보내는 것은 Internet을 거쳐 이루어지고, EC2-EBS는 AWS 내부 네트워크를 거쳐 이루어집니다. 둘의 Bandwidth는 다르고, 서로 연관이 없습니다.
EC2 하드웨어의의 네트워크 처리 능력에 의해 연관이 있다고 하더라도, 정확한 측정이 어렵습니다. 애초에 Internet을 거치겠다는 것은 오직 컴퓨터와 EC2의 성능만을 검사하는 것이 아닌, 그 사이 모든 Router를 통합한 성능을 측정하는 것이기 때문입니다.
정확한 테스트를 위해서는 다음과 같은 아키텍처가 필요할 것 같습니다만, 해당 실험을 위해서는 또 다른 사항을 고려해야 합니다.
(다행히도) 단일 흐름 트래픽 제한 == 다중 흐름 트래픽 제한으로, 5Gbps의 제한이 있습니다. 그런데 저희가 Jmeter 부하 테스트로 5Gbps를 다 사용하는 것은 꽤 어려운 일일 것으로 예상됩니다. (초당 625MB의 데이터를 전송해야 합니다.)
여기까지 해보니 네트워크 성능보다는 I/O 자체에 대한 성능 이슈가 더 크겠구나 하는 생각이 들었습니다. 그런 생각으로 조사에 나섰고 다음과 같은 명쾌한 답변을 찾을 수 있었습니다:
네. 기본적으로 EBS는 I/O 당 요금 부과를 합니다. 메모리가 늘 부족한 앱을 사용한다면 계속해서 swap을 하며 I/O가 생기고, 이것에 대한 요금이 부과됩니다. 게다가 디스크로의 I/O는 느리기도 하죠.
그리고 AWS 내부 네트워크 트래픽에 대한 Bandwidth를 사용하는 것 또한 사실입니다. 하지만 이보다는 위의 이유가 훨씬 크죠. 며칠 동안 테스트 한다고 고생을 좀 했는데 결론이 허무하군요.
Instance Store 볼륨을 사용한다고 무조건 잘 한 것은 아닙니다. insacne store 볼륨으로의 스왑보다는 RAM 스펙을 업그레이드 하는 것이 성능상 더 좋으니까요.
자 이렇게 스왑 메모리에 대해 알아보았는데요. 사실 모범 사례가 아니라곤 하지만 메모리가 평소에도 계속 부족한 서버가 아니라면 EBS 볼륨에 스왑 메모리를 할당해도 크게 문제가 되지 않습니다. 다만 애플리케이션이 커짐에 따라 / 트래픽이 올라감에 따라 이러한 점들을 고려해보아야 나중에 생길 문제에 대해 발빠르게 대응할 수 있겠죠?