데이터베이스 인터널스를 읽던 중 "fsync() 사용할 때 발생할 때 데이터 손실이 발생할 수 있습니다"를 보고 "O_DIRECT를 왜 사용하지 않을까, 그리고 어떻게 데이터 손실을 방지할 수 있을까" 생각이 들어 정리하게 됐습니다.
MySQL이 디스크로 데이터를 저장하는 주요 시나리오는 다음과 같습니다.
이 두 작업 모두 운영 체제의 fsync() 시스템 호출을 통해 이뤄집니다.
그렇다면 fsync()는 정확히 어떤 역할을 하며, 왜 데이터 손실 위험이 있을까요?
fsync()는 운영 체제의 페이지 캐시(버퍼 풀)에 있는 데이터를 디스크로 동기화하는 POSIX 표준 시스템 호출입니다. 데이터베이스에서 데이터 무결성과 지속성을 보장하기 위해 fsync()를 사용합니다.
데이터베이스에서 fsync()를 호출할 때 특정 상황에서 실패할 수 있습니다.
fsync()가 실패하는 흔한 원인으로는 디스크 공간 부족, 물리적 디스크 결함, SAN(Storage Area Network)의 연결 문제 등이 있습니다.
위 문제가 발생하면 다음과 같은 데이터 손상 시나리오가 발생할 수 있습니다.
fsync() 호출이 실패로 버퍼 풀에서 디스크로 데이터가 기록되지 않았지만, 더티 페이지의 상태가 "클린" 상태로 전환되어 데이터가 유실될 수 있음(다시 디스크로 반영되지 않기 때문임)O_DIRECT는 애플리케이션이 OS 페이지 캐시(버퍼 풀)를 우회하여 디스크에 직접 데이터를 쓰도록 강제하는 옵션입니다. 해당 옵션을 사용하면 다음과 같은 특징이 있습니다.
그렇다면 MySLQ에서는 fsync()를 사용함으로써 데이터 손실 위험이 있을 수 있음에도 불구하고, O_DIRECT를 기본으로 사용하지 않는 이유는 크게 세 가지가 있습니다.
fsync()는 POSIX 표준이라 대부분의 운영 체제와 파일 시스템에서 지원되지만, O_DIRECT는 특정 환경에서만 완전히 동작합니다.O_DIRECT가 올바르게 지원되지 않아 이식성과 안전성에 제약이 생깁니다.O_DIRECT는 OS 캐시를 우회하므로 더블 버퍼링 문제를 해결할 수 있지만, 랜덤 I/O가 많은 워크로드에서 버퍼 풀 캐시 효과를 활용하지 못해 오히려 성능이 저하될 수 있습니다.O_DIRECT를 사용할 경우, 디스크 블록 크기(대개 4KB)로 데이터 정렬이 필요한 등 엄격한 요구사항이 늘어납니다.O_DIRECT와 메타데이터 동기화를 제대로 처리하지 못해 데이터 손실이 발생할 수 있습니다.결국 데이터베이스에서 fsync()를 사용하는 이유는 다음과 같은 트레이드오프 때문이라고 생각합니다.
fsync()는 대부분의 운영 체제와 파일 시스템에서 안정적으로 지원O_DIRECT는 더블 버퍼링을 개선해주지만, 일부 파일 시스템에서 데이터 손실이 발생할 수 있음O_DIRECT가 오히려 성능상 이점을 줄 수 있습니다.fsync()는 운영 체제 내부의 캐시를 활용하며, 개발작 맞춰야할 정렬이나 블록 크기 요구사항이 상대적으로 적습니다.O_DIRECT 사용 시에는 I/O 크기와 메모리 정렬 문제 등이 추가로 발생하여 유지보수 부담이 늘어납니다.결국 MySQL이 기본적으로 fsync()를 선택하는 것은, 호환성과 안전성을 우선시하는 데서 비롯된 결정이라고 할 수 있습니다. fsync() 자체가 완벽하지 않더라도, 다양한 운영 체제와 파일 시스템에서 일관된 동작을 제공하고, 대부분의 워크로드에 잘 맞는 성능을 제공하기 때문입니다.
그렇다면 처음으로 돌아가 fsync() 실패가 발생할 경우 발생할 수 있는 데이터 손실을 방지하려면 어떻게 해야할까요?
fsync() 실패로 인해 한 노드에서 데이터 손실이 발생하더라도, 다른 노드에서 데이터를 안전하게 보관하기 위해 동기식 복제를 활용할 수 있습니다.
동기식 복제는 트랜잭션 로그(WAL, Redo 로그)를 다른 노드와 실시간으로 동기화하여, 주요 데이터를 여러 위치에 동시에 저장합니다.
예를들면 PostgreSQL의 동기식 스트리밍 복제나 MySQL의 Group Replication을 사용할 수 있습니다.
동기식 복제와 함께 자동 페일오버를 구성하면, 주요 노드에서 장애가 발생했을 때 대체 노드(Standby 노드)로 자동 전환하여 서비스 중단을 최소화할 수 있습니다. 이를 통해 Master 노드에서 fsync() 실패로 인한 서비스 중단을 최소화하여 시스템을 안정적으로 운영할 수 있습니다.
데이터 손실 위험을 줄이기 위해서는 정기적인 백업과 WAL 기반 복구를 활용해야 합니다.
fsync() 실패와 같은 문제로 인한 데이터 손실을 최소하할 수 있습니다.하지만, 정기 백업의 경우 백업 시점과 장애 시점간 시간 차이가 발생할 수 밖에 없기에 데이터 손실이 발생할 수 밖에 없습니다. 따라서 다른 대응 전략을 함께 사용하는 것이 좋습니다.
MySQL과 같은 데이터베이스는 데이터 손실이 발생할 수 있음에도 fsync()를 사용하는 이유는 호환성과 안전성이 우선되기 때문입니다. 하지만 데이터 손실이 여전히 발생할 수 있기 때문에, 해당 문제를 보완하기 위해 동기식 복제, 자동 페일오버와 같은 고가용성 전략, 그리고 정기 백업, WAL 복구같은 방법을 함께 사용해야 합니다.