오늘 아침에 재미있게 읽었습니다. Qdrant는 WAL을 durability 보장을 위해 사용합니다. MongoDB는 disk flush하지 않고 success를 반환한다고 합니다.
WAL이 좋은 디자인인지는 모르겠지만 성능이 웬만한 디스크를 사용한다면 괜찮다고 생각이 듭니다.

선행 기록(WAL)의 필요성:

  • WAL은 대부분의 데이터베이스에서 지속성을 위해 중요하지만, 반드시 필요한 것은 아닙니다.
  • 데이터베이스는 WAL 없이도 지속성을 달성할 수 있지만, 이는 좋은 방법은 아닙니다.
  • 이 글은 WAL 없이 지속성을 가진 데이터베이스를 설계하는 과정을 통해 WAL의 필요성을 설명합니다.

지속성(Durability)의 정의:

  • 지속성은 클라이언트가 데이터 시스템에 데이터 쓰기를 요청할 때 제공되는 보장입니다.
  • 요청이 성공하면 데이터가 안전하게 디스크에 기록되고, 실패하면 클라이언트가 재시도하거나 다른 조치를 취해야 합니다.
  • 지속성의 정의는 데이터베이스마다 다를 수 있습니다.

메모리 내 데이터베이스:

  • 메모리 내 데이터베이스는 지속성이 전혀 없습니다.
  • 간단한 의사 코드로 메모리 내 데이터베이스 서비스를 보여줍니다.

디스크에 쓰기:

  • 기본적인 지속성을 위해 데이터베이스를 파일에 쓸 수 있습니다.
  • 의사 코드로 디스크에 쓰는 과정을 보여줍니다.
  • B-tree 알고리즘을 사용하여 효율적으로 데이터를 디스크에 기록하는 방법을 설명합니다.

크래시 안전성 문제:

  • 단순히 디스크에 쓰는 것만으로는 크래시 안전성을 보장할 수 없습니다.
  • 시스템이 재부팅되면 파일에 쓴 데이터가 실제로 디스크에 기록되지 않을 수 있습니다.

fsync의 필요성:

  • 운영 체제는 기본적으로 파일 데이터를 버퍼링합니다.
  • 운영 체제 버퍼를 플러시하지 않고 데이터를 쓰는 것은 지속성이 있다고 간주되지 않습니다.
  • 새로운 데이터베이스가 빠른 삽입 속도를 주장하지만, 실제로 디스크에 데이터를 플러시하지 않는 경우가 종종 있습니다.

지속성의 일반적인 요구사항:

  • 데이터를 디스크의 파일에 쓰는 것뿐만 아니라 fsync(2)를 호출하여 파일을 동기화해야 합니다.
  • fsync는 운영 체제가 버퍼링한 데이터를 디스크에 강제로 플러시합니다.

fsync 실패 처리:

  • fsync 실패를 무시해서는 안 됩니다.
  • 실패 처리 방법은 개발자의 선택이지만, 즉시 종료하고 사용자에게 백업에서 복원하라는 메시지를 표시하는 것이 때로는 허용됩니다.

fsync의 성능 영향:

  • 데이터베이스는 fsync가 느리기 때문에 선호하지 않습니다.
  • 많은 주요 데이터베이스는 클라이언트에 성공을 반환하기 전에 데이터 파일을 fsync하지 않는 모드를 제공합니다.
  • 예: PostgreSQL은 이 안전하지 않은 모드를 제공하지만 기본값으로 사용하지 않고 경고합니다.
  • MongoDB는 기본적으로 이 안전하지 않은 모드를 사용합니다.

성능과 안전성의 트레이드오프:

  • 거의 모든 데이터베이스는 어느 정도 안전성을 희생하여 성능을 얻습니다.
  • 예: 대부분의 데이터베이스는 Serializable Isolation을 기본값으로 사용하지 않습니다.

그룹 커밋(Group commit):

  • fsync의 비용을 분산시키기 위한 방법입니다.
  • 여러 요청의 데이터를 모아서 한 번에 쓰고 fsync를 호출합니다.
  • 예: 5ms마다 한 번씩 디스크에 직렬화하고 fsync를 호출하는 백그라운드 스레드를 사용할 수 있습니다.

MongoDB와의 차이점:

  • 그룹 커밋에서는 write 요청이 fsync를 통해 지속성이 보장될 때까지 기다린 후 성공을 반환합니다.
  • MongoDB는 기본적으로 쓰기가 fsync되기 전에 성공을 반환할 수 있습니다.

지속성의 핵심 아이디어:

  • 클라이언트에게 성공을 반환하기 전에 클라이언트 메시지의 어떤 버전이든 디스크에 fsync를 통해 지속적으로 저장되어야 합니다.

지속성 있는 쓰기 최적화의 필요성:

  • 사용자가 쓰기 작업을 할 때마다 전체 데이터베이스 구조를 디스크에 직렬화하는 것은 비효율적입니다.
  • 이를 개선하기 위한 방법을 소개하고 있습니다.

새로운 접근 방식: 추가 전용 로그(Append-only log):

  • 사용자의 메시지만을 추가 전용 로그에 기록합니다.
  • 전체 B-tree는 주기적으로만 디스크에 기록합니다.
  • 추가 전용 로그 파일이 fsync되면, B-tree가 아직 디스크에 기록되지 않았더라도 사용자에게 안전하게 응답할 수 있습니다.

시작 시 추가 로직:

  • 시스템 시작 시, 디스크에서 B-tree를 읽고 그 위에 로그를 재생합니다.
  • 이렇게 하면 최신 상태의 데이터베이스를 복구할 수 있습니다.

코드 설명:

  • 데이터베이스 파일(kv.db)과 로그 파일(kv.log)을 엽니다.
  • B-tree를 초기화하고 마지막으로 처리된 로그 이후의 모든 로그를 읽어 B-tree에 적용합니다.
  • 그룹 커밋을 위한 백그라운드 작업자를 설정합니다.
  • 주기적으로(5ms마다) 로그를 디스크에 기록하고 fsync합니다.
  • 더 긴 간격(1분마다)으로 전체 B-tree를 디스크에 기록합니다.

쓰기 작업 처리:

  • 메모리 상의 B-tree를 업데이트합니다.
  • 로그 엔트리를 생성하고 그룹 커밋 대기열에 추가합니다.
  • 로그가 디스크에 기록되고 fsync될 때까지 기다립니다.
  • B-tree 전체가 디스크에 기록되기를 기다리지 않고 사용자에게 응답합니다.

선행 기록(Write-Ahead Log, WAL)의 개념:

  • 이 접근 방식이 바로 선행 기록(WAL)입니다.
  • WAL은 데이터베이스 변경사항을 먼저 로그에 기록하고, 나중에 실제 데이터 구조를 업데이트하는 기법입니다.

WAL의 장점 설명:

  • 예: 가장 작은 키와 가장 큰 키를 동시에 쓰는 상황을 고려합니다.
  • B-tree에 직접 쓰면 디스크 상의 서로 다른 위치에 있는 최소 두 개의 페이지를 수정해야 합니다.
  • 하지만 로그에 쓰면, 두 메시지가 같은 로그 페이지에 포함될 가능성이 높습니다.
  • 이는 디스크 I/O를 줄이고 성능을 향상시킵니다.

효율성 증대:

  • 클라이언트 요청을 나타내는 작은 메시지만 디스크에 기록하는 것이 더 효율적입니다.
  • 구조화된 B-tree의 지속성 유지는 덜 빈번한 주기로 수행할 수 있습니다.

파일 시스템과 디스크 버그:

  • 파일 시스템이 때때로 잘못된 위치에 데이터를 쓰는 경우가 있습니다.
  • 디스크가 데이터를 손상시키는 경우도 있습니다.
  • 이러한 문제들은 데이터 무결성에 심각한 위협이 될 수 있습니다.

체크섬(Checksum)을 통한 해결 방안:

  • 체크섬은 데이터의 무결성을 검증하는 방법입니다.
  • 작동 방식: a) 데이터를 쓸 때 체크섬을 계산합니다. b) 계산된 체크섬을 디스크에 저장합니다. c) 데이터를 읽을 때 체크섬을 다시 확인합니다.

스크러빙(Scrubbing):

  • 백그라운드에서 실행되는 프로세스입니다.
  • 읽히지 않은 데이터의 유효성을 검사합니다.
  • 데이터 손상을 빠르게 감지하여 백업에서 복구할 수 있게 합니다.

데이터베이스별 체크섬 사용 현황

a) MongoDB:

  • 기본 스토리지 엔진인 WiredTiger는 기본적으로 체크섬을 사용합니다.

b) PostgreSQL:

  • 기본적으로 데이터 페이지에 대한 체크섬을 사용하지 않습니다.
  • 클러스터 단위로 선택적으로 활성화할 수 있습니다.
  • 활성화 시, 각 데이터 페이지에 체크섬이 포함되어 쓰기 시 업데이트되고 읽기 시 확인됩니다.
  • 내부 데이터 구조와 임시 파일은 체크섬으로 보호되지 않습니다.

c) SQLite:

  • 기본적으로 체크섬을 사용하지 않습니다.
  • 선택적 확장 기능으로 체크섬을 사용할 수 있습니다.
  • 확장 기능 활성화 시, 각 페이지 끝에 8바이트 체크섬이 추가됩니다.
  • 이는 대용량 저장 장치의 무작위 비트 플립으로 인한 데이터베이스 손상을 감지하는 데 도움을 줍니다.

체크섬의 한계:

  • 체크섬만으로는 모든 문제를 해결할 수 없습니다.
  • 디스크나 노드가 완전히 실패하는 경우에는 체크섬으로 대처할 수 없습니다.

더 높은 수준의 내구성을 위한 방안:

  • 여러 디스크나 노드에 걸친 중복성(redundancy)을 도입해야 합니다.
  • 예: 분산 합의(distributed consensus) 알고리즘을 사용하는 방법

--

--