[CBRD-26609] implement OOS delete API#6909
Conversation
🧪 TC Test Environment ReadyCircleCI Testing:
TC Repositories & Branches:
Next Steps:
|
|
/run sql medium |
vimkim
left a comment
There was a problem hiding this comment.
전체적인 구현 리뷰입니다. 주요 포인트를 각 라인 코멘트로 남겼습니다.
| static void | ||
| oos_log_delete_physical (THREAD_ENTRY *thread_p, PAGE_PTR page_p, PGSLOTID slotid, RECDES *recdes_p) | ||
| { | ||
| LOG_DATA_ADDR log_addr; |
There was a problem hiding this comment.
[WAL 설계] undo/redo 역할 분담
RVOOS_DELETE 로그의 undo/redo 구성:
- undo data (
recdes_p) — rollback 시oos_rv_redo_insert가 이 레코드를 그대로 재삽입하여 삭제 이전 상태로 복원 - redo data (
NULL) — crash recovery 시oos_rv_redo_delete는rcv->offset(= slotid)만으로spage_delete를 재실행할 수 있으므로 별도 데이터 불필요
recovery.c 테이블에 이미 등록된 핸들러:
RVOOS_DELETE → undo: oos_rv_redo_insert / redo: oos_rv_redo_delete
신규 핸들러 추가 없이 기존 인프라를 그대로 활용한다.
| } | ||
|
|
||
| int | ||
| oos_delete (THREAD_ENTRY *thread_p, const VFID &oos_vfid, const OID &oid) |
There was a problem hiding this comment.
[oos_delete] multi-chunk 체인 순회
OOS 레코드가 여러 페이지에 걸쳐 저장된 경우(across-pages), next_chunk_oid를 따라 연결된 모든 청크를 순서대로 삭제한다.
체인 종료 조건: next_chunk_oid.pageid == NULL_PAGEID (마지막 청크의 헤더에 저장된 값)
반복 흐름:
head → chunk[0] → chunk[1] → ... → chunk[N] (next=NULL)
각 청크를 독립적으로 fix → log → delete → unfix 처리한다.
| return ER_FAILED; | ||
| } | ||
|
|
||
| scope_exit page_unfixer ([&] () |
There was a problem hiding this comment.
[RAII] scope_exit로 페이지 unfix 보장
scope_exit를 사용하여 이후 경로에서 에러가 발생하더라도 반드시 pgbuf_unfix가 호출되도록 한다.
pgbuf_unfix_and_init_after_check는 unfix 후 포인터를 nullptr로 초기화하여 dangling pointer 접근을 방지한다.
| }); | ||
|
|
||
| RECDES recdes_with_header; | ||
| SCAN_CODE code = spage_get_record (thread_p, page_ptr, slotid, &recdes_with_header, PEEK); |
There was a problem hiding this comment.
[순서] PEEK → next_chunk_oid 확보 → log → delete
spage_delete 호출 이전에 반드시 spage_get_record(PEEK)를 먼저 수행해야 한다.
이유: OOS_RECORD_HEADER 안에 있는 next_chunk_oid는 삭제 후에는 읽을 수 없다. PEEK로 헤더를 미리 복사해 두어야 다음 청크로 이동할 수 있다.
또한 PEEK로 가져온 recdes_with_header가 WAL undo data로 그대로 전달되므로, 별도 복사 없이 효율적으로 처리된다.
| std::memcpy (&header, recdes_with_header.data, sizeof (OOS_RECORD_HEADER)); | ||
| OID next_chunk_oid = header.next_chunk_oid; | ||
|
|
||
| oos_log_delete_physical (thread_p, page_ptr, slotid, &recdes_with_header); |
There was a problem hiding this comment.
[WAL 원칙] 로그는 반드시 실제 변경 이전에 기록
spage_delete 호출 전에 oos_log_delete_physical을 먼저 호출하는 것은 WAL(Write-Ahead Logging) 원칙을 지키기 위함이다.
crash가 로그 기록과 spage_delete 사이에 발생하면: redo 로그가 없으므로 삭제가 재실행되지 않고 레코드가 보존됨 → 안전
crash가 spage_delete 이후에 발생하면: redo 로그로 spage_delete를 재실행하여 일관성 유지
| extern int oos_file_destroy (THREAD_ENTRY *thread_p, const VFID &oos_vfid); | ||
| extern int oos_insert (THREAD_ENTRY *thread_p, const VFID &oos_vfid, RECDES &recdes, OID &oid); | ||
| extern int oos_read (THREAD_ENTRY *thread_p, const OID &oid, RECDES &recdes); | ||
| extern int oos_delete (THREAD_ENTRY *thread_p, const VFID &oos_vfid, const OID &oid); |
There was a problem hiding this comment.
[API] oos_vfid 파라미터 용도
현재 oos_delete 구현 내에서 oos_vfid는 직접 사용되지 않는다.
추후 확장 가능성을 위해 시그니처에 포함하였다 (예: 특정 VFID에 속한 페이지임을 검증하거나, 파일 레벨 통계 업데이트 등).
There was a problem hiding this comment.
이 코멘트 이후 수정 반영됨:
oos_log_delete_physical에 VFID *vfid_p 파라미터를 추가하고, oos_delete 호출부에서 const_cast<VFID *>(&oos_vfid)를 전달하도록 변경.
이제 oos_vfid가 실제로 WAL 로그에 기록되며, oos_log_insert_physical과 대칭적인 구조가 됨.
|
|
||
| // Peek the header of the first chunk to find the next chunk OID | ||
| OOS_RECORD_HEADER head_header{}; | ||
| err = peek_oos_header (head_oid, head_header); |
There was a problem hiding this comment.
[테스트 설계] 삭제 전에 next_chunk_oid를 미리 확보
oos_delete 호출 후에는 청크가 이미 삭제되어 헤더를 읽을 수 없다.
따라서 삭제 전에 peek_oos_header로 next_chunk_oid를 먼저 가져와 두고, 삭제 후 두 페이지(head, next)의 free space 변화를 각각 검증한다.
| err = oos_delete (thread_p, oos_vfid, target_oid); | ||
| ASSERT_EQ (err, NO_ERROR); | ||
|
|
||
| int free_after_delete = get_free_space_of_oid_page (target_oid); |
There was a problem hiding this comment.
[주의] spage_delete 후 free space가 완전히 복원되지 않는 이유
spage_insert는 레코드 데이터 + 슬롯 엔트리(SPAGE_SLOT, 4 bytes)를 함께 소비한다.
반면 spage_delete는 레코드 데이터만 total_free에 반환하고, 슬롯 엔트리는 REC_DELETED_WILL_REUSE 상태의 tombstone으로 남겨 재사용 대기 상태로 전환한다.
따라서 이 테스트에서는 "레코드 데이터 크기만큼 복구되었는가"를 검증하며, 완전한 free space 복원은 spage_compact 이후에 가능하다.
PR #6909 코드 리뷰:
|
| undo (rollback) | redo (crash recovery) | |
|---|---|---|
RVOOS_INSERT |
undo=NULL, redo=recdes | undo→delete, redo→insert |
RVOOS_DELETE |
undo=recdes, redo=NULL | undo→insert, redo→delete |
recovery.c의 등록 테이블:
{RVOOS_DELETE, "RVOOS_DELETE", oos_rv_redo_insert, oos_rv_redo_delete, NULL, NULL}
- Redo (크래시 복구):
oos_rv_redo_delete→spage_delete수행 — 커밋된 삭제를 재적용 - Undo (롤백):
oos_rv_redo_insert→spage_insert_for_recovery로 레코드 복원
INSERT와 DELETE가 정확히 역연산 관계이므로 recovery 함수를 교차 사용하는 것이 올바름.
3. 발견 사항
[Medium] oos_vfid 파라미터 미사용
oos_delete 시그니처에 const VFID &oos_vfid가 있지만, 함수 본문에서 전혀 사용되지 않음. oos_log_delete_physical에서도 log_addr.vfid = NULL로 설정.
비교: oos_log_insert_physical에서는 log_addr.vfid = vfid_p로 VFID를 설정.
확인 필요: DELETE 로깅에서 vfid가 NULL인 것이 의도적인지? INSERT와 대칭적으로 VFID를 기록해야 recovery나 replication에서 필요할 수 있음. 의도적으로 불필요하다면 코멘트가 있으면 좋겠음.
[Low] 멀티 chunk 삭제 시 atomicity
각 chunk을 개별적으로 fix→log→delete→unfix. 중간 chunk 삭제 후 크래시 시:
- 커밋 전 크래시: 트랜잭션 rollback 시 undo 로그로 각 chunk 복원 → 안전
- 커밋 후 크래시: redo 로그로 남은 chunk들도 삭제 → 안전
WAL의 undo/redo가 chunk 단위로 기록되므로 정확히 동작.
[Info] 테스트 커버리지 — 양호
| 테스트 | 검증 내용 |
|---|---|
OosDeleteBasic |
단일 chunk 삭제 후 free space 증가 |
OosDeleteThenReadFails |
삭제 후 read 실패 확인 |
OosDeleteMultiChunk |
2-chunk 레코드의 양쪽 페이지 free space 회수 |
OosUpdatePattern |
UPDATE 시나리오 (insert new → delete old) |
OosDeleteRestoresFreeSpace |
free space 정밀 비교 (slot tombstone 고려) |
OosDeleteLarge160KBMultiChunk |
160KB 대형 레코드 (다수 chunk) 삭제 |
4. 결론
코드 품질이 높고, 기존 oos_insert/oos_read와 일관된 패턴을 따름. WAL 로깅의 undo/redo 대칭성이 정확하고, scope_exit를 통한 리소스 관리도 깔끔.
주요 확인 필요 사항:
log_addr.vfid = NULL— DELETE에서 VFID를 기록하지 않는 것이 의도적인지 (INSERT와의 비대칭)oos_vfid파라미터가 미사용인 채로 남아도 되는지 (향후 확장용?)
http://jira.cubrid.org/browse/CBRD-26609
Description
Milestone 1에서 OOS insert/read는 구현되어 있으나, OOS 레코드를 물리적으로 삭제하는
oos_deleteAPI가 없었다.이로 인해 아래 두 경로에서 OOS 레코드가 영구히 잔존하는 문제가 있었다.
UPDATEDELETE+ vacuumspage_delete를 통해 OOS 페이지의 슬롯을 물리적으로 삭제하고, 페이지의total_free를 회수하는oos_deleteAPI를 구현한다.Implementation
신규 함수
oos_deletesrc/storage/oos_file.cppoos_log_delete_physicalsrc/storage/oos_file.cppoos_delete동작 흐름Multi-chunk OOS 레코드(across-pages)를 지원하기 위해
next_chunk_oid체인을 순회하며 모든 청크를 순서대로 삭제한다.WAL 로깅 설계
RVOOS_DELETE로그 레코드는 이미recovery.h에 정의되어 있으며, 핸들러도 등록되어 있다.log_addr.pgptrlog_addr.offsetoos_rv_redo_delete가 이 값 사용)oos_rv_redo_insert로 레코드 재삽입)기존 recovery 테이블에 등록된 핸들러를 그대로 활용한다:
oos_rv_redo_insert(원본 레코드 재삽입)oos_rv_redo_delete(슬롯 삭제)단위 테스트 (
unit_tests/oos/test_oos_delete.cpp)OosDeleteBasicoos_delete성공, 삭제 후 페이지 free space 증가 확인OosDeleteThenReadFailsoos_read시 에러 반환 확인OosDeleteMultiChunkOosUpdatePatternOosDeleteRestoresFreeSpaceOosDeleteLarge160KBMultiChunk모두 통과한 모습
Remarks
spage_delete는 레코드 데이터는 해제하지만 슬롯 엔트리(SPAGE_SLOT, 4 bytes)는REC_DELETED_WILL_REUSE상태로 남긴다. 추후spage_compact를 통한 in-page compaction에서 이 공간을 재활용할 수 있다.heap_update→oos_delete,vacuum_heap→oos_delete)은 이 PR 범위에 포함되지 않는다. 해당 연결은 상위 스토리에서 진행한다.