Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paged File, I/O System Implementation #6

Open
wants to merge 23 commits into
base: jazzloki
Choose a base branch
from

Conversation

seokmyungham
Copy link
Collaborator

@seokmyungham seokmyungham commented Jan 15, 2025

페이지 메타데이터를 정의하고 스토리지 엔진 버퍼 풀을 설계하였습니다.
더 빨리 PR을 올리고 싶었는데 독감 이슈와 더불어 학습할 내용이 점점 늘어나더니 시간이 길어져 볼륨을 조절하지 못했네요 😅

주요 구현 내용은 다음과 같습니다.

1. Page Structure

image

페이지 구조는 MySQL InnoDB 스토리지 엔진의 내용을 참고하였습니다.
이전에 같이 MySQL을 학습하기도 했고 오픈 소스 기술이다보니 학술적으로 참고할 수 있는 문서가 많았습니다.

일반적인 16KB 크기의 페이지를 설계하였고 페이지 메타데이터 중 아직 필요성을 느끼지 못한 몇몇 필드들은 제외하였습니다. (Page Directory, Fil Trailer)

2. ByteBuffer를 이용한 직렬화

Page 객체를 읽고 쓰기 위해 바이트로 변환하는 과정에서 java.nio 패키지의 ByteBuffer를 사용하였습니다.

자바 직렬화 방법에는 java.io가 제공하는 Serializable 마커 인터페이스를 구현하는 방법이 대표적이지만 객체를 바이트 스트림으로 변환하는 과정에서 추가적으로 발생하는 여러 시스템 비용들을 확인하게 되었습니다. 그 과정에서 데이터베이스 특성에 맞는 더 저수준의 API를 사용할 필요성을 느끼게 되었습니다.

ByteBuffer 할당 방식에는 allocate() 를 통한 힙 버퍼, allocateDirect()를 통한 다이렉트 버퍼가 존재합니다. 힙 버퍼는 JVM 힙 메모리 공간을 할당하여 일반적인 객체처럼 GC의 관리를 받는 방식이고, 다이렉트 버퍼는 JVM 힙 외부의 네이티브 메모리에 공간을 할당하는 방식입니다.

JVM은 OS 커널 버퍼에 직접 접근하지 못하므로 일반적인 Java I/O 방식에서는 커널 버퍼에 있는 내용을 JVM 내부 버퍼로 데이터를 복사하는 과정이 필요합니다. 이를 위해 CPU가 개입하게 되고 그동안 I/O 작업을 수행하던 Thread는 Blocking 상태에 빠지게 됩니다. CPU 자원 소모, Blocking과 더불어 복사 과정에 사용한 메모리를 정리해야 하므로 GC도 수행되니 속도가 느릴 수 밖에 없는 것입니다.

반면에 다이렉트 버퍼는 JVM 외부에 있기 때문에 커널 버퍼를 직접 핸들링할 수 있고 빠른 입출력 작업이 가능합니다. 그렇지만 네이티브 메모리를 사용한다는 것은 GC의 대상이 아니라는 뜻이고 관리를 잘하지 못하면 잦은 OOM 문제가 발생할 수 있습니다. javadoc에서는 이러한 내용을 경고하고 있습니다. 그리고 생명 주기가 길고 큰 버퍼에만 다이렉트 버퍼를 사용할 것을 권장합니다.

A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.
https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html

따라서 우선적으로 현재 프로젝트에는 힙 버퍼를 사용하는 것을 선택했고 메모리를 한정할 수 있도록 ByteBufferPool을 구현하였습니다. 추후 기능이 완성되면 벤치마크를 진행하여 힙 버퍼다이렉트 버퍼의 차이를 비교해서 성능을 개선할 수 있을 것 같습니다.

3. Page 교체 전략

처음에는 일반적인 LRU 방식을 구현하였습니다. LRU는 시간적 지역성을 활용하여 캐시 히트율을 높일 수 있지만, 다량의 페이지를 순차 스캔할 시 기존 캐시에 존재하는 핫 페이지들이 제거되는 문제를 가지고 있습니다. 실제로 InnoDB 스토리지 엔진은 버퍼 풀의 스캔 저항성을 확보하고자 LRU와 MRU가 결합된 방식을 사용하는데 이를 자바 코드로 구현해봤습니다.

@seokmyungham seokmyungham added the enhancement New feature or request label Jan 15, 2025
@seokmyungham seokmyungham self-assigned this Jan 15, 2025
@seokmyungham seokmyungham changed the base branch from main to jazzloki January 15, 2025 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant