From 4f31b1278039816b4b77c1f30d0e84718ab5be9b Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 02:06:18 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=A9=94=ED=83=80=EB=8D=B0=EC=9D=B4=ED=84=B0=20PageHeader=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=EC=99=80=20=ED=95=84=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/page/PageHeader.java | 38 +++++++++++++++++++++ src/main/java/database/page/PageType.java | 8 +++++ 2 files changed, 46 insertions(+) create mode 100644 src/main/java/database/page/PageHeader.java create mode 100644 src/main/java/database/page/PageType.java diff --git a/src/main/java/database/page/PageHeader.java b/src/main/java/database/page/PageHeader.java new file mode 100644 index 0000000..65fd429 --- /dev/null +++ b/src/main/java/database/page/PageHeader.java @@ -0,0 +1,38 @@ +package database.page; + +public class PageHeader { + + public static final int HEADER_SIZE = 13; + + private final int pageNum; + private final PageType pageType; + private final int recordCount; + private final boolean isDirty; + + public PageHeader(int pageNum, PageType pageType) { + this.pageNum = pageNum; + this.pageType = pageType; + this.recordCount = 0; + this.isDirty = false; + } + + public int getPageNum() { + return pageNum; + } + + public PageType getPageType() { + return pageType; + } + + public int getRecordCount() { + return recordCount; + } + + public boolean isDirty() { + return isDirty; + } + + public int getHeaderSize() { + return HEADER_SIZE; + } +} diff --git a/src/main/java/database/page/PageType.java b/src/main/java/database/page/PageType.java new file mode 100644 index 0000000..6ce25f7 --- /dev/null +++ b/src/main/java/database/page/PageType.java @@ -0,0 +1,8 @@ +package database.page; + +public enum PageType { + + CLUSTERED_INDEX, + SECONDARY_INDEX, + UNDO_LOG; +} From dbefaf9249265cb86fd035ff4410b814ffdf33b4 Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 02:20:21 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20=EC=A0=80=EC=9E=A5=ED=95=A0=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=9D=98=EB=AF=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A0=88=EC=BD=94=EB=93=9C=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/page/Record.java | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/database/page/Record.java diff --git a/src/main/java/database/page/Record.java b/src/main/java/database/page/Record.java new file mode 100644 index 0000000..8fdfdd4 --- /dev/null +++ b/src/main/java/database/page/Record.java @@ -0,0 +1,24 @@ +package database.page; + +public class Record { + + private final int recordId; + private final byte[] data; + + public Record(int recordId, byte[] data) { + this.recordId = recordId; + this.data = data; + } + + public int getRecordId() { + return recordId; + } + + public byte[] getData() { + return data; + } + + public int getSize() { + return data.length; + } +} From fa5bfb4d24f31298f637eabc92333823d7267f6b Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 02:23:53 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=EC=99=80=20=EA=B8=B0=EB=B3=B8=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/page/Page.java | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java index b421f9e..db1f879 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/page/Page.java @@ -1,19 +1,19 @@ package database.page; +import java.util.ArrayList; +import java.util.List; + public class Page { - private final long pageNum; - private final byte[] data; - public Page(long pageNum, byte[] data) { - this.pageNum = pageNum; - this.data = data; - } + private static final int PAGE_SIZE = 16 * 1024; - public long getPageNum() { - return pageNum; - } + private final PageHeader header; + private final List records; + private int freeSpace; - public byte[] getData() { - return data; + public Page(int pageNum, PageType pageType) { + this.header = new PageHeader(pageNum, pageType); + this.records = new ArrayList<>(); + this.freeSpace = PAGE_SIZE - header.getHeaderSize(); } } From ad34432ad9763853e1614d4610e9b71bf0dbcedb Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 02:28:45 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EB=A0=88=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=8A=94=20Page=20addRecord()=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/page/Page.java | 14 ++++++++ src/main/java/database/page/PageHeader.java | 12 +++++-- src/test/java/database/PageTest.java | 37 +++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/test/java/database/PageTest.java diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java index db1f879..d7e64f4 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/page/Page.java @@ -16,4 +16,18 @@ public Page(int pageNum, PageType pageType) { this.records = new ArrayList<>(); this.freeSpace = PAGE_SIZE - header.getHeaderSize(); } + + public boolean addRecord(Record record) { + if (freeSpace >= record.getSize()) { + records.add(record); + freeSpace -= record.getSize(); + + header.incrementRecordCount(); + header.setDirty(true); + + return true; + } else { + return false; + } + } } diff --git a/src/main/java/database/page/PageHeader.java b/src/main/java/database/page/PageHeader.java index 65fd429..2e97903 100644 --- a/src/main/java/database/page/PageHeader.java +++ b/src/main/java/database/page/PageHeader.java @@ -6,8 +6,8 @@ public class PageHeader { private final int pageNum; private final PageType pageType; - private final int recordCount; - private final boolean isDirty; + private int recordCount; + private boolean isDirty; public PageHeader(int pageNum, PageType pageType) { this.pageNum = pageNum; @@ -16,6 +16,14 @@ public PageHeader(int pageNum, PageType pageType) { this.isDirty = false; } + public void incrementRecordCount() { + this.recordCount++; + } + + public void setDirty(boolean isDirty) { + this.isDirty = isDirty; + } + public int getPageNum() { return pageNum; } diff --git a/src/test/java/database/PageTest.java b/src/test/java/database/PageTest.java new file mode 100644 index 0000000..1fe6899 --- /dev/null +++ b/src/test/java/database/PageTest.java @@ -0,0 +1,37 @@ +package database; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PageTest { + + @DisplayName("페이지에 레코드를 추가하는데 성공하면 true를 반환한다.") + @Test + void addRecordSuccessTest() { + Page page = new Page(1, PageType.CLUSTERED_INDEX); + Record record = new Record(1, new byte[100]); + + int previousSize = page.getFreeSpace(); + boolean isAdded = page.addRecord(record); + + assertAll( + () -> assertThat(isAdded).isTrue(), + () -> assertThat(page.getFreeSpace()).isEqualTo(previousSize - record.getSize()), + () -> assertThat(page.isDirty()).isTrue() + ); + } + + @DisplayName("페이지에 레코드를 추가하는데 실패하면 false를 반환한다.") + @Test + void addRecordFailTest() { + Page page = new Page(1, PageType.CLUSTERED_INDEX); + Record record = new Record(1, new byte[Page.PAGE_SIZE]); + + boolean isAdded = page.addRecord(record); + + assertThat(isAdded).isFalse(); + } +} From 05172c35229f3206093f2b927f1e7b6c3221039d Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 02:45:12 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EB=A0=88=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=ED=95=98=EB=8A=94=20Page=20deleteRecord()=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/page/Page.java | 25 ++++++++++++++++++ src/main/java/database/page/PageHeader.java | 4 +++ src/test/java/database/PageTest.java | 28 +++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java index d7e64f4..69ecb72 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/page/Page.java @@ -30,4 +30,29 @@ public boolean addRecord(Record record) { return false; } } + + public boolean deleteRecord(Record record) { + if (records.remove(record)) { + freeSpace += record.getSize(); + + header.setDirty(true); + header.decrementRecordCount(); + + return true; + } else { + return false; + } + } + + public PageHeader getHeader() { + return header; + } + + public List getRecords() { + return records; + } + + public int getFreeSpace() { + return freeSpace; + } } diff --git a/src/main/java/database/page/PageHeader.java b/src/main/java/database/page/PageHeader.java index 2e97903..d08be50 100644 --- a/src/main/java/database/page/PageHeader.java +++ b/src/main/java/database/page/PageHeader.java @@ -20,6 +20,10 @@ public void incrementRecordCount() { this.recordCount++; } + public void decrementRecordCount() { + this.recordCount--; + } + public void setDirty(boolean isDirty) { this.isDirty = isDirty; } diff --git a/src/test/java/database/PageTest.java b/src/test/java/database/PageTest.java index 1fe6899..db17419 100644 --- a/src/test/java/database/PageTest.java +++ b/src/test/java/database/PageTest.java @@ -34,4 +34,32 @@ void addRecordFailTest() { assertThat(isAdded).isFalse(); } + + @DisplayName("페이지에 레코드를 삭제하는데 성공하면 true를 반환한다.") + @Test + void deleteRecordSuccessTest() { + Page page = new Page(1, PageType.CLUSTERED_INDEX); + Record record = new Record(1, new byte[100]); + page.addRecord(record); + + int previousSize = page.getFreeSpace(); + boolean isDeleted = page.deleteRecord(record); + + assertAll( + () -> assertThat(isDeleted).isTrue(), + () -> assertThat(page.getFreeSpace()).isEqualTo(previousSize + record.getSize()), + () -> assertThat(page.isDirty()).isTrue() + ); + } + + @DisplayName("페이지에 레코드를 삭제하는데 실패하면 false를 반환한다.") + @Test + void deleteRecordFailTest() { + Page page = new Page(1, PageType.CLUSTERED_INDEX); + Record record = new Record(1, new byte[Page.PAGE_SIZE]); + + boolean isDeleted = page.deleteRecord(record); + + assertThat(isDeleted).isFalse(); + } } From 7bbf7f4091b695bdaf62f5dbfb281733180c2f1a Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 03:24:09 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20BufferPool=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EC=99=80=20LRU=20Cache=20=ED=95=84=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/BufferPool.java | 23 +++++++++++++++++++++++ src/main/java/database/page/Page.java | 17 +++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/main/java/database/BufferPool.java diff --git a/src/main/java/database/BufferPool.java b/src/main/java/database/BufferPool.java new file mode 100644 index 0000000..0392da3 --- /dev/null +++ b/src/main/java/database/BufferPool.java @@ -0,0 +1,23 @@ +package database; + +import database.page.Page; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class BufferPool { + + private static final int BUFFER_SIZE = 40; + + private static final Map bufferPool; + + static { + bufferPool = new LinkedHashMap<>(BUFFER_SIZE, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Entry eldest) { + return size() > BUFFER_SIZE && !eldest.getValue().isPinned(); + } + }; + } + +} diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java index 69ecb72..b311ee6 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/page/Page.java @@ -10,6 +10,7 @@ public class Page { private final PageHeader header; private final List records; private int freeSpace; + private int pinCount; public Page(int pageNum, PageType pageType) { this.header = new PageHeader(pageNum, pageType); @@ -44,6 +45,18 @@ public boolean deleteRecord(Record record) { } } + public void pin() { + this.pinCount++; + } + + public void unPin() { + this.pinCount--; + } + + public boolean isPinned() { + return this.pinCount > 0; + } + public PageHeader getHeader() { return header; } @@ -55,4 +68,8 @@ public List getRecords() { public int getFreeSpace() { return freeSpace; } + + public int getPinCount() { + return pinCount; + } } From c590b69b805690f78dcab2e2ca9b0beb40d6fbb6 Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 17:18:02 +0900 Subject: [PATCH 07/16] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20I/O=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EC=9D=84=20=EA=B0=96=EB=8A=94=20FileManager?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EC=A0=95=EC=9D=98=20=EB=B0=8F=20Serial?= =?UTF-8?q?izable=20=EC=83=81=EC=86=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/FileManager.java | 63 +++++++++++++++++++++ src/main/java/database/PageId.java | 39 +++++++++++++ src/main/java/database/page/Page.java | 5 +- src/main/java/database/page/PageHeader.java | 4 +- src/main/java/database/page/Record.java | 4 +- src/test/java/database/PageIdTest.java | 28 +++++++++ 6 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 src/main/java/database/FileManager.java create mode 100644 src/main/java/database/PageId.java create mode 100644 src/test/java/database/PageIdTest.java diff --git a/src/main/java/database/FileManager.java b/src/main/java/database/FileManager.java new file mode 100644 index 0000000..59798d6 --- /dev/null +++ b/src/main/java/database/FileManager.java @@ -0,0 +1,63 @@ +package database; + +import database.page.Page; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FileManager { + + private static final String DIRECTORY_PATH = "data/files/"; + private static final String FILE_EXTENSION = ".ibd"; + + public FileManager() { + createDirectory(); + } + + private void createDirectory() { + Path directory = Paths.get(DIRECTORY_PATH); + if (Files.notExists(directory)) { + try { + Files.createDirectory(directory); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public Page readPage(PageId pageId) { + Path filePath = Paths.get(DIRECTORY_PATH, pageId.getFileName() + FILE_EXTENSION); + + try (RandomAccessFile file = new RandomAccessFile(filePath.toString(), "r")) { + long offset = (long) pageId.getPageNum() * Page.PAGE_SIZE; + file.seek(offset); + + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file.getFD()))) { + return (Page) ois.readObject(); + } + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public void writePage(PageId pageId, Page page) { + Path filePath = Paths.get(DIRECTORY_PATH, pageId.getFileName() + FILE_EXTENSION); + + try (RandomAccessFile file = new RandomAccessFile(filePath.toString(), "rw")) { + long offset = (long) pageId.getPageNum() * Page.PAGE_SIZE; + file.seek(offset); + + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file.getFD()))) { + oos.writeObject(page); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/database/PageId.java b/src/main/java/database/PageId.java new file mode 100644 index 0000000..4c4cef7 --- /dev/null +++ b/src/main/java/database/PageId.java @@ -0,0 +1,39 @@ +package database; + +import java.util.Objects; + +public class PageId { + + private final String fileName; + private final int PageNum; + + public PageId(String fileName, int pageNum) { + this.fileName = fileName; + this.PageNum = pageNum; + } + + public String getFileName() { + return fileName; + } + + public int getPageNum() { + return PageNum; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PageId pageId = (PageId) o; + return PageNum == pageId.PageNum && Objects.equals(fileName, pageId.fileName); + } + + @Override + public int hashCode() { + return Objects.hash(fileName, PageNum); + } +} diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java index b311ee6..d49562a 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/page/Page.java @@ -1,11 +1,12 @@ package database.page; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class Page { +public class Page implements Serializable { - private static final int PAGE_SIZE = 16 * 1024; + public static final int PAGE_SIZE = 16 * 1024; private final PageHeader header; private final List records; diff --git a/src/main/java/database/page/PageHeader.java b/src/main/java/database/page/PageHeader.java index d08be50..6a1372c 100644 --- a/src/main/java/database/page/PageHeader.java +++ b/src/main/java/database/page/PageHeader.java @@ -1,6 +1,8 @@ package database.page; -public class PageHeader { +import java.io.Serializable; + +public class PageHeader implements Serializable { public static final int HEADER_SIZE = 13; diff --git a/src/main/java/database/page/Record.java b/src/main/java/database/page/Record.java index 8fdfdd4..d762822 100644 --- a/src/main/java/database/page/Record.java +++ b/src/main/java/database/page/Record.java @@ -1,6 +1,8 @@ package database.page; -public class Record { +import java.io.Serializable; + +public class Record implements Serializable { private final int recordId; private final byte[] data; diff --git a/src/test/java/database/PageIdTest.java b/src/test/java/database/PageIdTest.java new file mode 100644 index 0000000..a3f8230 --- /dev/null +++ b/src/test/java/database/PageIdTest.java @@ -0,0 +1,28 @@ +package database; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Objects; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PageIdTest { + + @DisplayName("내부 필드가 모두 같으면 동일한 객체이다.") + @Test + void equalsTest() { + PageId pageId1 = new PageId("jazz", 1); + PageId pageId2 = new PageId("jazz", 1); + + assertThat(Objects.equals(pageId1, pageId2)).isTrue(); + } + + @DisplayName("내부 필드가 모두 같으면 동일한 해시를 반환한다.") + @Test + void hashCodeTest() { + PageId pageId1 = new PageId("jazz", 1); + PageId pageId2 = new PageId("jazz", 1); + + assertThat(pageId1.hashCode()).isEqualTo(pageId2.hashCode()); + } +} From f07396adb3affbbccec2d62f5ec2a034708c43de Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 18:36:39 +0900 Subject: [PATCH 08/16] =?UTF-8?q?refactor:=20=EB=B2=84=ED=8D=BC=20?= =?UTF-8?q?=ED=92=80=EB=A1=9C=EB=B6=80=ED=84=B0=20LRU=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=90=EC=B2=B4=20=EC=A0=84=EB=9E=B5=EC=9D=84=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/BufferPool.java | 16 ++++------- src/main/java/database/LRUCache.java | 35 +++++++++++++++++++++++++ src/main/java/database/PageManager.java | 20 ++++++++++++++ src/main/java/database/page/Page.java | 4 +++ 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 src/main/java/database/LRUCache.java create mode 100644 src/main/java/database/PageManager.java diff --git a/src/main/java/database/BufferPool.java b/src/main/java/database/BufferPool.java index 0392da3..6a8b5ca 100644 --- a/src/main/java/database/BufferPool.java +++ b/src/main/java/database/BufferPool.java @@ -1,23 +1,17 @@ package database; import database.page.Page; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.Map.Entry; public class BufferPool { private static final int BUFFER_SIZE = 40; - private static final Map bufferPool; + private final Map bufferPool; + private final PageManager pageManager; - static { - bufferPool = new LinkedHashMap<>(BUFFER_SIZE, 0.75f, true) { - @Override - protected boolean removeEldestEntry(Entry eldest) { - return size() > BUFFER_SIZE && !eldest.getValue().isPinned(); - } - }; + public BufferPool(PageManager pageManager) { + this.bufferPool = new LRUCache<>(BUFFER_SIZE, 0.75f, true, pageManager); + this.pageManager = pageManager; } - } diff --git a/src/main/java/database/LRUCache.java b/src/main/java/database/LRUCache.java new file mode 100644 index 0000000..bc31b61 --- /dev/null +++ b/src/main/java/database/LRUCache.java @@ -0,0 +1,35 @@ +package database; + +import database.page.Page; +import java.util.LinkedHashMap; +import java.util.Map; + +public class LRUCache extends LinkedHashMap { + + private final int bufferSize; + private final PageManager pageManager; + + public LRUCache(int initialCapacity, float loadFactor, boolean accessOrder, PageManager pageManager) { + super(initialCapacity, loadFactor, accessOrder); + this.pageManager = pageManager; + this.bufferSize = initialCapacity; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + boolean isBufferPoolOverCapacity = size() > bufferSize; + boolean isUnpinned = !((Page) eldest.getValue()).isPinned(); + + if (isBufferPoolOverCapacity && isUnpinned) { + flushIfDirty((PageId) eldest.getKey(), (Page) eldest.getValue()); + return true; + } + return false; + } + + private void flushIfDirty(PageId pageId, Page page) { + if (page.isDirty()) { + pageManager.savePage(pageId, page); + } + } +} diff --git a/src/main/java/database/PageManager.java b/src/main/java/database/PageManager.java new file mode 100644 index 0000000..71f2d64 --- /dev/null +++ b/src/main/java/database/PageManager.java @@ -0,0 +1,20 @@ +package database; + +import database.page.Page; + +public class PageManager { + + private final FileManager fileManager; + + public PageManager(FileManager fileManager) { + this.fileManager = fileManager; + } + + public Page loadPage(PageId pageId) { + return fileManager.readPage(pageId); + } + + public void savePage(PageId pageId, Page page) { + fileManager.writePage(pageId, page); + } +} diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java index d49562a..c5e48cb 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/page/Page.java @@ -73,4 +73,8 @@ public int getFreeSpace() { public int getPinCount() { return pinCount; } + + public boolean isDirty() { + return header.isDirty(); + } } From 83a2755f736112f394bee38e05e88d27208e38b7 Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 18:39:34 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat:=20=EB=B2=84=ED=8D=BC=20=ED=92=80?= =?UTF-8?q?=EB=A1=9C=EB=B6=80=ED=84=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20getPage()=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/BufferPool.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/database/BufferPool.java b/src/main/java/database/BufferPool.java index 6a8b5ca..56babd1 100644 --- a/src/main/java/database/BufferPool.java +++ b/src/main/java/database/BufferPool.java @@ -14,4 +14,10 @@ public BufferPool(PageManager pageManager) { this.bufferPool = new LRUCache<>(BUFFER_SIZE, 0.75f, true, pageManager); this.pageManager = pageManager; } + + public Page getPage(PageId pageId) { + Page page = bufferPool.computeIfAbsent(pageId, id -> pageManager.loadPage(pageId)); + page.pin(); + return page; + } } From 9af29d56d7401ec87e940d0e09f3a6afd38644b0 Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 20:18:52 +0900 Subject: [PATCH 10/16] =?UTF-8?q?feat:=20=EB=B2=84=ED=8D=BC=20=ED=92=80=20?= =?UTF-8?q?=ED=94=8C=EB=9F=AC=EC=8B=9C=20flush()=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/BufferPool.java | 14 ++++++++++++++ src/main/java/database/page/Page.java | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/database/BufferPool.java b/src/main/java/database/BufferPool.java index 56babd1..28d7de9 100644 --- a/src/main/java/database/BufferPool.java +++ b/src/main/java/database/BufferPool.java @@ -20,4 +20,18 @@ public Page getPage(PageId pageId) { page.pin(); return page; } + + /** + * 플러시 리스트 플러시 + */ + public void flush() { + for (Map.Entry entry : bufferPool.entrySet()) { + Page page = entry.getValue(); + + if (!page.isPinned() && page.isDirty()) { + pageManager.savePage(entry.getKey(), page); + page.setDirty(false); + } + } + } } diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java index c5e48cb..1e9c408 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/page/Page.java @@ -74,6 +74,10 @@ public int getPinCount() { return pinCount; } + public void setDirty(boolean isDirty) { + header.setDirty(isDirty); + } + public boolean isDirty() { return header.isDirty(); } From 426db15483a11d6c271d1f134fe1f4991e36f596 Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 20:25:19 +0900 Subject: [PATCH 11/16] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98=EB=8A=94=20modifyPage()?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/BufferPool.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/database/BufferPool.java b/src/main/java/database/BufferPool.java index 28d7de9..9ad98c2 100644 --- a/src/main/java/database/BufferPool.java +++ b/src/main/java/database/BufferPool.java @@ -21,6 +21,15 @@ public Page getPage(PageId pageId) { return page; } + public void modifyPage(PageId pageId) { + Page page = getPage(pageId); + /* + ..modify.. + */ + page.setDirty(true); + page.unPin(); + } + /** * 플러시 리스트 플러시 */ From 8eedadb2a8a05f53f1e7c00f08967b5c40785ba2 Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 20:26:09 +0900 Subject: [PATCH 12/16] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/BufferPool.java | 1 - src/main/java/database/FileManager.java | 1 - src/main/java/database/LRUCache.java | 1 - src/main/java/database/{page => }/Page.java | 2 +- src/main/java/database/{page => }/PageHeader.java | 2 +- src/main/java/database/PageManager.java | 2 -- src/main/java/database/{page => }/PageType.java | 2 +- src/main/java/database/{page => }/Record.java | 2 +- 8 files changed, 4 insertions(+), 9 deletions(-) rename src/main/java/database/{page => }/Page.java (98%) rename src/main/java/database/{page => }/PageHeader.java (97%) rename src/main/java/database/{page => }/PageType.java (78%) rename src/main/java/database/{page => }/Record.java (95%) diff --git a/src/main/java/database/BufferPool.java b/src/main/java/database/BufferPool.java index 9ad98c2..812efee 100644 --- a/src/main/java/database/BufferPool.java +++ b/src/main/java/database/BufferPool.java @@ -1,6 +1,5 @@ package database; -import database.page.Page; import java.util.Map; public class BufferPool { diff --git a/src/main/java/database/FileManager.java b/src/main/java/database/FileManager.java index 59798d6..e5237c2 100644 --- a/src/main/java/database/FileManager.java +++ b/src/main/java/database/FileManager.java @@ -1,6 +1,5 @@ package database; -import database.page.Page; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; diff --git a/src/main/java/database/LRUCache.java b/src/main/java/database/LRUCache.java index bc31b61..6961016 100644 --- a/src/main/java/database/LRUCache.java +++ b/src/main/java/database/LRUCache.java @@ -1,6 +1,5 @@ package database; -import database.page.Page; import java.util.LinkedHashMap; import java.util.Map; diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/Page.java similarity index 98% rename from src/main/java/database/page/Page.java rename to src/main/java/database/Page.java index 1e9c408..969aeaf 100644 --- a/src/main/java/database/page/Page.java +++ b/src/main/java/database/Page.java @@ -1,4 +1,4 @@ -package database.page; +package database; import java.io.Serializable; import java.util.ArrayList; diff --git a/src/main/java/database/page/PageHeader.java b/src/main/java/database/PageHeader.java similarity index 97% rename from src/main/java/database/page/PageHeader.java rename to src/main/java/database/PageHeader.java index 6a1372c..fa225d8 100644 --- a/src/main/java/database/page/PageHeader.java +++ b/src/main/java/database/PageHeader.java @@ -1,4 +1,4 @@ -package database.page; +package database; import java.io.Serializable; diff --git a/src/main/java/database/PageManager.java b/src/main/java/database/PageManager.java index 71f2d64..2e366ef 100644 --- a/src/main/java/database/PageManager.java +++ b/src/main/java/database/PageManager.java @@ -1,7 +1,5 @@ package database; -import database.page.Page; - public class PageManager { private final FileManager fileManager; diff --git a/src/main/java/database/page/PageType.java b/src/main/java/database/PageType.java similarity index 78% rename from src/main/java/database/page/PageType.java rename to src/main/java/database/PageType.java index 6ce25f7..3ae70bf 100644 --- a/src/main/java/database/page/PageType.java +++ b/src/main/java/database/PageType.java @@ -1,4 +1,4 @@ -package database.page; +package database; public enum PageType { diff --git a/src/main/java/database/page/Record.java b/src/main/java/database/Record.java similarity index 95% rename from src/main/java/database/page/Record.java rename to src/main/java/database/Record.java index d762822..83da838 100644 --- a/src/main/java/database/page/Record.java +++ b/src/main/java/database/Record.java @@ -1,4 +1,4 @@ -package database.page; +package database; import java.io.Serializable; From 51ad9148c12437642f81aa5820810f592c40160f Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 22:38:31 +0900 Subject: [PATCH 13/16] =?UTF-8?q?fix:=20createDirectories=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EC=83=81=EC=9C=84=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=EA=B0=80=20=EB=A7=8C=EB=93=A4?= =?UTF-8?q?=EC=96=B4=EC=A7=80=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=EB=A5=BC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/database/FileManager.java | 2 +- src/main/java/database/Page.java | 8 +++++ src/test/java/database/FileManagerTest.java | 37 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/test/java/database/FileManagerTest.java diff --git a/src/main/java/database/FileManager.java b/src/main/java/database/FileManager.java index e5237c2..f2680fe 100644 --- a/src/main/java/database/FileManager.java +++ b/src/main/java/database/FileManager.java @@ -23,7 +23,7 @@ private void createDirectory() { Path directory = Paths.get(DIRECTORY_PATH); if (Files.notExists(directory)) { try { - Files.createDirectory(directory); + Files.createDirectories(directory); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/database/Page.java b/src/main/java/database/Page.java index 969aeaf..c184bdc 100644 --- a/src/main/java/database/Page.java +++ b/src/main/java/database/Page.java @@ -81,4 +81,12 @@ public void setDirty(boolean isDirty) { public boolean isDirty() { return header.isDirty(); } + + public PageType getPageType() { + return header.getPageType(); + } + + public int getPageNum() { + return header.getPageNum(); + } } diff --git a/src/test/java/database/FileManagerTest.java b/src/test/java/database/FileManagerTest.java new file mode 100644 index 0000000..4d350f2 --- /dev/null +++ b/src/test/java/database/FileManagerTest.java @@ -0,0 +1,37 @@ +package database; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FileManagerTest { + + private FileManager fileManager; + private PageId pageId; + private Page page; + + @BeforeEach + void setUp() { + fileManager = new FileManager(); + pageId = new PageId("jazz", 1); + page = new Page(1, PageType.SECONDARY_INDEX); + } + + @DisplayName("파일에 페이지를 저장하고, 읽어올 수 있다.") + @Test + void writePageTest() { + fileManager.writePage(pageId, page); + + Page readPage = fileManager.readPage(pageId); + + assertAll( + () -> assertThat(readPage.getPageType()).isEqualTo(page.getPageType()), + () -> assertThat(readPage.getPageNum()).isEqualTo(page.getPageNum()), + () -> assertThat(readPage.getFreeSpace()).isEqualTo(page.getFreeSpace()) + ); + } +} From 04a334469f09312c0df599ad575801a04295e28f Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 22:38:50 +0900 Subject: [PATCH 14/16] =?UTF-8?q?test:=20LRUCache=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/database/LRUCacheTest.java | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/test/java/database/LRUCacheTest.java diff --git a/src/test/java/database/LRUCacheTest.java b/src/test/java/database/LRUCacheTest.java new file mode 100644 index 0000000..0277ed0 --- /dev/null +++ b/src/test/java/database/LRUCacheTest.java @@ -0,0 +1,49 @@ +package database; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LRUCacheTest { + + private LRUCache lruCache; + + private PageId pageId1; + private Page page1; + private PageId pageId2; + private Page page2; + + @BeforeEach + void setUp() { + FileManager fileManager = new FileManager(); + PageManager pageManager = new PageManager(fileManager); + lruCache = new LRUCache<>(2, 0.75f, true, pageManager); + + pageId1 = new PageId("jazz", 1); + page1 = new Page(1, PageType.CLUSTERED_INDEX); + + pageId2 = new PageId("jazz", 2); + page2 = new Page(2, PageType.CLUSTERED_INDEX); + } + + @DisplayName("캐시가 가득차고 새 페이지가 추가되었을 때, 가장 오래된 페이지가 캐시에서 제거된다.") + @Test + void removeEldestEntryTest() { + lruCache.put(pageId1, page1); + lruCache.put(pageId2, page2); + + PageId newPageId = new PageId("jazz", 3); + Page newPage = new Page(3, PageType.CLUSTERED_INDEX); + + lruCache.get(pageId1); // cache hit + lruCache.put(newPageId, newPage); + + assertAll( + () -> assertThat(lruCache.containsKey(pageId2)).isFalse(), + () -> assertThat(lruCache.containsKey(newPageId)).isTrue() + ); + } +} From 28f11326f7b9cadd9c0677ff136b8a0f026ce69b Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 23:21:38 +0900 Subject: [PATCH 15/16] =?UTF-8?q?test:=20=EB=B2=84=ED=8D=BC=20=ED=92=80=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- src/main/java/database/BufferPool.java | 8 +++ src/test/java/database/BufferPoolTest.java | 73 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/test/java/database/BufferPoolTest.java diff --git a/build.gradle b/build.gradle index 2baf15d..3b356e4 100644 --- a/build.gradle +++ b/build.gradle @@ -23,4 +23,4 @@ java { test { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/src/main/java/database/BufferPool.java b/src/main/java/database/BufferPool.java index 812efee..baa97db 100644 --- a/src/main/java/database/BufferPool.java +++ b/src/main/java/database/BufferPool.java @@ -42,4 +42,12 @@ public void flush() { } } } + + public Map getBufferPool() { + return bufferPool; + } + + public PageManager getPageManager() { + return pageManager; + } } diff --git a/src/test/java/database/BufferPoolTest.java b/src/test/java/database/BufferPoolTest.java new file mode 100644 index 0000000..ba52a5f --- /dev/null +++ b/src/test/java/database/BufferPoolTest.java @@ -0,0 +1,73 @@ +package database; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BufferPoolTest { + + private PageManager pageManager; + private BufferPool bufferPool; + + private PageId pageId; + private Page page; + + @BeforeEach + void setUp() { + FileManager fileManager = new FileManager(); + pageManager = new PageManager(fileManager); + bufferPool = new BufferPool(pageManager); + + pageId = new PageId("jazz", 1); + page = new Page(1, PageType.SECONDARY_INDEX); + } + + @DisplayName("버퍼 풀에 없는 페이지를 불러오면 디스크에서 버퍼 풀로 페이지를 적재한다") + @Test + void loadPageFromDiskWhenNotInCache() { + pageManager.savePage(pageId, page); + + Page loadedPage = bufferPool.getPage(pageId); + + assertAll( + () -> assertThat(loadedPage.getPageType()).isEqualTo(page.getPageType()), + () -> assertThat(loadedPage.getPageNum()).isEqualTo(page.getPageNum()), + () -> assertThat(loadedPage.getFreeSpace()).isEqualTo(page.getFreeSpace()), + () -> assertThat(loadedPage.isPinned()).isTrue() + ); + } + + @DisplayName("플러시 호출 시 핀 상태인 페이지는 디스크에 저장되지 않는다.") + @Test + void notFlushPinnedPageToDisk() { + page.setDirty(true); + page.pin(); + bufferPool.getBufferPool().put(pageId, page); + + bufferPool.flush(); + + assertAll( + () -> assertThat(page.isPinned()).isTrue(), + () -> assertThat(page.isDirty()).isTrue() + ); + } + + @DisplayName("플러시 호출 시 핀 상태가 아닌 페이지는 디스크에 저장되고 더티 페이지가 아니다.") + @Test + void flushUnpinnedPageToDisk() { + page.setDirty(true); + page.pin(); + page.unPin(); + bufferPool.getBufferPool().put(pageId, page); + + bufferPool.flush(); + + assertAll( + () -> assertThat(page.isPinned()).isFalse(), + () -> assertThat(page.isDirty()).isFalse() + ); + } +} From d3f1c1520f4ea58e3448e63806488e1b8f873c43 Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Wed, 28 Aug 2024 23:24:02 +0900 Subject: [PATCH 16/16] =?UTF-8?q?style:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20Import=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/database/FileManagerTest.java | 1 - src/test/java/database/PageTest.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/database/FileManagerTest.java b/src/test/java/database/FileManagerTest.java index 4d350f2..2334cfd 100644 --- a/src/test/java/database/FileManagerTest.java +++ b/src/test/java/database/FileManagerTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/database/PageTest.java b/src/test/java/database/PageTest.java index db17419..c813e36 100644 --- a/src/test/java/database/PageTest.java +++ b/src/test/java/database/PageTest.java @@ -1,7 +1,7 @@ package database; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test;