PostgreSQL 업그레이드 이야기


시작

모씨의 메인 데이터베이스는 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 기반인 만큼 가장 간단하게 업그레이드 하는 방법은 다음과 같습니다.

  1. EBS 스냅샷을 생성 합니다.
  2. 생성한 스냅샷을 기반으로 증가된 용량의 EBS 를 생성 합니다.
  3. 인스턴스에 마운트 합니다.

얼핏 보면 참 쉬운 방법입니다만 여기에 함정이 하나 있습니다. 바로 스냅샷을 기반으로 생성된 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 에서 지원하는 복제 기능을 이용하는 것입니다. 이것 저것 생각해 보다가 내린 마이그레이션 시나리오는 다음과 같았습니다.

  1. 옮기고자 하는 스탠드 바이 서버를 준비합니다.
  2. 마스터 서버에서 복제 준비를 완료 합니다.
  3. 서비스를 중단합니다.
  4. 스탠드 바이 서버에서 마스터 서버로 pg_basebackup 을 이용하여 모든 데이터를 받아옵니다.
  5. 데이터 무결성을 체크합니다.
  6. 서비스를 재개합니다.

위와 같이 시나리오를 계획하고 테스트를 해 본 결과 충분한 시간 안에 데이터베이스 마이그레이션을 완료할 수 있을 것이라는 판단이 들었습니다.

마이그레이션, 그러나…

새벽에 서비스를 중단하고 데이터베이스 마이그레이션을 계획한 시나리오대로 진행하는 것은 순조로웠습니다. 계획된 시간 안에 모든 작업을 마무리 하고 서비스 오픈 전 마지막 내부 테스트를 진행하는데 갑자기 문제가 터졌습니다. 전혀 이상할 것이 없는 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 을 사용하시면서 어려움을 느끼는 분들께 도움이 되었으면 합니다. 늘 그렇지만 궁금한 점에 대해서는 언제든지 말씀 주세요. 귀 귀울여 듣겠습니다.

긴 글 읽어 주셔서 감사합니다.

참고