시작
모씨의 메인 데이터베이스는 PostgreSQL 입니다. 대부분의 데이터는 이곳에서 저장, 관리되고 있는데요, 최근에 데이터베이스 운용과 관련된 이슈가 발생하여 문제를 해결하는 과정에 대해 이야기를 해 보고자 합니다.
문제 인식
최근에 간헐적으로 데이터베이스의 커넥션이 꽉 차면서 서비스가 짧은 시간 느려지거나 멈추는 현상이 보고되기 시작 하였습니다. 흔히들 말하는 ‘렉’ 이라고 하죠. 그동안 쌓아올린 코드들에 문제가 있지 않을까 몇 번 검토도 해 보고, 쿼리의 문제인 것 같아서 쿼리도 분석을 해 보았습니다만 별 다른 복잡한 쿼리를 사용하지 않고 있었기에 원인을 파악하기가 쉽지 않았습니다.
그러다가 최근 이용자가 가장 붐비는 피크 타임에 비슷한 장애가 반복적으로 발생하면서 원인을 확인해 볼 수 있었습니다. 다양한 원인이 가설로 등장했지만 현재 가장 가능성 높은 원인으로 EBS 의 IOPS 문제로 결론 내렸습니다.
매력적인 EBS, 하지만…
실제 문제 사례를 이야기 하기 전에 EBS 에 대해서 간단하게 이야기할까 합니다.
AWS 에서 제공하는 EBS 스토리지 서비스는 굉장히 매력적인 기능들이 많이 포함되어 있습니다. EBS 에 저장된 데이터를 언제든 스냅샷을 생성하여 손쉽게 데이터를 백업할 수 있고, 스냅샷 이미지를 통해 클릭 몇 번 만으로 복제 인스턴스를 생성할 수도 있습니다. 또한 EBS 는 단순 스토리지가 아닌 추상화된 응용프로그램으로써 데이터 내결함성이 일반 스토리지에 비해 10 배이상 강력합니다.
하지만 꼭 장점만 있는 것은 아닙니다. EBS 는 네트워크를 통해 인스턴스와 데이터를 주고 받기 때문에 트래픽이 높은 인스턴스에서 사용할 경우 EBS Optimized 옵션을 켜 두지 않으면 제대로 된 성능을 확보할 수가 없습니다. 또한 gp2 EBS 의 경우 GB 당 3 IOPS 를 제공하며, 1TB 미만의 gp2 EBS 는 I/O Credit 을 통한 성능 제한까지 걸려 있기 때문에 높은 성능을 필요로 하는 곳에 제대로 EBS 를 설정하지 않을 경우 심각한 서비스 장애를 일으킬 수 있습니다. 또한 스냅샷으로부터 복원한 EBS 의 경우 최초 블럭에 접근되기 전에는 S3 에 블럭이 저장되어 있기 때문에 레이턴시가 발생하는 관계로 fio 등의 툴을 이용하여 EBS 의 모든 블럭을 미리 읽어서 성능을 확보하지 않으면 마찬가지로 서비스에 지장을 줄 수 있습니다.
실제로 작년에 I/O Credit 과 관련된 문제를 경험한 적이 있었는데요, PostgreSQL 데이터베이스에 500GB 의 EBS 를 할당 하여 운용중일때의 이야기입니다. 당시 잘 동작하던 데이터베이스가 갑자기 주기적으로 프리징 되는 현상이 발생하였습니다. 확인해 본 결과 급증하는 트랜잭션으로 인한 EBS I/O Credit 을 모두 소진하여 인스턴스에서 EBS 의 데이터에 접근을 할 수 없었기에 발생하는 문제였습니다. 당시 논의 끝에 EBS 를 gp2 2TB 로 증설하여 I/O Credit 제한을 없앰과 동시에 6,000 IOPS 를 확보 하는 것으로 문제를 해결할 수 있었습니다.
즉, EBS 는 매우 강력한 솔루션이지만 그만큼 주의깊게 사용해야 최고의 성능을 발휘할 수 있습니다.
IOPS 문제란?
다시 처음으로 돌아가서 이번에 발생했던 PostgreSQL 의 IOPS 문제는 간단히 말해 현재 EBS 에 할당된 IOPS 를 모두 소진할 정도로 트랜잭션이 증가해서 발생하는 문제였습니다. 쿼리를 튜닝하거나 캐시 서버의 활용을 극대화 할 수 있는 방법들도 있을 수 있겠습니다만 성능 개선의 포인트를 하드웨어 업그레이드에 집중하기로 결정 내렸고, 해당 상황에서 고려할 수 있는 대안은 대충 다음과 같았습니다.
PIOPS EBS(io1) 로 전환하기
가장 깔끔한 해결책입니다. PIOPS EBS(io1) 를 통해 원하는 성능과 용량의 EBS 를 선택할 수 있습니다. 다 좋은 io1 EBS 지만 도입시 가장 큰 걸림돌은 역시 가격입니다. 동일한 IOPS 를 기준으로 할 때 io1 EBS 는 gp2 에 비해 2배 이상 비쌉니다. 물론 IOPS 와 별도로 gp2 EBS 의 처리량은 160MB/s, io1 EBS 의 처리량은 320MB/s 로써 두 배이긴 합니다. 따라서 io1 을 도입하기 전에 단순 IOPS 요구량 뿐 아니라 초당 처리량이 얼마나 필요한지에 대한 고려가 필요합니다.
검토 결과 모씨 서비스의 데이터베이스는 IOPS 성능이 더 필요한 반면에 처리량이 부족하진 않은 상태였습니다. 따라서 io1 EBS 는 선택 후보지에서 제외하기로 결정 하였습니다.
gp2 EBS 를 최대한 활용하기
다음으로 gp2 EBS 를 RAID 로 묶어서 성능을 확보하는 방안을 고려 하였습니다. 하지만 RAID 0 으로 묶을 경우에 한하여 IOPS 성능이 향상되며, RAID 1(미러링) 을 통해서는 처리량 확보만 가능할 뿐(그것도 읽기에 한하여) IOPS 성능은 향상되지 않는다는 사실을 알게 되었습니다. 하지만 RAID 0 으로만 데이터베이스 스토리지를 구성할 경우 만에 하나 있을 수 있는 무결성 문제에 너무나도 취약해지는 관계로 RAID 10 을 생각할 수 밖에 없게 되는데 이 경우 결과적으로 io1 EBS 보다 비싸지게 됩니다.
고민 끝에 gp2 EBS 에서 IOPS 를 최대로 확보할 수 있는 3334GB 이상의 용량을 할당하여 적용해 보기로 하였습니다. 비용을 최대한 억제하면서 성능을 확보하고자 하는 의도이며, 만일 해당 성능도 부족해질 경우 io1 EBS 로 바로 이동하기로 결정 하였습니다.
어떻게 업그레이드 할 것인가?
이제 방법을 결정했으니 수행할 일이 남았습니다. 이것 역시 몇 가지 업그레이드 안을 두고 삽질을 거듭했습니다.
가장 간단하게
EBS 기반인 만큼 가장 간단하게 업그레이드 하는 방법은 다음과 같습니다.
- EBS 스냅샷을 생성 합니다.
- 생성한 스냅샷을 기반으로 증가된 용량의 EBS 를 생성 합니다.
- 인스턴스에 마운트 합니다.
얼핏 보면 참 쉬운 방법입니다만 여기에 함정이 하나 있습니다. 바로 스냅샷을 기반으로 생성된 EBS 스토리지의 경우 EBS 의 모든 블럭이 바로 활성화 되지 않는다는 점입니다. 스냅샷을 기반으로 생성된 EBS 의 경우에는 최소 한 번 이상 블록에 접근 요청이 발생해야 해당 블럭의 성능이 확보되는 구조입니다. 따라서 위와 같은 형태로 EBS 를 증설할 경우에는 AWS 설명서에 나온 대로 fio 나 dd 등의 명령어를 이용해서 모든 블럭에 접근하는 작업을 수행해야 합니다. 일반적으로 이 작업 시간은 6시간에서 8시간, 혹은 그 이상 걸릴 수 있습니다.
I/O 가 낮은 서버라면 모를까, 메인 데이터베이스 같이 I/O 요구량이 높은 서버에서 스냅샷 기반의 이미지를 이용해서 EBS 를 생성할 수도, 운용 환경에서 fio 를 돌릴 수도 없습니다.
AWS DMS
다음으로 생각해 본 것은 AWS 에서 제공하는 Database Migration Service 였습니다. 중계 서버가 데이터를 복제 해 주며, Write Ahead Log 역시 반영해 주는 굉장히 편리하고 놀라운 서비스입니다.
바로 DMS 를 이용한 테스트를 해 보았는데, 테스트 도중 생각지도 못한 문제점을 맞이합니다. 바로 PostgreSQL 에만 존재하는 JSON 타입과 ARRAY 타입을 AWS DMS 가 지원하지 않는 문제였습니다. 굉장히 편리한 서비스였으며, 거의 무중단에 가까운 마이그레이션 시나리오를 작성할 수 있었습니다만 어쩔 수 없이 사용을 포기해야 했습니다.
PostgreSQL 을 통해
마지막으로 생각해 본 것은 PostgreSQL 에서 지원하는 복제 기능을 이용하는 것입니다. 이것 저것 생각해 보다가 내린 마이그레이션 시나리오는 다음과 같았습니다.
- 옮기고자 하는 스탠드 바이 서버를 준비합니다.
- 마스터 서버에서 복제 준비를 완료 합니다.
- 서비스를 중단합니다.
- 스탠드 바이 서버에서 마스터 서버로 pg_basebackup 을 이용하여 모든 데이터를 받아옵니다.
- 데이터 무결성을 체크합니다.
- 서비스를 재개합니다.
위와 같이 시나리오를 계획하고 테스트를 해 본 결과 충분한 시간 안에 데이터베이스 마이그레이션을 완료할 수 있을 것이라는 판단이 들었습니다.
마이그레이션, 그러나…
새벽에 서비스를 중단하고 데이터베이스 마이그레이션을 계획한 시나리오대로 진행하는 것은 순조로웠습니다. 계획된 시간 안에 모든 작업을 마무리 하고 서비스 오픈 전 마지막 내부 테스트를 진행하는데 갑자기 문제가 터졌습니다. 전혀 이상할 것이 없는 SELECT 쿼리문이 너무나도 낮은 성능을 냈기 때문입니다. 당황하면서 이런 저런 테스트를 해 보는 사이에 오픈 예정 시간은 순식간에 지나가 버리고 이 난관을 어떻게 돌파해야 할 지 막막한 상황이 되어 버렸습니다.
이런 저런 테스트를 진행하다가 문득 혹시 인덱스가 문제가 있지 않았을까? 인덱스를 리빌딩 해 줘야 하는 것일까? 혹은 FULLVACUUM 을 해줘야 하는 것일까? REINDEX 나 FULL VACUUM 모두 엄청난 시간을 잡아먹는데 오늘 데이터베이스 마이그레이션은 실패하는 것일까? 하는 생각이 들었습니다. 에라 모르겠다 하는 심정으로 일단 가장 시간을 적게 소모하는 ANALYZE 를 진행해 보고, 안되면 VACUUM, REINDEX 순으로 작업을 진행하던 아니면 마이그레이션을 포기하던 결정하기로 했습니다.
PostgreSQL 의 VACUUM 과 ANALYZE
PostgreSQL 은 다른 데이터베이스에는 없거나 희박한 개념인 VACUUM 과 ANALYZE 라는 명령어가 있습니다. 각 명령어가 수행하는 역할은 대략 다음과 같습니다.
VACUUM
VACUUM 은 테이블에 임계점을 초과하는 업데이트나 삭제가 발생할 경우 더 이상 참조되지 않는 데이터들을 정리하여 FSM(Free Space Map) 이라는 공간으로 확보하여 파일 접근의 효율성을 최대한 높이는 작업입니다.
ANALYZE
테이블에 임계점을 초과하는 업데이트나 삭제가 발생할 경우 테이블 파일에 통계 데이터를 기록하는데 ANALYZE 는 주기적으로 해당 데이터를 수집하여 최적의 쿼리 계획을 수립하는 작업입니다.
해결
놀랍게도 ANALYZE table_name; 을 각 테이블 별로 한 번씩 수동으로 수행한 순간 그렇게 느리던 쿼리들의 성능이 순식간에 원상 복귀되는 것을 확인할 수 있었습니다. 생각지도 못한 해결책이었습니다.
최종적으로 테스트를 잘 마무리 하고 서비스 오픈 예정 시간보다 약 2시간 초과한 끝에 간신히 서비스를 재개할 수 있었습니다.
결론
문제를 인식하고 이런 저런 삽질과 테스트를 반복한 끝에 만 3일만에 성공적으로(?) 데이터베이스 서버 교체 및 업그레이드 작업을 마무리 할 수 있었습니다. 결과적으로 성능은 매우 만족스러울 정도로 높아졌습니다만 그 보다 더 많은 것들을 배울 수 있었던 시간이었습니다. 항목별로 정리해 보는 결론은 다음과 같습니다.
- EBS
- EBS 는 매우 좋은 솔루션이지만, 그만큼 주의깊게 사용해야 합니다.
- io1 은 매우 뛰어난 성능을 제공합니다만 정말 io1 을 사용해야 하는가 고민할 필요가 있습니다.
- Cloud Watch 에서 제공하는 EBS 의 IOPS, Bandwidth 를 잘 모니터링하여 적합한 솔루션을 찾아내야 합니다.
- PostgreSQL
- 정말 좋은 데이터베이스입니다만, 참고자료가 많지 않다는 것은 약간 아쉽습니다.
- 복제가 끝난 후에는 테이블 별로 반드시 ANALYZE 를 수행하는 것이 성능 확보에 도움, 아니 성능이 확보됩니다.
- DMS 서비스는 매우 뛰어난 기능을 제공합니다. 단, 기능상 제약을 잘 확인할 필요가 있습니다.
이 글이 AWS 에서 데이터베이스를 구성하시려는 분, 그리고 PostgreSQL 을 사용하시면서 어려움을 느끼는 분들께 도움이 되었으면 합니다. 늘 그렇지만 궁금한 점에 대해서는 언제든지 말씀 주세요. 귀 귀울여 듣겠습니다.
긴 글 읽어 주셔서 감사합니다.