코드 위치 : https://github.com/CUBRID/cubrid
storage/double_write_buffer.c: 2694
/*
* dwb_add_page () - Add page content to DWB.
*
* return : Error code.
* thread_p (in): The thread entry.
* io_page_p(in): In-memory address where the current content of page resides.
* vpid(in): Page identifier.
* p_dwb_slot(in/out): DWB slot where the page content must be added.
*
* Note: thread may flush the block, if flush thread is not available or we are in stand alone.
*/
int dwb_add_page(THREAD_ENTRY *thread_p, FILEIO_PAGE *io_page_p, VPID *vpid, DWB_SLOT **p_dwb_slot)
{
unsigned int count_wb_pages; // write buffer에 추가된 페이지 수
int error_code = NO_ERROR; // #define NO_ERROR 0
bool inserted = false; // DWB에 slot이 잘 추가되었는지 확인하는 bool 타입 변수
DWB_BLOCK *block = NULL; // block 포인터
DWB_SLOT *dwb_slot = NULL; // slot 포인터
bool needs_flush; // flush가 필요한지 확인하는 bool 타입 변수 (count_wb_pages >= DWB 각 block의 page 수 → flush)
assert(p_dwb_slot != NULL && (io_page_p != NULL || (*p_dwb_slot)->io_page != NULL) && vpid != NULL);
// 위 조건에 만족하지 않으면 crash
if (thread_p == NULL)
{
thread_p = thread_get_thread_entry_info();
// 인자로 thread_p가 제대로 들어오지 않았다면 위 함수를 통해 자체적으로 thread entry 구함
}
if (*p_dwb_slot == NULL)
// *p_dwb_slot이 NULL이라면
{
error_code = dwb_set_data_on_next_slot(thread_p, io_page_p, true, p_dwb_slot);
// 가능한 경우, next DWB slot에 데이터 set / error code 설정
// dwb_acquire_next_slot()에서 position_with_flags에서 현재 block num, 다음 slot의 위치를 구함
// 현재 block의 slot pointer + 구한 slot의 index로 slot의 pointer를 구하고 p_dwb_slot에 대입
// dwb_set_slot_data()에서 p_dwb_slot에 io_page_p를 입력
if (error_code != NO_ERROR)
{
return error_code;
// 위 함수 안에서 에러 발생 시 add_page 중단 / error_code 반환
}
if (*p_dwb_slot == NULL)
{
return NO_ERROR;
// 위 함수를 거치고도 아직 *p_dwb_slot이 NULL이라면 add_page 중단 / NO_ERROR 반환
}
}
dwb_slot = *p_dwb_slot;
// 함수 내에서 선언한 dwb_slot 포인터 변수에 *p_dwb_slot 대입
assert(VPID_EQ(vpid, &dwb_slot->vpid));
// #define VPID_EQ(vpid_ptr1, vpid_ptr2) ((vpid_ptr1) == (vpid_ptr2) || ((vpid_ptr1)->pageid == (vpid_ptr2)->pageid && (vpid_ptr1)->volid == (vpid_ptr2)->volid))
// vipd와 slot->vpid를 비교하는 이유는 dwb_set_slot_data() 함수에서 입력 과정에서 error가 발생됐는지 check하기 위함으로 추측됨
// vpid와 &dwb_slot->vpid가 일치하지 않으면 crash
if (!VPID_ISNULL(vpid))
// vpid가 NULL이 아니면
{
error_code = dwb_slots_hash_insert(thread_p, vpid, dwb_slot, &inserted);
// slots hash에 삽입 진행 / error code 설정
if (error_code != NO_ERROR)
{
return error_code;
// 위 함수 안에서 에러 발생 시 add_page 중단 / error_code 반환
}
if (!inserted)
// 삽입 진행 후에도 inserted 변수가 아직 false라면
{
/* Invalidate the slot to avoid flushing the same data twice. */
VPID_SET_NULL(&dwb_slot->vpid);
// 동일한 데이터를 두 번 flush하지 않도록 slot을 NULL로 세팅해 무효화 시킴
fileio_initialize_res(thread_p, dwb_slot->io_page, IO_PAGESIZE);
// LSA를 NULL로 세팅 / io_page 구조체 내의 변수들 default값으로 초기화
}
}
dwb_log("dwb_add_page: added page = (%d,%d) on block (%d) position (%d)\n", vpid->volid, vpid->pageid,
dwb_slot->block_no, dwb_slot->position_in_block);
// 추가된 page 정보를 log에 남김
block = &dwb_Global.blocks[dwb_slot->block_no];
// 함수 내에서 선언한 block 포인터 변수에 DWB 전역 구조체 내의 블록 배열의 slot의 block 번호 주소 대입
count_wb_pages = ATOMIC_INC_32(&block->count_wb_pages, 1);
// 함수 내에서 선언한 count_wb_pages에 ATOMIC_INC_32로 &block->count_wb_pages 대입
// write_buffer의 page가 1개 추가되었으므로 +1 해서 대입
assert_release(count_wb_pages <= DWB_BLOCK_NUM_PAGES);
// count_wb_pages가 DWB 각 block의 page 수보다 크면 crash
if (count_wb_pages < DWB_BLOCK_NUM_PAGES)
// count_wb_pages가 DWB 각 block의 page 수보다 작으면
{
needs_flush = false;
// flush 불필요
}
else
{
needs_flush = true;
// flush 필요
}
if (needs_flush == false)
{
return NO_ERROR;
// flush 할 필요 없으면 add_page 중단 / NO_ERROR 반환
}
/*
* The blocks must be flushed in the order they are filled to have consistent data.
* The flush block thread knows how to flush the blocks in the order they are filled.
* So, we don't care anymore about the flushing order here.
* Initially, we waited here if the previous block was not flushed.
* That approach created delays.
* Block은 일관된 데이터를 갖기 위해 채워진 순서대로 flush 되어야 한다.
* Flush block thread는 채워진 순서대로 block을 flush하는 방법을 알고 있다.
* 따라서 여기에선 flushing 순서에 대해 더 이상 신경 쓰지 않는다.
* 최초에는 이전 block이 flush되지 않은 경우 여기에서 기다렸었다.
* 이러한 접근 방식으로 인해 지연이 발생됐었다.
*/
// SEVER_MODE로 실행됐을 경우에만 진행
#if defined(SERVER_MODE)
/*
* Wake ups flush block thread to flush the current block.
* The current block will be flushed after flushing the previous block.
* 현재 block을 flush하기 위해 flush block thread를 깨운다.
* 현재 block은 이전 block을 flush한 후 flush된다.
*/
if (dwb_is_flush_block_daemon_available())
// flush block daemon이 가능한 상태이면
{
/* Wakeup the thread that will flush the block. */
dwb_flush_block_daemon->wakeup();
// block을 flush할 thread를 깨움
return NO_ERROR;
}
#endif /* SERVER_MODE */
/* Flush all pages from current block */
error_code = dwb_flush_block(thread_p, block, false, NULL);
// 현재 block에서 모든 pages flush / error code 설정
if (error_code != NO_ERROR)
{
dwb_log_error("Can't flush block = %d having version %lld\n", block->block_no, block->version);
return error_code;
// flush 실패 시 log에 에러 메세지 남기고 error code 반환
}
dwb_log("Successfully flushed DWB block = %d having version %lld\n", block->block_no, block->version);
return NO_ERROR;
// flush 성공 시 해당 메세지를 log에 성공 메세지 남기고 함수 종료
}
storage/double_write_buffer.c: 2656
/*
* dwb_set_data_on_next_slot () - Sets data at the next DWB slot, if possible.
*
* return : Error code.
* thread_p(in): The thread entry.
* io_page_p(in): The data that will be set on next slot.
* can_wait(in): True, if waiting is allowed.
* p_dwb_slot(out): Pointer to the next free DWB slot.
*/
int dwb_set_data_on_next_slot(THREAD_ENTRY *thread_p, FILEIO_PAGE *io_page_p, bool can_wait, DWB_SLOT **p_dwb_slot)
{
int error_code;
assert(p_dwb_slot != NULL && io_page_p != NULL);
/* Acquire the slot before setting the data. */
error_code = dwb_acquire_next_slot(thread_p, can_wait, p_dwb_slot);
// 페이지를 놓을 슬롯을 찾아 *p_dwb_slot 에 넣어줌
if (error_code != NO_ERROR)
{
return error_code;
}
assert(can_wait == false || *p_dwb_slot != NULL);
// can_wait가 false 이거나, can_wait가 true이면서 *p_dwb_slot이 NULL은 아니어야 함
if (*p_dwb_slot == NULL)
{
/* Can't acquire next slot. */
return NO_ERROR;
}
// can_wait가 false이고, *p_dwb_slot이 NULL인 경우 처리에 사용할 슬롯을 찾을 수 없었던 것이므로 종료
/* Set data on slot. */
dwb_set_slot_data(thread_p, *p_dwb_slot, io_page_p);
// 찾은 슬롯에 페이지 놓기
return NO_ERROR;
}
storage/double_write_buffer.c: 2442
/*
* dwb_acquire_next_slot () - Acquire the next slot in DWB.
*
* return : Error code.
* thread_p(in): The thread entry.
* can_wait(in): True, if can wait to get the next slot.
* p_dwb_slot(out): The pointer to the next slot in DWB.
*/
STATIC_INLINE int
dwb_acquire_next_slot(THREAD_ENTRY *thread_p, bool can_wait, DWB_SLOT **p_dwb_slot)
{
UINT64 current_position_with_flags, current_position_with_block_write_started, new_position_with_flags;
unsigned int current_block_no, position_in_current_block;
int error_code = NO_ERROR;
DWB_BLOCK *block;
assert(p_dwb_slot != NULL);
*p_dwb_slot = NULL;
start:
/* Get the current position in double write buffer. */
current_position_with_flags = ATOMIC_INC_64(&dwb_Global.position_with_flags, 0ULL);
// current_position_with_flags = dwb_Global.position_with_flags
if (DWB_NOT_CREATED_OR_MODIFYING(current_position_with_flags))
// dwb가 만들어지지 않았고, 만들어지는 중이라면
{
/* Rarely happens. */
if (DWB_IS_MODIFYING_STRUCTURE(current_position_with_flags))
// 만들어지는 중이라면
{
if (can_wait == false)
// (dwb가 만들어질 때까지) 대기가 불가능하다면
{
return NO_ERROR;
}
/* DWB structure change started, needs to wait. */
error_code = dwb_wait_for_strucure_modification(thread_p);
// dwb 가 만들어질 때까지 대기
if (error_code != NO_ERROR)
// 에러 발생
{
if (error_code == ER_CSS_PTHREAD_COND_TIMEDOUT)
{
/* timeout, try again */
goto start;
// 타임아웃일 경우 위에서부터 재시도
}
return error_code;
// 다른 에러일 경우 에러 반환
}
/* Probably someone else advanced the position, try again. */
goto start;
// 다시 제일 위 조건부터 플래그 검증
}
else if (!DWB_IS_CREATED(current_position_with_flags))
// 만들어지지 않았으면
{
if (DWB_IS_ANY_BLOCK_WRITE_STARTED(current_position_with_flags))
// 블록이 하나라도 WRITE_STARTED 상태라면 (0~31 번째 비트 중 하나라도 SET 이라면)
{
/* Someone deleted the DWB, before flushing the data. */
er_set(ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_DWB_DISABLED, 0);
return ER_DWB_DISABLED;
// 에러처리
}
/* Someone deleted the DWB */
return NO_ERROR;
// DWB 가 사용불가하므로 종료
}
else
{
assert(false);
}
}
current_block_no = DWB_GET_BLOCK_NO_FROM_POSITION(current_position_with_flags);
// current_block_no = (current_position_with_flags & DWB_POSITION_MASK) >> dwb_Global.log2_num_block_pages
position_in_current_block = DWB_GET_POSITION_IN_BLOCK(current_position_with_flags);
// position_in_current_block = current_position_with_flags & DWB_POSITION_MASK & (dwb_Global.num_block_pages - 1)
/*
* ex)
* num_block_pages: 64
* log2_num_block_pages: 6
* current_position_with_flags & DWB_POSITION_MASK 을 통해 block 번호와 slot index를 알아낼 수 있음
* 해당 값이 0 이면 블록 번호 0 // 64 = 0, 슬롯 인덱스 0 % 64 = 0
* 해당 값이 65 이면 블록 번호 65 // 64 = 1, 슬롯 인덱스 65 % 64 = 1
* 이 과정을 비트로 나타내면 아래와 같습니다
* current_position_with_flags & DWB_POSITION_MASK => 1이라고 가정 => 00 0000 0000 0000 0000 0000 0000 0001
* 블록 번호: 0000 0001 >> 6 = 0
* 슬롯 인덱스: 0000 0001 & 63 = 0000 0001 & 0011 1111 = 1
* current_position_with_flags & DWB_POSITION_MASK => 65라고 가정 => 00 0000 0000 0000 0000 0000 0100 0001
* 블록 번호: 0100 0001 >> 6 = 1
* 슬롯 인덱스: 0100 0001 & 0011 1111 = 1
*/
assert(current_block_no < DWB_NUM_TOTAL_BLOCKS && position_in_current_block < DWB_BLOCK_NUM_PAGES);
if (position_in_current_block == 0)
// 처리하게 될 슬롯이 0번째 슬롯일 때
{
/* This is the first write on current block. Before writing, check whether the previous iteration finished. */
if (DWB_IS_BLOCK_WRITE_STARTED(current_position_with_flags, current_block_no))
// current_position_with_flags의 MSB에서 current_block_no 번째 비트가 SET 되어 있으면 = current_block_no 블록이 쓰기 작업 중이면
{
if (can_wait == false)
{
return NO_ERROR;
// 대기가 불가능하면 종료
}
dwb_log("Waits for flushing block=%d having version=%lld) \n",
current_block_no, dwb_Global.blocks[current_block_no].version);
/*
* The previous iteration didn't finished, needs to wait, in order to avoid buffer overwriting.
* Should happens relative rarely, except the case when the buffer consist in only one block.
*/
error_code = dwb_wait_for_block_completion(thread_p, current_block_no);
// 버퍼가 덮어씌워지는 것을 피하기 위해서 이전 쓰기 작업이 끝날 때까지 대기
// dwb_wait_for_strucure_modification 와 체크 플래그만 다르고 나머진 동일
if (error_code != NO_ERROR)
{
if (error_code == ER_CSS_PTHREAD_COND_TIMEDOUT)
{
/* timeout, try again */
goto start;
// timeout의 경우 처음부터 재시도
}
dwb_log_error("Error %d while waiting for flushing block=%d having version %lld \n",
error_code, current_block_no, dwb_Global.blocks[current_block_no].version);
return error_code;
// 다른 에러의 경우 에러처리
}
/* Probably someone else advanced the position, try again. */
goto start;
// 대기가 끝난 이후 플래그 검토를 위해 처음부터 시작
}
/* First write in the current block. */
assert(!DWB_IS_BLOCK_WRITE_STARTED(current_position_with_flags, current_block_no));
current_position_with_block_write_started =
DWB_STARTS_BLOCK_WRITING(current_position_with_flags, current_block_no);
// current_position_with_block_write_started =
// current_position_with_flags의 MSB에서 current_block_no 번째 비트를 SET
new_position_with_flags = DWB_GET_NEXT_POSITION_WITH_FLAGS(current_position_with_block_write_started);
// new_position_with_flags = slot의 index가 (num_pages - 1) 과 같다면 0, 아니라면 slot을 1 증가하고 플래그를 반환
// 위 방식으로 슬롯을 순회
}
else
{
/* I'm sure that nobody else can delete the buffer */
assert(DWB_IS_CREATED(dwb_Global.position_with_flags));
assert(!DWB_IS_MODIFYING_STRUCTURE(dwb_Global.position_with_flags));
/* Compute the next position with flags */
new_position_with_flags = DWB_GET_NEXT_POSITION_WITH_FLAGS(current_position_with_flags);
// new_position_with_flags = slot의 index가 (num_pages - 1) 과 같다면 0, 아니라면 slot을 1 증가하고 플래그를 반환
// 위 방식으로 슬롯을 순회
}
/* Compute and advance the global position in double write buffer. */
if (!ATOMIC_CAS_64(&dwb_Global.position_with_flags, current_position_with_flags, new_position_with_flags))
// 만약 다른 스레드에서 처리가 이뤄져 플래그가 바뀌었다면, 다시 처음부터 시작
// 그게 아니라면 dwb_Global.position_with_flags에 새 플래그를 대입
{
/* Someone else advanced the global position in double write buffer, try again. */
goto start;
}
block = dwb_Global.blocks + current_block_no;
// block = 포인터 dwb_Global.blocks에 현재 블록 번호를 더한 포인터
*p_dwb_slot = block->slots + position_in_current_block;
// *p_dwb_slot = 해당 블록의 슬롯 포인터에 슬롯 위치를 더한 포인터
/* Invalidate slot content. */
VPID_SET_NULL(&(*p_dwb_slot)->vpid);
assert((*p_dwb_slot)->position_in_block == position_in_current_block);
return NO_ERROR;
}
storage/double_write_buffer.c: 2585
/*
* dwb_set_slot_data () - Set DWB data at the location indicated by the slot.
*
* return : Error code.
* thread_p(in): Thread entry
* dwb_slot(in/out): DWB slot that contains the location where the data must be set.
* io_page_p(in): The data.
*/
STATIC_INLINE void
dwb_set_slot_data(THREAD_ENTRY *thread_p, DWB_SLOT *dwb_slot, FILEIO_PAGE *io_page_p)
{
assert(dwb_slot != NULL && io_page_p != NULL);
assert(io_page_p->prv.p_reserve_2 == 0);
if (io_page_p->prv.pageid != NULL_PAGEID)
// 데이터 페이지의 페이지가 유효하다면
{
memcpy(dwb_slot->io_page, (char *)io_page_p, IO_PAGESIZE);
// 슬롯의 페이지 위치에 해당 페이지 복사
}
else
{
/* Initialize page for consistency. */
fileio_initialize_res(thread_p, dwb_slot->io_page, IO_PAGESIZE);
// 슬롯의 페이지 자체를 초기화
}
assert(fileio_is_page_sane(io_page_p, IO_PAGESIZE));
LSA_COPY(&dwb_slot->lsa, &io_page_p->prv.lsa);
VPID_SET(&dwb_slot->vpid, io_page_p->prv.volid, io_page_p->prv.pageid);
// dwb_slot->vpid.volid = io_page_p->prv.volid
// dwb_slot->vpid.pageid = io_page_p->prv.pageid
}
storage/double_write_buffer.c: 1368
/*
* dwb_slots_hash_insert () - Insert entry in slots hash.
*
* return : Error code.
* thread_p (in): The thread entry.
* vpid(in): The page identifier.
* slot(in): The DWB slot.
* inserted (out): 1, if slot inserted in hash.
*/
STATIC_INLINE int
dwb_slots_hash_insert(THREAD_ENTRY *thread_p, VPID *vpid, DWB_SLOT *slot, bool *inserted)
{
int error_code = NO_ERROR;
DWB_SLOTS_HASH_ENTRY *slots_hash_entry = NULL;
assert(vpid != NULL && slot != NULL && inserted != NULL);
// vpid, slot, inserted == NULL이면 crash
*inserted = dwb_Global.slots_hashmap.find_or_insert(thread_p, *vpid, slots_hash_entry);
// lf_hash_find_or_insert () - find or insert an entry in the hash table
// vpid 를 key 값으로 dwb_global 변수의 slots_hashmap 변수에서 해쉬 함수를 사용해 value 를 가져오고 해쉬맵을 탐색한다.
// 해쉬맵에서 slots_hash_entry 가 있으면 가져오고 inserted 0,
// 없으면 thread_p의 freelist에서 받아와 해쉬맵에 추가하고 inserted 1로 설정한다.
assert(VPID_EQ(&slots_hash_entry->vpid, &slot->vpid));
// 같은 주소를 가르키거나, 페이지 아이디가 같거나, volid가 같아야 한다.
assert(slots_hash_entry->vpid.pageid == slot->io_page->prv.pageid && slots_hash_entry->vpid.volid == slot->io_page->prv.volid);
if (!(*inserted))
// *inserted == 0 인경우, find 한 경우
{
assert(slots_hash_entry->slot != NULL);
// slot이 NULL이면 crash
if (LSA_LT(&slot->lsa, &slots_hash_entry->slot->lsa))
// log 는 순서대로 쌓인다.
// slot의 lsa보다, slots_hash_entry의 lsa가 최신인 경우
{
dwb_log("DWB hash find key (%d, %d), the LSA=(%lld,%d), better than (%lld,%d): \n",
vpid->volid, vpid->pageid, slots_hash_entry->slot->lsa.pageid,
slots_hash_entry->slot->lsa.offset, slot->lsa.pageid, slot->lsa.offset);
/* The older slot is better than mine - leave it in hash. */
pthread_mutex_unlock(&slots_hash_entry->mutex);
return NO_ERROR;
}
else if (LSA_EQ(&slot->lsa, &slots_hash_entry->slot->lsa))
{
/*
* If LSA's are equals, still replace slot in hash. We are in "flushing to disk without logging" case.
* The page was modified but not logged. We have to flush this version since is the latest one.
* LSA 가 동일하더라도 해쉬의 슬롯을 바꾼다.
* Page는 변경되었지만, 로그가 남지는 않았다. flush해야함.
*/
if (slots_hash_entry->slot->block_no == slot->block_no)
{
/* Invalidate the old slot, if is in the same block. We want to avoid duplicates in block at flush. */
assert(slots_hash_entry->slot->position_in_block < slot->position_in_block);
// slot->position_in_block이 더 최신임을 assert
VPID_SET_NULL(&slots_hash_entry->slot->vpid);
// hash 테이블에 있는 page id 를 null 로 바꿔줌
fileio_initialize_res(thread_p, slots_hash_entry->slot->io_page, IO_PAGESIZE);
// 초기화
dwb_log("Found same page with same LSA in same block - %d - at positions (%d, %d) \n",
slots_hash_entry->slot->position_in_block, slot->position_in_block);
// 로그 남김
}
else
// LSA 가 동일한데, block_no가 다를 때
{
#if !defined(NDEBUG)
int old_block_no = ATOMIC_INC_32(&slots_hash_entry->slot->block_no, 0);
if (old_block_no > 0)
{
/* Be sure that the block containing old page version is flushed first. */
DWB_BLOCK *old_block = &dwb_Global.blocks[old_block_no];
DWB_BLOCK *new_block = &dwb_Global.blocks[slot->block_no];
/* Maybe we will check that the slot is still in old block. */
assert((old_block->version < new_block->version) || (old_block->version == new_block->version && old_block->block_no < new_block->block_no));
// new_block 이 더 최신임을 assert한다.
dwb_log("Found same page with same LSA in 2 different blocks old = (%d, %d), new = (%d,%d) \n",
old_block_no, slots_hash_entry->slot->position_in_block, new_block->block_no,
slot->position_in_block);
}
#endif
}
}
dwb_log("Replace hash key (%d, %d), the new LSA=(%lld,%d), the old LSA = (%lld,%d)",
vpid->volid, vpid->pageid, slot->lsa.pageid, slot->lsa.offset,
slots_hash_entry->slot->lsa.pageid, slots_hash_entry->slot->lsa.offset);
// 바꿀게라고 로그 표시
}
else
// inserted가 됐을 때
{
dwb_log("Inserted hash key (%d, %d), LSA=(%lld,%d)", vpid->volid, vpid->pageid, slot->lsa.pageid,
slot->lsa.offset);
}
slots_hash_entry->slot = slot;
// 바꿔줌
pthread_mutex_unlock(&slots_hash_entry->mutex);
*inserted = true;
return NO_ERROR;
}
본 시리즈의 글들은 CUBRID DB엔진 오픈 스터디를 진행하며 팀원들과 함께 공부한 내용을 정리한 것입니다.
Github 링크