11초의 다운 타임만에 PostgreSQL DB를 마이그레이션 하는 방법
David McDonald님이 영국 정부 서비스 블로그에 기술한 글의 요약/의역본입니다. --- 배경 * Gov UK에는 알림을 담당하는 Notify라는 서비스가 있음 * 기존에는 Gov UK 내부적으로 운영하던 PaaS에서 호스팅 중이었음 * PaaS가 서비스 종료함에 따라 Notify를 AWS로 옮기는 방향으로 결정됨 데이터베이스 * PaaS에 운영되던 데이터베이스는 PaaS쪽 AWS 계정에 생성된 AWS RDS의 PostgreSQL DB고 이를 “소스 데이터베이스”라고 명칭 하겠음 * 우리 (Notify 운영팀)의 AWS 계정에 새로운 PostgreSQL DB를 만들었고 이를 “타겟 데이터베이스”라고 명칭 하겠음 * 소스 데이터베이스는 대략 400GB 크기였고, 13억개의 행, 85개의 테이블, 185개의 인덱스, 120개의 외래 키로 구성되어있고 PostgreSQL 버전 11임 * 평일 기준 평균적으로 초 당 1000개의 insert 또는 update를 처리하고 조회도 거의 비슷한 수치 * Gov UK Notify는 유저들에게 재난 상황 같은 시의적절한 알림을 보내기도 하고 공무 처리 같은 알림을 보내기도 함 * 알림을 보낼 때 데이터베이스와 상호작용 하며 이는 다운 타임을 최소한으로 가져가는 것이 중요함 사용한 서비스 * PaaS 팀에서 제공한 AWS DMS를 사용함 * DMS는 크게 1) 특정 시간까지 전체 데이터를 복제하는 full load와 2) 소스 데이터베이스에서 일어나는 모든 변경을 그대로 반복해서 타겟 데이터베이스로 전달 해주는 replication 방식이 지원됨 * DMS가 지원하는 2가지 방법으로 데이터를 처리한다면, 우리가 신경 써야 할 부분은 연결되어 있는 앱 관리였음 마이그레이션 과정 DMS 설정 * 우선 DMS 설정은 기존 PaaS 계정에 했음. 이유는 이미 PaaS 운영 팀에서 설정해서 사용하고 있던 DMS가 많았고 쉽게 도움을 받았음 * PaaS 계정에 있는 DB와 우리 계정에 있는 DB를 DMS로 연결 시키려면, 두 DB는 서로 다른 VPC에 있었기 때문에 VPC peering이라는 기술을 사용해야 했음 * PaaS 운영 팀의 도움을 받아 인터넷을 통하지 않고 하나의 VPC에서 다른 VPC로 네트워크 연결을 맺을 수 있었음 타겟 데이터베이스 설정 * 우리 AWS 계정에 새로운 PostgreSQL DB를 설정하면서 버전을 15로 올림 * 소스 DB에서 pg_dump로 DB 스키마를 추출해냈음 * DB 테이블 생성 과정에서 인덱스와 외래 키는 설정하지 않았음. 이유는 Full load 과정이 느려질 것이 우려됐고 DMS에서 넘어오는 정보가 순차적으로 들어오지 않아 외래 키 제한이 맞지 않을 것을 알고 있었음 * 차라리 모든 정보를 마이그레이션 한 뒤 인덱스와 외래 키를 추가하는 것이 더 효율적이라고 판단함 Full load * 모든 설정이 끝난 뒤에 소스 데이터베이스에서 타겟 데이터베이스로 데이터를 이전 시킴 * 총 6시간이 걸렸음 * 이후, 인덱스 추가와 외래 키 설정까지 더하는데 추가적으로 3시간이 걸렸음 Replication * Full load 과정이 끝난 뒤, 해당 시점부터 현재까지 소스 데이터베이스에 추가된 데이터도 많았음 * 이를 동기화 하려고 DMS의 replication을 켜두었음 * 이는 소스 데이터베이스에서 일어나는 트랜잭션을 기록한 트랜잭션 로그를 그대로 타겟 데이터베이스에 보내주는 기술 * 몇 시간 만에 둘의 싱크는 거의 같아졌고 이후 모니터링 과정을 가졌음 * 소스 데이터베이스에 들어오는 처리량을 타겟 데이터베이스가 똑같이 잘 처리할 수 있는지 확인하고자 DMS의 replication 설정을 10일 가량 켜두었음 * 이 시점에 사용자들에게 마이그레이션으로 인한 다운 타임에 대한 예고를 미리 공지했음 트래픽 전환 준비 * 이미 수 개월 전, 소스 데이터베이스와 연결된 앱을 어떻게 타겟 데이터베이스로 전환 시킬지에 대해 논의한 적이 있음. * 소스 데이터 베이스와의 연결을 끊는다 (다운 타임 발생) * 타겟 데이터베이스가 해당 시점까지 모든 데이터를 복제한 것을 확인한다 * 앱을 타겟 데이터베이스로 연결한다 (다운 타임 해소) * 다운 타임을 최소화 하는 것과 전환 과정 중 앱들이 서로 다른 데이터베이스로 연결하는 걸 방지하는 것이 중요했음 * 이 모든 과정을 좀 더 수월하게 할 수 있도록 Python 스크립트를 작성했고 우리의 목표는 다운 타임 시간을 5분 이내로 하자 였음 * 가장 서비스가 조용한 토요일 저녁으로 마이그레이션 날짜를 정했음 소스 데이터베이스로 가는 트래픽 차단 * 스크립트에서 pg_terminate_backend를 호출해 연결된 모든 앱을 끊는 방법을 사용했음 * 추가적으로 DB로 연결하는 user의 정보를 바꿔서 앱이 재연결을 시도할 때 권한 없음 에러를 받도록 의도했음 타겟 데이터베이스 복제 여부 확인 * DMS는 타겟 데이터베이스에 현재 replication 상태를 나타내는 값을 계속 업데이트 해주는 테이블을 생성해줌 * 해당 테이블을 참조해서 현재 소스 데이터베이스와 타겟 데이터베이스의 격차를 알 수 있음 * 좀 더 정확하게 하기 위해, 스크립트에서 소스 데이터베이스에 연결된 앱을 끊고 난 후, 새로운 데이터를 삽입하고 해당 데이터가 타겟 데이터베이스에 기록이 되는 것까지 확인했음 트래픽 전환 * 앱에서 데이터베이스를 연결할 때, 사용자 정보와 데이터베이스 주소를 환경 변수로 관리하고 있었음 * 이 말은 즉 새로운 데이터베이스 정보가 입력된 환경 변수로 앱을 다시 배포해야 된다는 뜻이었음 * 하지만, 앱을 재배포하면 최소 5분이 걸렸고 마이그레이션 과정에서 이를 시행할 시 다운타임이 5분 이상이 된다는 뜻이었음 * 그래서 앱을 재배포하는 대신, 사전에 DNS로 트래픽을 전환할 수 있도록 설정해둠 * 우선, 소스와 타겟 데이터베이스에 같은 이름과 패스워드를 가진 유저를 생성했음. 이렇게 하면 새로운 환경변수를 설정해줄 필요가 없음 * 이후, AWS route 53에서 1초 TTL을 가진 레코드를 생성해서 100%의 트래픽을 소스 데이터베이스로 향하게 하고, 0%의 트래픽을 타겟 데이터베이스로 향하게 설정해둠 * 마지막으로, 앱에서 환경변수에 RDS 주소로 직접 연결하기 보다 route 53에서 정의한 주소를 바라보도록 설정함 * 이렇게 하면, 마이그레이션 과정에서 타겟 데이터베이스로 가는 트래픽을 100%로 설정해주고 소스 데이터베이스로 가는 트래픽을 0%로 해주면 트래픽이 전환됨 마이그레이션 당일 * 당일 우린 마지막으로 데이터베이스 동기화 상태를 확인했고 문제가 없다고 판단함 * 스크립트를 돌렸고 결과적으로 11초의 다운 타임만에 성공적으로 데이터베이스 전환을 할 수 있었음 배운 것 * 기존 PaaS 팀에서 잘 지원해줘서 DMS를 사용하긴 했지만, DMS는 불필요한 복잡성을 높이는 도구라고 생각함 * PostgreSQL에서 PostgreSQL 간 마이그레이션 방식은 DMS외에 여러가지가 있었음 * 다음엔 pglogical 같은 대체 툴을 좀 더 연구해볼 것 --- 원글: https://gds.blog.gov.uk/2024/01/17/how-we-migrated-our-postgresql-database-with-11-seconds-downtime/