Recovery by DWB (코드분석 05)

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

Recovery by DWB (코드분석 05)

코드 위치 : https://github.com/CUBRID/cubrid

dwb_load_and_recover_pages()

storage/double_write_buffer.c: 3094

/*
 * dwb_load_and_recover_pages () : DWB에서 페이지를 로드하고 복구
 *
 * return   : Error code.
 * thread_p (in): The thread entry.
 * dwb_path_p (in): The double write buffer path.
 * db_name_p (in): The database name.
 *
 * Note:	이 함수는 recovery 시 호출된다.
 *			Corrupted pages는 double write buffer disk에서 복구된다.
 * 			그런 다음 사용자가 명시하는대로 double write buffer가 재생성된다.
 *			현재 우리는 corrupted page를 복구하기 위해 memory의 DWB block을 사용한다.
 */
int dwb_load_and_recover_pages(THREAD_ENTRY *thread_p, const char *dwb_path_p, const char *db_name_p)
{
	int error_code = NO_ERROR, read_fd = NULL_VOLDES;
	unsigned int num_dwb_pages, ordered_slots_length, i;
	DWB_BLOCK *rcv_block = NULL;
	DWB_SLOT *p_dwb_ordered_slots = NULL;
	FILEIO_PAGE *iopage;
	int num_recoverable_pages;

	assert(dwb_Global.vdes == NULL_VOLDES);
	// dwb_Global의 vdes가 유효한 값이라면 crash

	dwb_check_logging();
	// #define dwb_check_logging() (dwb_Log = prm_get_bool_value(PRM_ID_DWB_LOGGING))

	fileio_make_dwb_name(dwb_Volume_name, dwb_path_p, db_name_p);
	// DWB volume의 이름 지정

	if (fileio_is_volume_exist(dwb_Volume_name))
	// 인자로 넘긴 이름의 volume이 존재한다면
	{
		/* Open DWB volume first */
		read_fd = fileio_mount(thread_p, boot_db_full_name(), dwb_Volume_name, LOG_DBDWB_VOLID, false, false);
		// 해당 이름(및 식별자)의 volume을 mount(open)
		if (read_fd == NULL_VOLDES)
		// 실패 시
		{
			return ER_IO_MOUNT_FAIL;
			// #define ER_IO_MOUNT_FAIL -10
		}

		num_dwb_pages = fileio_get_number_of_volume_pages(read_fd, IO_PAGESIZE);
		// open한 DWB volume의 page 수 구하기(volume의 크기)
		dwb_log("dwb_load_and_recover_pages: The number of pages in DWB %d\n", num_dwb_pages);
		// 구한 volume의 page 수 로그 기록

		/* We are in recovery phase. The system may be restarted with DWB size different than parameter value.
		 * There may be one of the following two reasons:
		 *   - the user may intentionally modified the DWB size, before restarting.
		 *   - DWB size didn't changed, but the previous DWB was created, partially flushed and the system crashed.
		 * We know that a valid DWB size must be a power of 2.
		 * In this case we recover from DWB.
		 * Otherwise, skip recovering from DWB - the modifications are not reflected in data pages.
		 * Another approach would be to recover, even if the DWB size is not a power of 2 (DWB partially flushed).
		 *
		 * 우리는 recovery 단계에 있다. 매개변수 값과 다른 DWB 크기로 시스템이 재시작될 수도 있다.
		 * 이는 다음 두 가지 이유 중 하나일 수 있다.
		 * 		1. 사용자는 재시작 전에 의도적으로 DWB 크기를 수정할 수 있다.
		 * 		2. DWB 크기는 변경되지 않았지만, 이전 DWB가 생성되어 부분적으로 flush되고 system crash가 일어났다.
		 * DWB의 크기는 2의 거듭제곱이어야 한다.
		 * 그렇지 않으면 DWB recovery를 건너뛴다 - 변경사항이 data page에 반영되지 않는다.
		 * 또 다른 방식은 DWB의 크기가 2의 거듭제곱이 아닌 경우에도 recover하는 것이다. (DWB가 부분적으로 flush됨)
		 */
		if ((num_dwb_pages > 0) && IS_POWER_OF_2(num_dwb_pages))
		// DWB에 page가 존재하고 그 수가 2의 거듭제곱이라면
		{
			/* Create DWB block for recovery purpose. */
			error_code = dwb_create_blocks(thread_p, 1, num_dwb_pages, &rcv_block);
			// recovery 용도의 DWB 단일 블록을 생성
			if (error_code != NO_ERROR)
			{
				// 실패 시
				goto end;
			}

			/* Read pages in block write area. This means that slot pages are set. */
			// 블록 쓰기(write)영역에서 페이지들을 읽는다. 이것은 슬롯 페이지들이 set되었음을 의미한다.
			// DWB volume에서 read로 rcv_block->write_buffer에 내용을 채운다. (volume 전체)
			if (fileio_read_pages(thread_p, read_fd, rcv_block->write_buffer, 0, num_dwb_pages, IO_PAGESIZE) == NULL)
			{
				// 실패 시
				error_code = ER_FAILED;
				goto end;
			}

			/* Set slots VPID and LSA from pages. */
			// 페이지에서 슬롯 VPID 및 LSA 설정
			for (i = 0; i < num_dwb_pages; i++)
			{
				// 페이지 수만큼 반목문을 돌면서
				iopage = rcv_block->slots[i].io_page;
				// iopage 포인터 변수에 읽은 페이지를 저장하고

				VPID_SET(&rcv_block->slots[i].vpid, iopage->prv.volid, iopage->prv.pageid);
				// volid 및 pageid 값으로 VPID 설정
				LSA_COPY(&rcv_block->slots[i].lsa, &iopage->prv.lsa);
				// &rcv_block->slots[i].lsa = &iopage->prv.lsa
			}
			rcv_block->count_wb_pages = num_dwb_pages;

			/* Order slots by VPID, to flush faster. */
			error_code = dwb_block_create_ordered_slots(rcv_block, &p_dwb_ordered_slots, &ordered_slots_length);
			// 빠른 flush를 위해 블록의 슬롯들을 p_dwb_ordered_slots에 VPID 순으로 정렬
			if (error_code != NO_ERROR)
			{
				// 실패 시
				error_code = ER_FAILED;
				goto end;
			}

			/* Remove duplicates. Normally, we do not expect duplicates in DWB. 
			 * However, this happens if the system crashes in the middle of flushing into double write file.
			 * In this case, some pages in DWB are from the last DWB flush and the other from the previous DWB flush.
			 *
			 * 중복 제거하기. 일반적인 경우에는 DWB에서 중복이 일어나진 않는다.
			 * 하지만 double write file로 flush하는 도중 system crash가 일어나면 중복이 일어난다.
			 * 이 경우, DWB의 일부 페이지는 가장 최근 flush에서 가져온 것이고 다른 페이지는 이전 flush에서 가져온 것이다.
			 */
			for (i = 0; i < rcv_block->count_wb_pages - 1; i++)
			{
				// 페이지 수 - 1만큼 반복문을 돌면서
				DWB_SLOT *s1, *s2;

				s1 = &p_dwb_ordered_slots[i];
				s2 = &p_dwb_ordered_slots[i + 1];

				if (!VPID_ISNULL(&s1->vpid) && VPID_EQ(&s1->vpid, &s2->vpid))
				// s1의 VPID가 NULL이 아니고 s1과 s2의 VPID가 같으면
				{
					/* Next slot contains the same page. Search for the oldest version. */
					// 다음 슬롯에 동일한 페이지가 있다. 가장 오래된 버전을 검색한다.
					assert(LSA_LE(&s1->lsa, &s2->lsa));
					// &s1->lsa <= &s2->lsa이 false이면 crash (s1이 더 최신이면 crash)

					dwb_log("dwb_load_and_recover_pages: Found duplicates in DWB at positions = (%d,%d) %d\n",
							s1->position_in_block, s2->position_in_block);
					// 중복을 발견했다는 로그 남김

					if (LSA_LT(&s1->lsa, &s2->lsa))
					// &s1->lsa < &s2->lsa 이면 (s2가 더 최신이면)
					{
						/* Invalidate the oldest page version. */
						VPID_SET_NULL(&s1->vpid);
						// 더 오래된 페이지 버전인 s1 무효화
						dwb_log("dwb_load_and_recover_pages: Invalidated the page at position = (%d)\n",
								s1->position_in_block);
						// 무효화 시켰다는 로그 남김
					}
					else
					{
					/* Same LSA. This is the case when page was modified without setting LSA.
		    		 * The first appearance in DWB contains the oldest page modification - last flush in DWB!
					 *
					 * LSA가 같음. 이는 LSA를 설정하지 않고 페이지를 수정한 경우이다.
					 * DWB에 가장 오래된 페이지 수정이 포함되어 있다 - DWB의 마지막 flush
		    		 */
						assert(s1->position_in_block != s2->position_in_block);
						// s1의 position_in_block이 s2의 position_in_block과 같으면 crash

						if (s1->position_in_block < s2->position_in_block)
						// s2가 더 최신이면
						{
							/* Page of s1 is valid. */
							VPID_SET_NULL(&s2->vpid);
							// s1의 페이지가 유효하므로 s2의 VPID 무효화
							dwb_log("dwb_load_and_recover_pages: Invalidated the page at position = (%d)\n",
									s2->position_in_block);
						}
						else
						// s1이 더 최신이면
						{
							/* Page of s2 is valid. */
							VPID_SET_NULL(&s1->vpid);
							// s2의 페이지가 유효하므로 s1의 VPID 무효화
							dwb_log("dwb_load_and_recover_pages: Invalidated the page at position = (%d)\n",
									s1->position_in_block);
						}
					}
				}
			}

#if !defined(NDEBUG)		// DEBUG 모드로 실행됐을 경우
			// check sanity of ordered slots
			error_code = dwb_debug_check_dwb(thread_p, p_dwb_ordered_slots, num_dwb_pages);
			// 정렬된 슬롯의 온전성 확인
			if (error_code != NO_ERROR)
			{
				// 실패 시
				goto end;
			}
#endif // DEBUG

			/* Check whether the data page is corrupted. If the case, it will be replaced with the DWB page. */
			error_code = dwb_check_data_page_is_sane(thread_p, rcv_block, p_dwb_ordered_slots, &num_recoverable_pages);
			// 데이터 페이지가 corrupted되었는지 확인. 이 경우 DWB 페이지로 대체된다.
			if (error_code != NO_ERROR)
			{
				// 실패 시
				goto end;
			}

			if (0 < num_recoverable_pages)
			// recover 가능한 corrupted page가 있다면
			{
				/* Replace the corrupted pages in data volume with the DWB content. */
				error_code =
					dwb_write_block(thread_p, rcv_block, p_dwb_ordered_slots, ordered_slots_length, false, false);
				// Data volume의 corrupted pages를 DWB content로 교체
				if (error_code != NO_ERROR)
				{
					// 실패 시
					goto end;
				}

				/* Now, flush the volumes having pages in current block. */
				// 이제 현재 블록에 페이지가 있는 volumes를 flush
				for (i = 0; i < rcv_block->count_flush_volumes_info; i++)
				{
					if (fileio_synchronize(thread_p, rcv_block->flush_volumes_info[i].vdes, NULL,
										   FILEIO_SYNC_ONLY) == NULL_VOLDES)
					// Database volume의 상태를 disk의 상태와 동기화
					{
						// 실패 시
						error_code = ER_FAILED;
						goto end;
					}

					dwb_log("dwb_load_and_recover_pages: Synchronized volume %d\n",
							rcv_block->flush_volumes_info[i].vdes);
					// 동기화 했다는 로그 남김
				}

				rcv_block->count_flush_volumes_info = 0;
				// flush 완료 했으니 count 변수 0으로 초기화
			}

			assert(rcv_block->count_flush_volumes_info == 0);
			// flush가 필요한 count 변수가 0이 아니면 crash
		}

		/* Dismount the file. */
		fileio_dismount(thread_p, read_fd);
		// mount했던 file을 dismount

		/* Destroy the old file, since data recovered. */
		fileio_unformat(thread_p, dwb_Volume_name);
		// Data가 recover되었으므로 이전 file 폐기
		read_fd = NULL_VOLDES;
	}

	/* Since old file destroyed, now we can rebuild the new double write buffer with user specifications. */
	error_code = dwb_create(thread_p, dwb_path_p, db_name_p);
	// 오래된 file이 파기되었으므로 이제 사용자 지정으로 새로운 DWB를 다시 생성한다.
	if (error_code != NO_ERROR)
	{
		// 실패 시
		dwb_log_error("Can't create DWB \n");
	}

end:
	/* Do not remove the old file if an error occurs. */
	// 에러 발생 시 이전 파일을 폐기하면 안된다.
	if (p_dwb_ordered_slots != NULL)
	{
		free_and_init(p_dwb_ordered_slots);
		// #define free_and_init(ptr) do { free ((void*) (ptr)); (ptr) = NULL; } while (0)
	}

	if (rcv_block != NULL)
	{
		dwb_finalize_block(rcv_block);
		// rcv_block->slots, rcv_block->write_buffer, rcv_block->flush_volumes_info을 free_and_init()
		// dwb_destroy_wait_queue(&rcv_block->wait_queue, &rcv_block->mutex);
		// pthread_mutex_destroy(&rcv_block->mutex);
		free_and_init(rcv_block);
	}

	return error_code;
}

 

dwb_check_data_page_is_sane()

storage/double_write_buffer.c: 2987

/*
 * dwb_check_data_page_is_sane () : Data page가 corrupted 됐는지 확인
 *
 * return   : Error code
 * thread_p (in): The thread entry.
 * block(in): DWB recovery block.
 * p_dwb_ordered_slots(in): DWB ordered slots
 * p_num_recoverable_pages(out): number of recoverable corrupted pages
 *
 */
static int
dwb_check_data_page_is_sane(THREAD_ENTRY *thread_p, DWB_BLOCK *rcv_block, DWB_SLOT *p_dwb_ordered_slots,
							int *p_num_recoverable_pages)
{
	char page_buf[IO_MAX_PAGE_SIZE + MAX_ALIGNMENT];
	FILEIO_PAGE *iopage;
	VPID *vpid;
	int vol_fd = NULL_VOLDES, temp_vol_fd = NULL_VOLDES, vol_pages = 0;
	INT16 volid;
	int error_code;
	unsigned int i;
	int num_recoverable_pages = 0;
	bool is_page_corrupted;

	assert(rcv_block != NULL && p_dwb_ordered_slots != NULL && p_num_recoverable_pages != NULL);
	// 함수 인자로 들어온 포인터 변수들의 유효성 검사
	iopage = (FILEIO_PAGE *)PTR_ALIGN(page_buf, MAX_ALIGNMENT);
	/* #define PTR_ALIGN(addr, boundary) \
	 *		(memset((void*)(addr), 0, DB_WASTED_ALIGN((UINTPTR)(addr), (UINTPTR)(boundary))),\
	 *		(char *)((((UINTPTR)(addr) + ((UINTPTR)((boundary)-1)))) & ~((UINTPTR)((boundary)-1))))
	 * #endif
	 */
	memset(iopage, 0, IO_PAGESIZE);
	// iopage를 페이지 사이즈만큼 0으로 초기화

	volid = NULL_VOLID;
	// #define NULL_VOLID  (-1)

	/* Check whether the data page is corrupted. If true, replaced with the DWB page. */
	// Data page가 corrupted 됐는지 확인해보고 그렇다면 DWB page 대체된다.
	for (i = 0; i < rcv_block->count_wb_pages; i++)
	{
		// 블록 내 페이지 수만큼 반복문을 돌면서
		vpid = &p_dwb_ordered_slots[i].vpid;
		// vipd 포인터 변수에 정렬된 순서대로 슬롯의 vpid를 넣고
		if (VPID_ISNULL(vpid))
		// vpid 유효성 검사
		{
			continue;
			// NULL이면(유효하지 않으면) continue
		}

		if (volid != vpid->volid)
		{
			/* Update the current VPID and get the volume descriptor. */
			temp_vol_fd = fileio_get_volume_descriptor(vpid->volid);
			// 현재 VPID를 업데이트하고 volume descriptor를 가져온다.
			if (temp_vol_fd == NULL_VOLDES)
			// 유효성 검사
			{
				continue;
				// 유효하지 않은 volume descriptor라면 continue
			}
			vol_fd = temp_vol_fd;
			volid = vpid->volid;
			vol_pages = fileio_get_number_of_volume_pages(vol_fd, IO_PAGESIZE);
			// 페이지 수 구하기 (페이지 수 = 볼륨의 크기)
		}

		assert(vol_fd != NULL_VOLDES);
		// vol_fd가 유효하지 않으면 crash

		if (vpid->pageid >= vol_pages)
		{
			/* The page was written in DWB, not in data volume. */
			// 페이지가 data volume이 아닌 DWB로 작성된 경우
			continue;
		}

		/* Read the page from data volume. */
		// Data volume에서 페이지 읽기
		if (fileio_read(thread_p, vol_fd, iopage, vpid->pageid, IO_PAGESIZE) == NULL)
		{
			/* There was an error in reading the page. */
			// 읽는 도중 에러 발생
			ASSERT_ERROR_AND_SET(error_code);
			// 반환될 error_code가 NO_ERROR가 아닌지 확인
			return error_code;
		}

		error_code = fileio_page_check_corruption(thread_p, iopage, &is_page_corrupted);
		// Data volume의 페이지가 corrupted 됐는지 확인
		if (error_code != NO_ERROR)
		{
			// 에러 발생 시
			return error_code;
		}

		if (!is_page_corrupted)
		// page가 corrupted되지 않았다면
		{
			/* The page in data volume is not corrupted. Do not overwrite its content - reset slot VPID. */
			// 페이지 손상이 없으니 내용을 덮어쓰면 안된다.
			VPID_SET_NULL(&p_dwb_ordered_slots[i].vpid);
			// VPID를 NULL로 초기화
			fileio_initialize_res(thread_p, p_dwb_ordered_slots[i].io_page, IO_PAGESIZE);
			// 나머지 요소들도 초기화
			continue;
		}

		/* Corrupted page in data volume. Check DWB. */
		// Data volume의 page가 corrupted된 경우 DWB 체크
		error_code = fileio_page_check_corruption(thread_p, p_dwb_ordered_slots[i].io_page, &is_page_corrupted);
		// DWB의 페이지가 corrupted 됐는지 확인
		if (error_code != NO_ERROR)
		{
			// 에러 발생 시
			return error_code;
		}

		if (is_page_corrupted)
		// page가 corrupted되었다면
		{
			/* The page is corrupted in data volume and DWB. Something wrong happened. */
			// Data volume의 페이지와 DWB의 페이지 모두 corrupted. 문제 발생.
			assert_release(false);
			dwb_log_error("Can't recover page = (%d,%d)\n", vpid->volid, vpid->pageid);
			// recover 불가하다는 로그 남김
			return ER_FAILED;
		}

		/* The page content in data volume will be replaced later with the DWB page content. */
		// Data volume의 페이지는 후에 DWB 페이지 content로 대체된다.
		dwb_log("page = (%d,%d) is recovered with DWB.\n", vpid->volid, vpid->pageid);
		// DWB로 recover 됐음을 로그 남김
		num_recoverable_pages++;
	}

	*p_num_recoverable_pages = num_recoverable_pages;
	return NO_ERROR;
}

 

본 시리즈의 글들은 CUBRID DB엔진 오픈 스터디를 진행하며 팀원들과 함께 공부한 내용을 정리한 것입니다.
Github 링크

profile
블록체인 개발 어때요

0개의 댓글