DWB 최종 정리

개발새발·2022년 1월 14일
0
post-thumbnail

DWB (Double Write Buffer)

최종 발표

1. Database Volume 구조

CUBRID 데이터베이스의 볼륨은 크게 영구적 볼륨, 일시적 볼륨, 백업 볼륨으로 분류한다.

영구적 볼륨은 영구적 데이터를 저장하지만 일시적 데이터도 저장할 수 있는 데이터 볼륨도 있다.

  • 사용자는 일시적 데이터를 저장할 일부 영구적 볼륨을 명시적으로 할당할 수 있다.
  • 이러한 볼륨은 절대 제거되지 않는다는 점에서는 영구적이지만 일시적 볼륨과 비슷하게 동작한다.
  • DWB 파일이 여기에 해당한다.

DWB 파일은 flush 대상 페이지를 먼저 쓰기 위한 이중 쓰기 버퍼(Double Write Buffer) 저장 파일이다.

2. DWB 파일

DWB 파일은 부분 쓰기(Partial write)로 인한 I/O 에러 방지를 위하여 disk에 쓰여지는 데이터 페이지들의 복사본을 저장하는 공간이다.

  • 데이터 파일에 쓰기 전에 Buffer pool로 flush된 페이지를 쓰는 저장영역이다.
  • 모든 데이터 페이지는 DWB에 먼저 쓰여지고 난 후에 영구 데이터 볼륨에 있는 데이터 위치에 쓰여진다.
  • 데이터베이스가 재시작될 때, 부분적으로 쓰여진(Partial write) 페이지들이 탐지되고 DWB에서 대응되는 페이지로 대치된다.
  • 파일 크기는 cubrid.conf의 double_write_buffer_size에 의해 결정된다.
    • 만약 0으로 설정되었다면, DWB는 사용되지 않고 DWB 파일도 생성되지 않는다.

Flush : Memory에 있는 data를 disk에 쓰고 동기화하는 행위
Partial write : OS의 file I/O 단위인 block과 DB 시스템의 page 단위의 크기가 같지 않기 때문에 발생 가능한 문제 (일반적으로 DB page의 크기가 더 큼)
Buffer pool : Memory에 있는 data를 page로 관리하는 data cache 영역

3. DWB 기능

  • DB의 page는 16KB가 기본 사이즈다. 즉, DB단의 I/O 작업은 page가 기본단위이다. 하지만 OS단의 I/O 작업은 기본적으로 4KB가 된다. DB단의 논리적인 I/O 단위와 OS단의 실질적인 I/O 단위가 다름으로 인해 발생할 수 있는 page corrupt를 방지하기 위해 사용하는 기능이다.
  • Buffer pool에서 변경된 page의 내용이 disk로 내려 써지는 경우, 그 내용을 DWB에 기록한다. 이 때, 기록하는 내용은 해당되는 page 정보와 operation 정보가 기록된다. 즉, flush 이벤트가 발생하는 경우, 먼저 DWB에 기록하고, 그 다음에 대상 페이지의 내용을 변경하는 작업을 진행한다. 이 때, 대상 page의 변경 작업은 fsync()를 호출해서 진행하게 되는데, OS 상의 작업은 4KB로 나누어서 작업이 발생하게 되고, 시스템 설정에 따라 cache에 미리 저장되기도 한다. 이 때, 작업을 다 완료하기 전 특정 page의 일부분만 I/O 작업이 완료되고, 특정 page의 다른 부분은 아직 완료되지 않은 경우에 system crash가 발생하면 page가 깨질 수 있다. 이런 경우 recovery가 진행되는데, 이 때, DWB에 기록된 정보를 기준으로 page를 복구하게 된다.
  • 비록 데이터는 두 번 쓰여지지만, I/O 오버헤드나 I/O 작업이 두 배나 필요하진 않다.
    • 데이터는 DWB에 OS에서 단일 fsync()라고 불리는 기능을 통해 하나의 큰 덩어리 단위로 순차적으로 쓰여지기 때문이다.

fsync() : 프로세스가 파일에 쓰기 작업을 요청하면 운영체제가 요청을 수행하게 되는데 시스템에서 효율을 위해 쓰기 요청을 버퍼에 기록해두었다가 처리한다. (쓰기 요청한 시간 != 실제 디스크에 쓰여지는 시간) fsync()는 kernel에 저장된 buffer를 무조건 disk에 write하게 하는 함수 (data, file의 meta data를 함께 동기화)

4. DWB 개요

  • DB 시스템은 Page Replace를 위한 강제적 / 주기적 flush 작업을 진행한다.
  • Flush는 DWB를 사용하지 않고 진행하거나 DWB를 사용하여 진행할 수 있다.
  • DWB를 사용하는 경우 system crash가 발생해서 일어난 partial write에 대해 page recovery가 가능하다.

DB 시스템이 DWB를 사용하는 방식

  • Page를 저장할 공간 탐색 및 저장
  • 특정 개수의 page를 disk로 한번에 flush
  • Partial write가 일어난 DB 복구

Slot Hash Entry : Page Replacement에서 cache 역할을 담당 / Memory에 찾으려는 page가 없다면 DWB에서 찾는다. (key, value 값으로 저장)

5. DWB 구조

DWB라 함은 메모리에 할당되어 있는 DWB block들을 의미하며 block들은 slot들로 구성되어 있다.

DWB volume은 disk에 저장되어 있는 DWB의 단일 블록으로 DB에 flush 하려는 DWB block을 먼저 disk에 flush한 block이다.

dwb_Global

storage/double_write_buffer.c: 306

/* DWB */
static DOUBLE_WRITE_BUFFER dwb_Global;
typedef struct double_write_buffer DOUBLE_WRITE_BUFFER;
struct double_write_buffer
{
	unsigned int num_blocks;
	// 블록의 전체 개수

	unsigned int num_pages;
	// 페이지의 전체 개수

	unsigned int num_block_pages;
	// 블록당 페이지 수

	unsigned int log2_num_block_pages;
	// log2(블록당 페이지 수)

	pthread_mutex_t mutex;
	// wait queue 변경 시 사용될 lock

	DWB_WAIT_QUEUE wait_queue;
	// wait queue

	UINT64 volatile position_with_flags;
	// 현재 페이지를 저장해야 할 블록과 슬롯의 index, 현재 블록 혹은 전체 DWB의 상태
	// flags는 각 블록의 상태(시작, 종료)를 유지하고 DWB 상태를 생성하고 DWB 상태를 수정

	...
}

potision_with_flags

VPID : Volume PID (page identifier)
LSA : Log Sequence Address, 각 로그마다 붙는 고유 식별자

6. DWB 생성 과정

  1. dwb_create()에 들어가면 먼저 구조 변경이 시작되게 되는데 dwb_starts_structure_modification()을 통해 position_with_flags를 체크 및 세팅한다.
  2. dwb_Global.position_with_flagsDWB_MODIFY_STRUCTURE bit가 1이면 ER_FAILED를 리턴하고, 0이면 DWB_MODIFY_STRUCTURE를 1로 세팅한다. 1로 설정하는 이유는 다른 thread들이 dwb_create()에 접근하는 것을 막기위해서이다.
  3. flush thread가 dwb에 접근 중이라면 flush가 끝날 때까지 대기해서 DWB에 유일하게 접근할 수 있는 권한을 획득한다.
  4. block들은 version 별로 정렬되어 있는데 그 중에서 불완전한 block들은 flush해야 한다. 모든 block을 순회하면서 DWB block에 쓰기 작업이 시작되었는지 확인한다. 확인하는 방법은 position_with_flags에서 MSB부터 정방향으로 block_no번째 bit가 세팅되어있다면 writing 중인 것이다. 확인 결과 writing 중이라면 version이 가장 낮은 block부터 오른쪽으로 순회하면서 flush가 필요한 blocks_count만큼 해당 block을 flush한다. flush 후 dwb_log_error()로 로그를 기록한다.
  5. DWB가 만들어져 있는지 확인(DWB_CREATE flag 확인)하고 이미 만들어져있으면 12번으로 가고 아직 만들어지지 않았다면 6번으로 간다.
  6. 먼저 DWB volume 이름을 설정한 후 dwb_create_internal()을 통해 실질적으로 DWB를 생성하는 과정을 거친다.
  7. double_write_buffer_sizenum_blocks를 시스템 파라미터로 읽어와 num_pagesnum_block_pages를 구한다. 이 때 num_blocks, num_pages, num_block_pages의 값은 모두 2의 거듭제곱이어야 한다.
  8. DWB volume을 생성하고 마운트한다.
  9. 모든 database volume들을 disk와 동기화한다. DWB를 활성화하기 전에 dirty page를 flush해야 하기 때문이다.
  10. dwb_create_blocks()를 통해 dwb block의 메모리 할당 및 초기화를 진행한 후 dwb_Global 구조체에 block과 데이터를 입력한다.
  11. position_with_flags를 초기화(MSB에서 32bit, DWB_CREATE, DWB_MODIFY_STRUCTURE을 제외한 flag clear)하고 DWB_CREATE bit을 1로 세팅한다.
  12. 구조변경이 끝났다. dwb_ends_structure_modification()를 통해 DWB_MODIFY_STRUCTURE를 0으로 세팅하고, 해당 thread의 점유 상태를 해제하고, wait_queue에 있는 다음 thread를 깨운다. (internal 함수에서 wait_queue를 초기화했기 때문에 사실상 기능 안함)
profile
블록체인 개발 어때요

0개의 댓글