fsync()와 데이터 손실

심규민·2025년 1월 29일
post-thumbnail

데이터베이스 인터널스를 읽던 중 "fsync() 사용할 때 발생할 때 데이터 손실이 발생할 수 있습니다"를 보고 "O_DIRECT를 왜 사용하지 않을까, 그리고 어떻게 데이터 손실을 방지할 수 있을까" 생각이 들어 정리하게 됐습니다.

더티 페이지 관리와 fsync()

MySQL이 디스크로 데이터를 저장하는 주요 시나리오는 다음과 같습니다.

  1. 버퍼 풀의 더티 페이지를 디스크로 반영할 때
    • InnoDB 버퍼 풀에서 수정된 데이터 페이지(더티 페이지)를 체크 포인트를 통해 주기적으로 디스크에 동기화
  2. WAL(Write Ahead Log)를 디스크에 저장할 때
    • 트랜잭션 커밋 시 Redo 로그를 먼저 디스크에 기록하여 ACID 속성 보장

이 두 작업 모두 운영 체제의 fsync() 시스템 호출을 통해 이뤄집니다.
그렇다면 fsync()는 정확히 어떤 역할을 하며, 왜 데이터 손실 위험이 있을까요?

fsync()의 역할과 한계

fsync()란?

fsync()운영 체제의 페이지 캐시(버퍼 풀)에 있는 데이터를 디스크로 동기화하는 POSIX 표준 시스템 호출입니다. 데이터베이스에서 데이터 무결성과 지속성을 보장하기 위해 fsync()를 사용합니다.

fsync() 실패 시 발생할 수 있는 문제

데이터베이스에서 fsync()를 호출할 때 특정 상황에서 실패할 수 있습니다.

fsync()가 실패하는 흔한 원인으로는 디스크 공간 부족, 물리적 디스크 결함, SAN(Storage Area Network)의 연결 문제 등이 있습니다.

위 문제가 발생하면 다음과 같은 데이터 손상 시나리오가 발생할 수 있습니다.

  1. 더티 페이지의 살못된 상태 전환
    • fsync() 호출이 실패로 버퍼 풀에서 디스크로 데이터가 기록되지 않았지만, 더티 페이지의 상태가 "클린" 상태로 전환되어 데이터가 유실될 수 있음(다시 디스크로 반영되지 않기 때문임)
  2. WAL 불일치
    • WAL을 디스크에 정상적으로 기록하지 못하면, 특정 시점 이후 로그가 손상되어 데이터베이스 복구가 불가능해질 수 있습니다.

O_DIRECT 대 fsync()

O_DIRECT란?

O_DIRECT는 애플리케이션이 OS 페이지 캐시(버퍼 풀)를 우회하여 디스크에 직접 데이터를 쓰도록 강제하는 옵션입니다. 해당 옵션을 사용하면 다음과 같은 특징이 있습니다.

  1. 더블 버퍼링 제거
    • 애플리케이션 버퍼와 OS 페이지 캐시에 동일한 데이터가 중복 저장되는 문제를 없애, 메모리 사용량을 줄일 수 있습니다.
  2. 성능 이점
    • 대규모 순차 쓰기/읽기 시에는 OS 캐시를 거치지 않아 오버헤드를 낮출 수 있습니다.
  3. 엄격한 정렬 요구
    • 읽기/쓰기할 데이터가 디스크 블록 크기(보통 4KB)에 맞춰 정렬되어야 하며, 이를 만족하지 못하면 암묵적 버퍼링이 발생하여 앞선 이점이 사라집니다.

데이터베이스에서 O_DIRECT를 쓰지 않는 이유

그렇다면 MySLQ에서는 fsync()를 사용함으로써 데이터 손실 위험이 있을 수 있음에도 불구하고, O_DIRECT를 기본으로 사용하지 않는 이유는 크게 세 가지가 있습니다.

  1. 운영 체제 및 파일 시스템 호환성
    • fsync()는 POSIX 표준이라 대부분의 운영 체제와 파일 시스템에서 지원되지만, O_DIRECT는 특정 환경에서만 완전히 동작합니다.
    • 운영 체제마다 구현 방식이 다르고, 일부 파일 시스템(ext4, XFS 등)에서는 O_DIRECT가 올바르게 지원되지 않아 이식성안전성에 제약이 생깁니다.
  2. 성능 최적화
    • MySQL은 버퍼 풀을 사용하여 데이터 읽기/쓰기를 최적화합니다.
    • O_DIRECT는 OS 캐시를 우회하므로 더블 버퍼링 문제를 해결할 수 있지만, 랜덤 I/O가 많은 워크로드에서 버퍼 풀 캐시 효과를 활용하지 못해 오히려 성능이 저하될 수 있습니다.
  3. 구현 복잡성과 안정성
    • O_DIRECT를 사용할 경우, 디스크 블록 크기(대개 4KB)로 데이터 정렬이 필요한 등 엄격한 요구사항이 늘어납니다.
    • 일부 파일 시스템은 O_DIRECT와 메타데이터 동기화를 제대로 처리하지 못해 데이터 손실이 발생할 수 있습니다.

트레이드 오프

결국 데이터베이스에서 fsync()를 사용하는 이유는 다음과 같은 트레이드오프 때문이라고 생각합니다.

  1. 호환성과 안전성
    • POSIX 표준인 fsync()는 대부분의 운영 체제와 파일 시스템에서 안정적으로 지원
    • O_DIRECT는 더블 버퍼링을 개선해주지만, 일부 파일 시스템에서 데이터 손실이 발생할 수 있음
  2. 워크로드 특성
    • 랜덤 I/O가 많은 일반적인 OLTP 환경에서는 버퍼 풀 캐시를 활용하는 fsync() 방식이 높은 성능을 유지할 수 있습니다.
    • 대규모 순차 쓰기/읽기 위주의 특수한 상황에서는 O_DIRECT가 오히려 성능상 이점을 줄 수 있습니다.
  3. 유지보수성과 구현 복잡성
    • fsync()는 운영 체제 내부의 캐시를 활용하며, 개발작 맞춰야할 정렬이나 블록 크기 요구사항이 상대적으로 적습니다.
    • O_DIRECT 사용 시에는 I/O 크기와 메모리 정렬 문제 등이 추가로 발생하여 유지보수 부담이 늘어납니다.

결국 MySQL이 기본적으로 fsync()를 선택하는 것은, 호환성과 안전성을 우선시하는 데서 비롯된 결정이라고 할 수 있습니다. fsync() 자체가 완벽하지 않더라도, 다양한 운영 체제와 파일 시스템에서 일관된 동작을 제공하고, 대부분의 워크로드에 잘 맞는 성능을 제공하기 때문입니다.

그렇다면 처음으로 돌아가 fsync() 실패가 발생할 경우 발생할 수 있는 데이터 손실을 방지하려면 어떻게 해야할까요?

fsync() 실패와 대응 전략

고가용성 기반 대응 전략

동기식 복제(Sync Replication)

fsync() 실패로 인해 한 노드에서 데이터 손실이 발생하더라도, 다른 노드에서 데이터를 안전하게 보관하기 위해 동기식 복제를 활용할 수 있습니다.

동기식 복제는 트랜잭션 로그(WAL, Redo 로그)를 다른 노드와 실시간으로 동기화하여, 주요 데이터를 여러 위치에 동시에 저장합니다.

예를들면 PostgreSQL의 동기식 스트리밍 복제나 MySQL의 Group Replication을 사용할 수 있습니다.

자동 페일오버(Failover)

동기식 복제와 함께 자동 페일오버를 구성하면, 주요 노드에서 장애가 발생했을 때 대체 노드(Standby 노드)로 자동 전환하여 서비스 중단을 최소화할 수 있습니다. 이를 통해 Master 노드에서 fsync() 실패로 인한 서비스 중단을 최소화하여 시스템을 안정적으로 운영할 수 있습니다.

데이터 복구

정기 백업 및 WAL 기반 복구(Point-in-Time Recovery)

데이터 손실 위험을 줄이기 위해서는 정기적인 백업과 WAL 기반 복구를 활용해야 합니다.

  • 정기적인 물리적/논리적 백업은 장애 발생 시 데이터를 복원할 수 있는 기본적인 안전망 역할을 합니다.
  • WAL 기반 복구는 특정 시점으로 데이터를 복원할 수 있어 fsync() 실패와 같은 문제로 인한 데이터 손실을 최소하할 수 있습니다.

하지만, 정기 백업의 경우 백업 시점과 장애 시점간 시간 차이가 발생할 수 밖에 없기에 데이터 손실이 발생할 수 밖에 없습니다. 따라서 다른 대응 전략을 함께 사용하는 것이 좋습니다.

결론

MySQL과 같은 데이터베이스는 데이터 손실이 발생할 수 있음에도 fsync()를 사용하는 이유는 호환성과 안전성이 우선되기 때문입니다. 하지만 데이터 손실이 여전히 발생할 수 있기 때문에, 해당 문제를 보완하기 위해 동기식 복제, 자동 페일오버와 같은 고가용성 전략, 그리고 정기 백업, WAL 복구같은 방법을 함께 사용해야 합니다.

0개의 댓글