본문은 [웹 프로그래머를 위한 데이터베이스를 지탱하는 기술]를 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.
1. 데이터베이스는 어떤 때에 크래쉬되는가?
일반 PC와 같이 데이터베이스 서버에서도 장애가 일어날 수 있는데, 그 영향이 매우 크므로 장애에 대한 대비가 필요하다.
1) 전형적인 장애 시나리오
- 장애 대책으로서는 먼저 어떤 장애 패턴이 있는지를 정리하여 각각에 대해 적절한 대책을 마련하는 것이 중요하다
- 대표적인 장애 패턴에는 다음과 같은 것이 있다
소프트웨어 장애
- MySQL, Oracle 등 소프트웨어 주변의 결함으로 인해 서비스를 제공할 수 없는 유형의 장애다
- 문제의 심각성에 따라 데이터 자체가 파괴되어 다시 시작되지 않는 등 치명상이 될 수도 있다
- 소프트웨어 장애는 그 소프트웨어의 품질이 장애 빈도와 심각성에 결정적인 영향을 미친다
- 오픈 소스/상용 제품에 관계없이 실적이 있고 노하우도 갖춘 제품을 사용하는 것이 중요하다
- 기능과 성능뿐만 아니라 품질도 중요한 지표이다
OS 장애
- Windows의 블루 스크린 또는 Linux 커널 패닉과 같은 OS 주변의 장애로 인하여 서비스를 제공할 수 없게 되는 패턴이다
- 문제 해결에는 OS 및 하드웨어 주변의 고급 기술이 요구되므로 RDBMS와 같은 미들웨어 장애와는 또 다른 어려움이 존재한다
- 장치 드라이버의 버그가 발생 빈도가 높은 문제이므로 최신 드라이버 업데이트가 중요하다
- 또한, 어느 정도 검증된 기술을 사용하는 것도 중요하다
하드웨어 장애
- HDD와 같이 물리적으로 작동하는 것은 한 개당 수명이 2~3년 정도로 그다지 길지 않다
- 따라서 사전조치를 잘 취해놓았다고 하더라도 하드웨어 장애를 피하는 것은 쉽지 않다
조작 실수
- 잘못해서 테이블을 지워 버리는 식의 조작 실수에 의한 장애가 많다
- 물론 방지할 수 있으면 방지하는 것이 좋겠지만, 많은 사람들이 데이터베이스를 조작할수록 이런 문제가 일어날 확률이 높아진다
2) 디스크 이중화로 데이터 손실 방지하기
- 지금까지 언급한 문제를 발생시키지 않는 것이 최선이겠지만, 현실적으로 발생 확률을 제로로 하는 것은 불가능하다
- 물론 확률을 줄이는 노력은 필요하나, 서비스를 운영하는 관점에서 보면 장애가 발생해도 서비스를 제공할 수 있도록 설계하는 것에 중점을 두어야 한다
- 데이터를 잃어 버리면 서버가 복구되었더라 하더라도 서비스를 지속할 수 없으므로 데이터 손실을 막기 위한 대책이 결정적으로 중요하다
RAID
- 현재의 데이터베이스의 데이터는 HDD에 저장되는 것이 보통이지만, 불행히도 HDD는 하드웨어 중에서 가장 고장률이 높은 부품이다
- 따라서 하나의 서버에 여러 개의 HDD를 탑재하고 동일한 데이터를 두 개 이상의 HDD에 분산시키는 기술이 사용되는데, 이를 RAID라고 한다
- 기준은 용량에 있으면 RAID1, 여유가 없는 경우는 RAID5를 사용하는 것이 일반적이다
- RAID의 구성에 따라 다르지만, 한 개가 망가진 상태에서 방치하면 성능이 크게 저하될 수 있고, 그대로 두 번째가 손상되면 데이터의 손실의 위험이 있다
- 그래서 두 번째가 손상된 경우에 서비스를 멈추지 않고 망가진 HDD와 새로운 HDD를 교체하여 복구하는 핫 스왑이라는 기술도 병용된다
서버 이중화에 의한 다운 타임 줄이기
- 실제로는 RAID를 사용하는 것만으로는 서비스를 운영하는 데 충분하지 않다
- CPU 고장, 커널 패닉, 데이터베이스 프로세스 장애 등 장애의 원인이 되는 것은 디스크만이 아니기 때문이다
- RAID 구성을 짜고 있다 해도 서버가 하나밖에 없으면 시스템은 즉시 다운된다
- 따라서 서버는 두 대 이상 필요하다
2.복제
웹 서비스를 중심으로 현재 가장 널리 사용되고 있는 중복화 방식이 복제(replication)다. 복제는 문자 그대로 레플리카(복제본)를 다른 서버에 생성하는 기술로, 하나의 서버가 다운되더라도 나머지 서버에 동일한 데이터가 있으므로 서비스를 계속 할 수 있다. 여기서는 MySQL에 도입되어 있는 것을 중심으로 소개한다.
1) 단방향 복제
단방향 비동기
- 단방향/비동기 복제는 그림 5-1와 같이 마스터에서 갱신한 결과가 슬레이브에 비동기로 전파하는 유형의 복제다
- 이는 MySQL에서 표준으로 사용되는 복제 기능이다
- MySQL에서는 마스터에 실행한 갱신계의 SQL 문이 바이너리 로그라는 전용 로그 파일로 기록된다
- 이 로그 파일의 내용이 슬레이브로 전송되어 저장된다
- 슬레이브는 저장된 로그 파일을 순차적으로 실행함으로써 마스터와 슬레이브의 상태가 일치되는 구조가 된다
- 슬레이브에는 바이너리 로그 수신과 바이너리 로그 실행이라는 2단계로 구성되어 있다
- 전자는 I/O 스레드, 후자는 SQL 스레드에 의해 비동기적으로 실행된다
- 디스크에 비해 네트워크에서 병목현상이 발생하는 일은 적으므로 수신은 거의 동기에 가까운 속도로 진행된다
- 그러나 바이너리 로그에 기록되어 있는 쿼리의 실행은 테이블에 액세스하여 내용을 변경할 수 있다는 성질상 디스크가 병목현상을 일으키면 처리가 늦어지는 경향이 있다
- 마스터가 장애를 일으킨 경우 슬레이브에서는 지금까지의 업데이트 결과가 반영되지 않을 수 있는데, 이 반영되지 않은 상황은 다음의 두가지 패턴이다
① 마스터에서 생성한 바이너리 로그가 슬레이브에서는 마지막까지 수신되지 않은 상황
- 이는 슬레이브의 I/O 스레드가 비동기이기 때문에 발생할 수 있는 현상이다
- 마스터가 죽으면 최근의 바이너리 로그를 전송할 수 있는 서버가 없으므로 슬레이브가 수신한 최종 결과와 마스터의 최종 결과 사이의 데이터가 손실될 수 있다
- 다만 MySQL이 크래쉬되었을 뿐 OS는 살아있는 경우 SSH 접속하여 바이너리 로그를 가져오는 구제 조치도 가능하므로 실제 그러한 처리를 하는 경우도 있다
② 슬레이브에서의 바이너리 로그의 실행이 마지막까지 종료되지 않은 상황
- 이는 슬레이브의 SQL 스레드가 지연되어 발생하는 현상이다
- 슬레이브는 바이너리 로그를 받을 수 있기 때문에 마스터가 죽어 있어도 슬레이브에 저장된 바이너리 로그를 사용하여 지연의 해소가 가능하다
- 한편 마스터가 죽었을 경우 애플리케이션은 죽은 마스터 대신에 슬레이브를 새로운 마스터로 보고 업데이트를 하게 될 것이다
- 이때 마스터가 죽었다고 해서 슬레이브로 갑자기 업데이트를 걸어 버리게 되면 슬레이브에서는 일부 업데이트가 끝나지 않은 상황이므로 불일치가 생길 위험이 있다
- 따라서 제대로 복구를 하기 위해서는 슬레이브가 바이너리 로그를 마지막까지 실행한 것을 확인하고 나서 업데이트 트래픽을 옮길 필요가 있다
단방향/준동기화
- MySQL의 경우에는 ①슬레이브가 바이너리 로그를 수신, ②바이너리 로그의 내용을 실행이라는 두 개의 단계로 나뉘어 있으며, 그 모두가 비동기라고 설명했다
- 새로운 버전의 MySQL(10년 전 기준)에서는 이 중 ①의 부분을 동기화 방식으로 할 수 있는데, 이를 준동기식 복제(semi-synchronous replication)라고 한다
- ①이 동기 방식이 됨으로써 클라이언트에서 응답을 받았을 때 그 업데이트가 슬레이브에 도착해 있는 것이 보증된다
- 이에 따라 마스터 손실에 의한 데이터 손실의 위험을 되도록 억제할 수 있다
- 반면 마스터 -> 슬레이브 간의 바이너리 로그의 교환을 기다리는 동안만큼 응답 시간이 나빠진다는 단점도 있다
단방향/동기
- MySQL에서는 기능으로서 구현되어 있지 않지만, 슬레이브에 대해 업데이트 결과의 반영까지 마친 상태에서 처음으로 클라이언트에 응답을 반환하는 방식도 생각할 수 있다
- 동기 복제는 슬레이브에서도 업데이트 결과가 반영되어 있으므로 즉시 슬레이브에서 서비스를 재개할 수 있다
2. 양방향 복제
- 지금까지 소개한 복제 구성에서 마스터 -> 슬레이브는 단방향이라는 제약이 있으므로 업데이트는 마스터에서만 할 수 있었다
- 또한 대부분의 경우에서 슬레이브는 단일 스레드로 복제를 담당하게 되어 있으므로 갱신의 동시성이 없다
- 대신 마스터를 두 개 이상 갖게 하고 각각의 마스터를 업데이트할 수 있도록 한 멀티 마스터(양방향 복제)라는 구성도 생각할 수 있다
양방향 복제는 기술적으로 어렵다
- 성능 면에서는 양방향 복제 쪽이 우수하지만, 그 구현이 단방향 복제보다 다소 어려운 측면이 있다
- 특히 "업데이트가 서로 충돌하면 어떻게 할 것인가"라는 문제가 중요하다
- 그림 5-2와 같이 DB 서버 A와 DB 서버 B에서 각각 업데이트가 동시에 발생되면 데이터의 불일치가 발생할 수 있으므로 이를 방지하기 위한 분산형 배타 제어의 구조가 필요하다
- MySQL과 같이 비동기/준동기 복제를 기반으로 한 제품에서는 이런 배타 제어를 실현하는 것이 어렵다
- 그래서 동일 ID에 대한 업데이트가 여러 서버에서 모두 일어나지 않도록 애플리케이션 로직을 제어하는 것이 필요하다
- 이에 대해 데이터베이스 제품에 따라서는 여러 서버에 각각 업데이트를 할 수 있으며, 자동 동기화되는 구조를 가진 것이 있다
- 오픈 소스의 경우 MySQL Cluster라는 제품이 이러한 양방향 복제의 구조를 가지고 있다
2) 장애로부터의 복구 방법
- 슬레이브 하드웨어 오류로 인해 해당 서버의 데이터를 소실했을 경우, 다른 살아있는 슬레이브 또는 마스터에서 데이터를 복원하거나 정기적으로 백업을 받았다면 그것으로 되돌릴 수밖에 없다
- 한편, OS 장애 등에 의해 일시적으로 사용할 수 없게 되었으나 데이터 자체는 디스크에 남아있는 유형의 장애도 있다
- 운용의 설정이 적절하지 않으면 재시작하더라도 필요한 데이터가 남아있지 않아 복제를 재개할 수 없거나 불일치를 일으키게 된다
3) 인위적 실수에 대한 해결
- 테이블을 실수로 지워버리는 등 인위적 실수가 발생할 수도 있다
- 복제와 같은 실시간 이중화 기술은 인위적 실수에 강하지 않다
- 마스터에서 지워 버린 테이블은 슬레이브에 즉시 반영되어 슬레이브가 같이 지워져 버리기 때문이다
- 인위적 실수는 일으키지 않는 것이 가장 좋지만, 그래도 발생하는 일이 종종 있으므로 그때를 대비하여 백업을 해두어야 한다
- 백업을 정기적으로 해두면 모든 DB 서버에서 데이터가 손실된 경우라 할지라도 백업 시점으로 복구할 수 있다
4) 백업을 복원한 후 어떻게 하면 좋은가?
- 백업을 해두었다면 적어도 백업 시점으로 복구할 수 있지만, 그것만으로는 부족하다
- 백업이란 전체 데이터베이스를 복사하는 무거운 작업이므로 대개는 새벽 4시 등 부하가 적은 시간대에 하루에 한 번만 실행하는 방법으로 백업을 수행한다
- 그렇다면 마지막 백업으로부터 시간이 경과한 후에 데이터가 손실되면 백업 이후에 업데이트된 결과는 어떻게 될까?
- 데이터베이스는 시점 복구(Point In Time Recovery, PITR) 기능이 문제를 해결해 주고 있다
- MySQL에서는 UPDATE문 등에 의한 업데이트 결과가 바이너리 로그라고 불리는 업데이트 로그에 기록되고 있다
- 마지막 백업 이후의 업데이트 로그가 성공적이면 마지막 백업 복구 이후 업데이트 로그를 순차 적용하여 장애 이전 상태로 복구할 수 있다
- 제대로 동작시키려면 업데이트 로그의 어느 위치가 마지막 백업 시점인가를 파악해야 한다
- 가장 쉬운 방법으로는 일시적으로 업데이트를 멈추고 백업을 하여 그 시점에서의 업데이트 로그의 위치를 특정하는 방법이 있다
- 그러나 이 방법은 백업 중에 업데이트가 불가하므로 데이터 양이 크면 장시간 업데이트가 멈추게 된다
- 이 문제에 대해 주요 RDBMS는 트랜잭션 구조를 응용함으로써 업데이트를 멈추지 않고 백업을 하는 온라인 백업 기능을 제공한다
- 온라인 백업 + PITR 기능은 만일의 운영 실수와 같은 예상치 못한 사태로부터 복구하는 데 꼭 필요한 기능이다
- 데이터베이스로 RDBMS를 대신해 NoSQL과 같은 간단한 데이터베이스를 사용하는 경우에는 이러한 기능이 없는 것도 많으니 주의해야 한다
5) 고의로 지연시킨 복제
- 비동기 복제를 응용하여 인위적 실수에 대비하는 솔루션도 있는데, 이를 Time Delayed Replication이라고 한다
- 이는 마스터에서 수행한 업데이트를 즉시 슬레이브에 반영하지 않고, 1시간 등 어느 정도의 시간이 지난 후에 반영하는 방식이다
- 만약 마스터에서 잘못된 작업을 수행하는 경우는 그 지연되고 있는 슬레이브를 마스터로 승격하게 된다
- 이 작업만 한다면 1시간만큼의 업데이트 결과가 반영되지 않은 상태이므로 원래의 마스터에 있는 업데이트 로그 중에서 문제가 있는 쿼리만을 제거하여 별도로 반영하는 수법을 취할 수 있을 것이다
- 단, 이 방법은 대상 슬레이브가 항상 지연된 상태이므로 애플리케이션에서의 참조 쿼리를 돌릴 수 없다는 점에 주의해야 한다
'책 > 웹 프로그래머를 위한 데이터베이스를 지탱하는 기술' 카테고리의 다른 글
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 6장: 트랜잭션과 무결성ㆍ무정지성 (1) | 2023.01.04 |
---|---|
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 4장: SQL 문의 특징과 이를 잘 다루는 법 (0) | 2022.12.06 |
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 3장: 테이블 설계와 릴레이션 (0) | 2022.11.29 |
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 2장: 인덱스로 고속 액세스 실현하기 (0) | 2022.11.11 |
[웹 프로그래머를 위한 데이터베이스를 지탱하는 기술] 1장: 데이터베이스가 없으면 무엇이 곤란한가? (0) | 2022.11.09 |