-
Notifications
You must be signed in to change notification settings - Fork 0
Project3
on-disk pages의 cashing을 위한 in-memory buffer manager를 구현
- 명시된 형태에 own design을 추가한 buffer block structure 정의
- 명시된 동작을 수행하는 init_db, open_table, db_insert, db_find, db_delete, close_table, shutdown_db 함수를 API services로 제공
- 최대 10개의 table을 동시에 열 수 있도록 구현
- Project2에서는 index manager와 disk space manager의 2개 layer로 구현했으며, 과제에서 요구한 API services는 index manager에 존재하도록 구현했으나, Project3에서는 buffer manager가 추가되며 추후 과제에서 더욱 추가될 수 있으므로, 제공해야 하는 API services를 위한 wrapper functions를 가지는 API layer를 추가하였다. (다른 학생들과 논의 중 전윤성 학생이 제안한 design)
- 따라서, (상위) API layer - index manager - buffer manager - disk space manager (하위) 의 layer 구조를 가진다.
- wrapper functions 순서도
- buffer를 관리하기 위한 buffer manager layer (Project3의 요구사항)
- 과제에서 구현을 요구한 buffer_block 구조체는, 명시된 것 외에, 해당 table에서의 탐색을 위한 buffer_block_t 포인터 next_of_table과 prev_of_table을 추가하였다.
- buffer_table 구조체를 만들어서, table id와 file descripter 값, buffer에 올려진 해당 table에 해당하는 block의 개수, 이의 탐색을 위한 buffer_block_t 포인터 head_of_table을 사용하였다.
- project3에서 추가적으로 요구한 init_db, shutdown_db, open_table, close_table의 실질적인 구현이 여기서 이루어졌으며, 이는 상위 layer의 wrapper function으로 연결된다.
- index manager가 file manager에 존재하던 file_alloc_page와 file_free_page를 사용하던 것과 유사하게 사용할 수 있도록, buffer_alloc_page, buffer_free_page를 구현했으며,
- file_read/write_page 대신 사용하도록 buffer_request_page / buffer_release_page를 추가하였다. 특히 다른 점은, 수정 사항이 없을 시 write를 하지 않아도 됐던 file manager와 다르게, 사용 완료 시 무조건 release를 해줘야 하며, paremeter로 받는 is_writed로 dirty 여부를 판단한다.
- 자세한 내용은 buffer manager 상세정보 참고
- Project2에서는 index manager가 disk space manager 소속 함수를 호출하여 사용하였으나, Project3에서는 index manager의 각 함수가 disk space manager가 아닌 buffer manager 소속 함수를 호출하여 작업을 수행하도록 수정하였다.
- 따라서, Project2에서는 file_read한 후, 필요하면 file_write하고, 필요하지 않으면 추가적인 조치를 취하지 않았으나, Project3에서는 buffer_request하며 pin을 꽂은 후, 사용이 완료되면 write 여부와 무관하게 buffer_release하며 pin을 해제한다. 또한 이 때 write 여부를 송신하여 is_dirty 값을 수정하도록 한다.
- Project2에서는 단일 table을 사용하며 table 관련 정보를 index manager에서 따로 다루지 않았으나, Project3에서는 최대 10개 table을 동시에 열어서 사용할 수 있어야 하므로 각 함수가 table_id를 인자로 받고 그에 적합한 동작을 수행한다.
- Project2에서는 이어지는 함수에 page number를 전달해서 해당 함수에서 다시 read하여 사용했으나, Project3에서는 이어지는 함수에 page buffer 포인터를 전달해서 해당 함수에서 buffer 상의 page에 바로 접근할 수 있도록 한다. 단, 한 cycle 동안 request된 page는 모두 그 안에서 release될 수 있도록 한다.
- API layer의 wrapper functions가 호출할 함수 7개가 존재하며, 이 중 4개(index_init_db, index_shutdown_db, index_open_table, index_close_table)는 다시 buffer manager 함수의 wrapper functions이고, 3개(index_insert, index_find, index_delete)는 실제로 이 계층에서 작업을 수행한다.
- Project2에서는 단일 table을 사용하며 전역변수 fd로 table file을 관리했지만, Project3에서는 최대 10개의 table을 동시에 열어서 사용할 수 있어야 하므로, disk space manager의 read/write 함수는 상위 layer인 buffer manager에서 호출될 때 fd값을 인자로 받아서 이에 적합한 동작을 수행한다. 즉 buffer manager가 각 table에 관한 정보를 관리한다.
- Project2에서는 allocate와 free 기능을 disk space manager가 수행했지만, Project3에서는 disk space manager의 read/write 함수를 활용하여 buffer manager가 allocate와 free 기능을 관리하므로, disk space manager에서는 해당 기능을 하는 함수를 삭제한다.
- API layer는 과제에서 요구하는 API services를 제공하며, 하위 layer의 wrapper functions로 이루어져 있다.
- index manager는 project2에서와 유사하게 b+tree의 indexing을 수행한다. 하위 layer인 buffer manager의 wrapper functions 일부와, 필요에 따라 buffer manager 소속 함수를 호출해가며 동작하는 함수들로 이루어져 있다.
- buffer manager는 project3에서 구현을 요구한 사항으로, on-disk pages를 cashing하기 위해 in-memory buffer를 생성하고 관리한다. 이를 수행하기 위한 구조체와 함수 등으로 이루어져 있다.
- disk space manager는 on-disk pages, 즉 table file들을 직접적으로 관리한다. 이를 수행하기 위한 구조체와 함수 등으로 이루어져 있다.
- 전체 layers/functions 순서도
index manager의 wrapper functions로 이루어져 있다.
int init_db(int buf_num);
int shutdown_db();
int open_table(char* pathname);
int close_table(int table_id);
int db_insert(int table_id, int64_t key, char* value);
int db_find(int table_id, int64_t key, char* ret_val);
int db_delete(int table_id, int64_t key);
index manager는 위의 수정사항 외에는 project2에서와 매우 유사하게 동작하며 b+tree의 indexing을 수행한다. 하위 layer인 buffer manager의 wrapper functions 일부와, 필요에 따라 buffer manager 소속 함수를 호출해가며 동작하는 함수들로 이루어져 있다.
buffer manager는 on-disk pages를 cashing하기 위해 in-memory buffer를 생성하고 관리한다. 이를 수행하기 위한 구조체와 함수 등으로 이루어져 있다.
- Index
typedef struct buffer_block buffer_block_t;
struct buffer_block {
// Buffer page frame.
page_t frame;
// Buffer control block.
int table_id;
pagenum_t page_num;
bool is_dirty;
bool is_pinned;
buffer_block_t* next_of_LRU;
buffer_block_t* prev_of_LRU;
buffer_block_t* next_of_table;
buffer_block_t* prev_of_table;
};
- on-disk page를 memory buffer에 올리기 위해 사용하는 buffer_block 구조체를 정의한다.
- 과제에서 구현을 요구한 fields를 구현한다.
- frame : 해당 page(4096 bytes)를 contain.
- table_id : 해당 page가 속한 table의 id.
- page_num : 해당 page의 page number.
- is_dirty : read한 후 write 여부.
- is_pinned : 현재 사용 중 여부.
- next_of_LRU/prev_of_LRU : LRU policy에 따라 buffer를 관리하기 위해 buffer_block들을 2 way linked list로 연결한다. 이러한 LRU list의 구현을 위해 buffer_block 구조체가 가져야할 포인터.
- next_of_table/prev_of_table : table별로 buffer_block들을 2 way linked list로 연결하여, 특정 table의 page를 탐색할 때 사용한다. 선형적으로 탐색하므로 buffer와 buffer에 올라간 page 수가 증가할수록 느려지는 미흡한 성능을 보여주고 있다. 따라서 hash 등의 방법으로 교체하여 개선할 필요가 있다.
typedef struct buffer_table buffer_table_t;
struct buffer_table {
int table_id;
int fd;
int block_num;
buffer_block_t* head_of_table;
};
- 최대 10개의 table을 동시에 열어 관리하기 위해 각각의 table 정보를 저장하는 데에 사용하는 buffer_table 구조체를 정의한다.
- table_id : 해당 table의 unique id. buffer manager 내부와 상위 layer에서 각각의 table을 관리하는 데에 사용한다.
- fd : 해당 table의 file descriptor 값. disk space manager를 통해 실제 table file을 read/write할 때 사용한다.
- block_num : 현재 buffer에 올라간 해당 table의 page 수. 추후 buffer_block 관리 방법이 변경될 시 삭제될 수 있다.
- head_of_table : 해당 table 소속 buffer_block linked list의 head. 추후 buffer_block 관리 방법이 변경될 시 삭제될 수 있다.
bool db_opened = false;
int buffer_size = 0;
int table_num = 0;
int block_num = 0;
buffer_table_t buf_tables[MAX_NUM_OF_TABLE_ID];
buffer_block_t* head_of_LRU_list = NULL;
buffer_block_t* tail_of_LRU_list = NULL;
buffer_block_t* buf_pool = NULL;
- buffer 관리를 위한 buffer manager layer의 전역 변수들을 선언한다.
- db_opened : 현재 db의 initialize / shutdown 여부를 저장한다. 초기값으로 false를 가진다.
- buffer_size : db initialize 시 buffer pool의 size, 즉 buffer block의 최대 개수를 입력받아 저장한다.
- table_num : 현재 열려 있는 table의 개수를 저장한다.
- block_num : 현재 on-disk page가 올라와있는 buffer_block의 개수를 저장한다.
- buf_tables[] : 여러 개(최대 10개)의 table을 동시에 사용하기 위해 buffer_table 구조체의 배열을 사용한다.
- head_of_LRU_list / tail_of_LRU_list : LRU policy에 따라 buffer를 관리하기 위해 linked list인 LRU list를 사용한다.
- buf_pool : buffer_block의 배열인 buffer pool. 포인터로 선언하고 db initialize 시 입력받은 buffer_size에 맞게 동적할당한다.
int find_table_index(int table_id);
buf_tables 배열에서 특정 id를 가지는 table의 index를 탐색한다.
- 인자
- table_id (int) : 탐색할 table의 id.
- 반환 : (int)
- buf_tables 배열에서 입력받은 id를 가지는 table의 index.
- 실패 시 negative value.
- find_buffer_block_index, create_buffer_block, buffer_close_table, buffer_alloc_page, buffer_request_page 함수에서 호출된다.
- index manager의 index_find, index_insert, index_delete 함수에서 호출된다.
- db가 초기화되지 않았으면 -2를
반환
한다. - buf_tables 배열을 선형적으로 탐색하여 해당 id를 가지는 table의 index를 탐색하고 그 값을
반환
한다.- 찾지 못하면 -1을
반환
한다.
- 찾지 못하면 -1을
int evict_buffer_block(buffer_block_t* buf_block);
특정 buffer block을 buffer에서 evict한다.
- 인자
- buf_block (buffer_block_t*) : evict할 buffer block 포인터.
- 반환 : (int)
- 성공 여부.
- create_buffer_block, buffer_close_table 함수에서 호출된다.
- 해당 buffer block이 write된 상태(is_dirty)일 경우 file_write_page 함수를 call하여 실제 table file에 write한다.
- LRU list와 table에서의 buffer block linked list에서 해당 buffer block을 제거한다.
- 해당 buffer_block의 table_id 값을 0으로 변경한다. table_id는 1 이상의 값을 가지므로, 0으로 두는 것은 현재 사용하고 있지 않은 buffer_block임을 의미한다.
- 관련 변수들을 최신화한다.
- 0(성공)을
반환
한다.
int create_buffer_block(int table_id, pagenum_t pagenum, page_t buf_page, buffer_block_t **dest);
특정 table의 특정 page를 buffer에 올린다.
- 인자
- table_id (int) : buffer에 올릴 page가 속한 table의 id.
- pagenum (pagenum_t) : buffer에 올릴 page의 page number.
- buf_page (page_t) : buffer에 올릴 page 구조체.
- dest (buffer_block_t**) : caller가, 해당 page를 올린 buffer_block 포인터를 받을, 포인터 변수의 주소.
- 반환 : (int)
- 성공 여부.
- buffer_request_page 함수에서 호출된다.
- buffer pool의 full 여부를 판단한다.
- full일 경우, LRU policy에 따라 선택된 page를 evict_buffer_block 함수를 call하여 evict하고 빈 buffer block을 얻는다.
- full이 아닐 경우, 현재 사용하고 있지 않은, 즉 빈 buffer block을 탐색한다.
- 해당 buffer block을 입력받은 값 등으로 초기화한다.
- 해당 buffer block을 LRU list의 head로 설정한다.
- 해당 buffer block을 해당 table의 buffer block linked list에 추가한다.
- 관련 변수들을 최신화한다.
- 입력받은, caller가, 해당 page를 올린 buffer_block 포인터를 받을, 포인터 변수에 현재 buffer block 포인터 값을 대입하고, 0(성공)을
반환
한다.
int buffer_init_db(int buf_num);
DB를 초기화, 즉 DBMS의 실행을 시작한다.
- 인자
- buf_num (int) : 초기화하는 DBMS가 가질 buffer pool의 size.
- 반환 : (int)
- 성공 여부.
- index manager의 index_init_db 함수(wrapper function)에서 호출된다.
- DB가 이미 초기화되어 있으면 -1을
반환
한다. - 입력받은 buf_num 개수만큼의 buffer block들로 buffer pool을 동적 할당한다.
- 할당에 실패하면 -2를
반환
한다.
- buffer block들의 table_id 값을 0으로, 즉 빈 buffer block으로 초기화한다.
- 관련 변수들을 최신화한다.
- 0(성공)을
반환
한다.
int buffer_shutdown_db(void);
DBMS의 실행을 종료한다.
- 반환 : (int)
- 성공 여부.
- index manager의 index_shutdown_db 함수(wrapper function)에서 호출된다.
- DB가 초기화되어 있지 않으면, 즉 현재 DBMS가 실행 중이 아니면 -1을
반환
한다. - buffer_close_table을 호출하여 모든 table을 닫는다.
- close table 중 오류 발생 시 -2를
반환
한다.
- buffer pool의 할당을 해제한다.
- 관련 변수를 최신화한다.
- 0(성공)을
반환
한다.
int buffer_open_table(char* pathname);
특정 table을 연다.
- 인자
- pathname (char*) : 열고자 하는 table file의 pathname.
- 반환 : (int)
- 성공 여부.
- index manager의 index_open_table 함수(wrapper function)에서 호출된다.
- DB가 초기화되어 있지 않으면, 즉 현재 DBMS가 실행 중이 아니면 -1을
반환
한다. - 테이블이 최대 개수(현재 10)만큼 열려 있으면 -2를
반환
한다. - file_open_table 함수를 call하여 파일을 열고 file descriptor (fd) 값을 받는다.
- 실패(fd < 0) 시 -3을
반환
한다.
- 관련 변수와 해당 table에 사용할 buffer_table 구조체를 최신화/초기화한다. table_id는 fd 값 + 1로 한다. (fd 값은 0부터 시작하므로, table_id 값을 1 이상으로 하기 위해서.) (일반적으로 0, 1, 2는 stdin, stdout, stderr의 용도로 사용되어 3부터 시작된다고 볼 수 있으나, 이는 변경될 수 있는 부분이므로.)
- table_id를
반환
한다.
int buffer_close_table(int table_id);
현재 열려 있는 특정 table을 닫는다.
- 인자
- table_id (int) : 닫고자 하는 table의 id.
- 반환 : (int)
- 성공 여부.
- buffer_shutdown_db 함수에서 호출된다.
- index manager의 index_open_table 함수(wrapper function)에서 호출된다.
- DB가 초기화되어 있지 않으면, 즉 현재 DBMS가 실행 중이 아니면 -1을
반환
한다. - find_table_index 함수를 call하여, buf_tables 배열에서, 입력받은 table_id에 해당하는 table의 index를 확인한다.
- 해당 table이 존재하지 않을 시 -2를
반환
한다.
- 해당 table에 속하는 모든 buffer block의 pinned 여부를 확인하고, 없을 시 모두 pin을 꽂는다.
- pin이 꽂힌 buffer block이 있을 경우 -3을
반환
한다. (현재 단일 유저가 close_table을 시도했을 때 pin이 꽂힌 buffer block이 있는 것은 처리가 잘못된 것이므로 에러를 반환하나, 추후 여러 유저가 동시에 접근할 수 있게 될 경우 waiting을 주는 등의 방법으로 교체해야 한다.)
- evict_buffer_block 함수를 call하여 해당 table에 속하는 모든 buffer block을 evict한다.
- evict 중 에러 발생 시 -4를
반환
한다.
- file_close_table 함수를 call하여 실제 테이블 파일을 닫는다.
- 실패 시 -5를
반환
한다.
- buf_tables 배열에서 해당 buffer_table 구조체를 제거한다.
- 0(성공)을
반환
한다.
pagenum_t buffer_alloc_page(int table_id);
특정 table에 새로운 page를 allocate한다.
- 인자
- table_id (int) : 새로운 page를 allocate할 table의 id.
- 반환 : (pagenum_t)
- 할당된 page의 page number. page number는 0부터 시작하므로, 음수를 error value로 사용한다.
- index manager의 index_start_new_tree, index_insert_into_leaf_after_splitting, index_insert_into_new_root, index_insert_into_internal_after_splitting 함수에서 호출된다.
- free page 존재 여부를 확인한다.
- 존재하지 않을 시, 파일 끝에 새로운 page를 생성한다. 실제 file write가 이루어진다.
- 존재할 시, free page list에서 1개 page를 추출하여 사용하도록 한다.
- 이 과정 중 file_write_page 함수와 buffer_request_page 함수가 사용된다. 해당 함수들에서 오류 발생 시, 음수값을
반환
한다.
- 할당된 page의 page number를
반환
한다.
int buffer_free_page(int table_id, pagenum_t pagenum);
특정 table의 특정 page를 free한다.
- 인자
- table_id (int) : 특정 page를 free할 table의 id.
- pagenum (pagenum_t) : free할 page의 page number.
- 반환 : (int)
- 성공 여부.
- index manager의 index_remove_entry_from_internal, index_adjust_root 함수에서 호출된다.
- 입력받은 page를 free page list에 추가한다.
- 이 과정 중 buffer_request_page 함수가 사용된다. 해당 함수들에서 오류 발생 시, 음수값을
반환
한다.
- 관련 변수들을 최신화한다.
- 0(성공)을
반환
한다.
int buffer_request_page(int table_id, pagenum_t pagenum, buffer_block_t** dest);
상위 layer의 page 요청에 응한다.
- 인자
- table_id (int) : 요청하는 page가 속한 table의 id.
- pagenum (pagenum_t) : 요청하는 page의 page number.
- dest (buffer_block_t**) : caller가, 해당 page의 buffer_block 포인터를 받을, 포인터 변수의 주소.
- 반환 : (int)
- 성공 여부.
- buffer_alloc_page, buffer_free_page 함수에서 호출된다.
- index manager의 index_find, index_find_leaf, index_insert, index_start_new_tree, index_insert_into_leaf_after_splitting, index_insert_into_parent, index_insert_into_new_root, index_insert_into_internal_after_splitting, index_delete_record, index_delete_entry, index_remove_entry_from_internal, index_adjust_root, index_merge_pages, index_redistribute_pages, index_find_left_leaf 함수에서 호출된다.
- DB가 초기화되어 있지 않으면, 즉 현재 DBMS가 실행 중이 아니면 -1을
반환
한다. - 해당 table이 열려있지 않으면 -2를
반환
한다. - 해당 table의 buffer block linked list에서 해당 page를 탐색한다.
- 존재하지 않을 시, file_read_page 함수를 call하여 table 파일에서 해당 page를 읽고, create_buffer_block 함수를 call하여 이를 buffer에 올린다. 이 과정 중 오류 발생 시 음수를
반환
한다.
- 해당 buffer에 pin을 꽂는다.
- 입력받은, caller가, 해당 page의 buffer_block 포인터를 받을, 포인터 변수에 해당 buffer block 포인터 값을 대입하고, 0(성공)을
반환
한다.
int buffer_release_page(buffer_block_t* buf_block, bool is_writed);
상위 layer의 page 해제 요청에 응한다.
- 인자
- buf_block (buffer_block_t*) : 해제할 page의 buffer block 구조체 포인터.
- is_writed (bool) : request 되어 지금 release 될 때까지 page에 수정이, 즉 write가 이루어졌는지 여부.
- 반환 : (int)
- 성공 여부.
- buffer_alloc_page, buffer_free_page 함수에서 호출된다.
- index manager의 index_find, index_find_leaf, index_start_new_tree, index_insert_into_leaf, index_insert_into_parent, index_insert_into_new_root, index_insert_into_internal, index_insert_into_internal_after_splitting, index_delete_record, index_delete_entry, index_remove_entry_from_internal, index_adjust_root, index_merge_pages, index_redistribute_pages, index_find_left_leaf 함수에서 호출된다.
- 해당 buffer block에 pin이 꽂혀있지 않으면 -1을
반환
한다. (caller에서 해당 page를 request하여 pin을 꽂고 사용하다가 이를 마치고 unpin하기 위해 release하는데, unpin된 상태의 buffer block 입력은 비정상적인 상황이다.) - is_writed 인자가 true이면 buffer block의 is_dirty 값을 true로 변경한다.
- 해당 buffer block을 LRU list의 head가 되도록 한다.
- pin을 제거하고 0(성공)을
반환
한다.
disk space manager는 위의 [수정사항](# 4-disk-space manager-수정) 외에는 project2에서와 매우 유사하게 동작하며 on-disk pages, 즉 table file들을 직접적으로 관리한다. 이를 수행하기 위한 구조체와 함수 등으로 이루어져 있다.
- 현재 구현한 buffer manager는 table별로 linked list를 만들어서 각 buffer block을 관리하고 있다.
- buffer block의 수가 증가하면, 매 request마다 해당 table에서 특정 page를 탐색할 때 linked list를 선형적으로 탐색하면서 많은 delay가 발생한다.
- 실제로 10만 / 100만 개의 중복 없는 랜덤 insert / find / delete 테스트를 하였을 때, buffer block 수 400 > 1000 > 4000 개 순으로 실행 속도가 빠름을 확인하였다. 즉 미흡한 구현으로 인해 buffer가 클수록 속도가 느려지는 비정상적인 상태이다.
- 따라서, hash 등의 방법을 사용하여 buffer pool 탐색 속도를 개선할 필요가 있다.
- 또한, buffer에 빈 공간이 있을 때 request에서 disk read를 위해 빈 buffer block을 찾아야 하는데, 현재 빈 공간을 찾을 때 buffer pool array를 선형적으로 탐색하고 있으므로 이 부분도 개선이 필요하다.
- 현재 구현된 index_find_leaf와 index_find 함수는 특정 key를 가지고 entry / record 탐색 시 선형적으로 탐색하고 있다.
- B+tree는 정렬된 형태이므로, binary search 등의 방법을 통해 탐색 속도을 개선할 수 있다.
- 현재 merge를 최대한 줄이기 위해 redistribute을 우선으로 실행하고 있다.
- 하지만 merge보다 redistribute의 반복이 I/O를 더 많이 발생시킬 수 있으므로, 이를 확인해보고 무엇을 우선으로 할 때 더 효율적일지 판단하여 적용해야 한다.
- 현재 구현은 redistribute을 우선으로 한다는 가정 하에 이루어져 있으므로, merge 우선을 선택할 시 merge / redistribute 함수의 수정이 필요하다.
- 현재 각각의 함수에서 에러 발생 시 그에 대한 적절한 조치 없이 단순히 음수값을 반환하는 등 에러 체크가 미흡한 상태이다.
- 추후 recovery를 적용할 것을 고려하며 함수들의 각 위치에서 에러가 발생했을 때 해당 transaction의 수행을 취소하고 그 전 상태로 온전히 복구하는 등의 조치가 필요하다.
- 현재 서로 유사한 기능을 가진 함수들 / 특정 기능을 불필요하게 여러 함수로 나누어 수행하는 경우 등이 존재하므로 정리가 필요하다.