A B+Tree Storage Engine
at main 212 lines 5.5 kB view raw
1package buffer 2 3import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path" 8 "testing" 9 10 "github.com/jobala/petro/storage/disk" 11 "github.com/stretchr/testify/assert" 12) 13 14func TestBufferPoolManager(t *testing.T) { 15 t.Run("reads a page from disk", func(t *testing.T) { 16 file := CreateDbFile(t) 17 t.Cleanup(func() { 18 _ = os.Remove(file.Name()) 19 }) 20 21 replacer := NewLrukReplacer(5, 2) 22 diskMgr := disk.NewManager(file) 23 diskScheduler := disk.NewScheduler(diskMgr) 24 bufferMgr := NewBufferpoolManager(5, replacer, diskScheduler) 25 26 pageId := 1 27 data := make([]byte, disk.PAGE_SIZE) 28 copy(data, []byte("hello, world!")) 29 syncWrite(pageId, data, diskScheduler) 30 31 pageGuard, err := bufferMgr.ReadPage(int64(pageId)) 32 defer pageGuard.Drop() 33 assert.NoError(t, err) 34 35 assert.Equal(t, data, pageGuard.GetData()) 36 assert.Equal(t, data, bufferMgr.frames[0].data) 37 }) 38 39 t.Run("evicts least recently used page", func(t *testing.T) { 40 file := CreateDbFile(t) 41 t.Cleanup(func() { 42 _ = os.Remove(file.Name()) 43 }) 44 45 replacer := NewLrukReplacer(2, 2) 46 diskMgr := disk.NewManager(file) 47 diskScheduler := disk.NewScheduler(diskMgr) 48 bufferMgr := NewBufferpoolManager(2, replacer, diskScheduler) 49 50 content := []string{"1", "2", "3"} 51 for pageId, d := range content { 52 data := make([]byte, disk.PAGE_SIZE) 53 copy(data, []byte(d)) 54 syncWrite(pageId+1, data, diskScheduler) 55 } 56 57 // access page 2 many times 58 for range 5 { 59 pageGuard, err := bufferMgr.ReadPage(int64(2)) 60 assert.NoError(t, err) 61 pageGuard.Drop() 62 } 63 64 // access page 1 to make page 2 least recently used 65 pageGuard, err := bufferMgr.ReadPage(int64(1)) 66 assert.NoError(t, err) 67 pageGuard.Drop() 68 69 // accessing page 3 should evict page 1 70 for i := range len(content) { 71 pageGuard, err := bufferMgr.ReadPage(int64(i + 1)) 72 73 assert.NoError(t, err) 74 assert.Equal(t, string(bytes.Trim(pageGuard.GetData(), "\x00")), content[i]) 75 pageGuard.Drop() 76 } 77 78 // page id 1, should have been evicted 79 assert.Equal(t, bufferMgr.frames[0].pageId, int64(2)) 80 assert.Equal(t, bufferMgr.frames[1].pageId, int64(3)) 81 82 // buffermanager's pagetable shouldn't have evicted pageId 83 _, ok := bufferMgr.pageTable[1] 84 assert.Equal(t, false, ok) 85 }) 86 87 t.Run("writes a page to disk", func(t *testing.T) { 88 file := CreateDbFile(t) 89 t.Cleanup(func() { 90 _ = os.Remove(file.Name()) 91 }) 92 93 replacer := NewLrukReplacer(5, 2) 94 diskMgr := disk.NewManager(file) 95 diskScheduler := disk.NewScheduler(diskMgr) 96 bufferMgr := NewBufferpoolManager(5, replacer, diskScheduler) 97 98 pageId := 1 99 data := make([]byte, disk.PAGE_SIZE) 100 copy(data, []byte("hello, world!")) 101 102 pageGuard, err := bufferMgr.WritePage(int64(pageId)) 103 copy(*pageGuard.GetDataMut(), data) 104 defer pageGuard.Drop() 105 106 assert.NoError(t, err) 107 assert.Equal(t, data, bufferMgr.frames[0].data) 108 assert.True(t, bufferMgr.frames[0].dirty, true) 109 110 bufferMgr.flush(bufferMgr.frames[0]) 111 res := syncRead(pageId, diskScheduler) 112 assert.Equal(t, data, res) 113 }) 114 115 t.Run("dirty evicted pages are flushed to disk", func(t *testing.T) { 116 file := CreateDbFile(t) 117 t.Cleanup(func() { 118 _ = os.Remove(file.Name()) 119 }) 120 121 replacer := NewLrukReplacer(2, 2) 122 diskMgr := disk.NewManager(file) 123 diskScheduler := disk.NewScheduler(diskMgr) 124 bufferMgr := NewBufferpoolManager(2, replacer, diskScheduler) 125 126 content := []string{"1", "2", "3"} 127 for pageId, d := range content { 128 data := make([]byte, disk.PAGE_SIZE) 129 copy(data, []byte(d)) 130 131 pageGuard, err := bufferMgr.WritePage(int64(pageId + 1)) 132 copy(*pageGuard.GetDataMut(), data) 133 pageGuard.Drop() 134 135 assert.NoError(t, err) 136 } 137 138 // page 1 should have been evicted and flushed to disk 139 res := syncRead(1, diskScheduler) 140 assert.Equal(t, content[0], string(bytes.Trim(res, "\x00"))) 141 }) 142 143 t.Run("can read and write", func(t *testing.T) { 144 file := CreateDbFile(t) 145 t.Cleanup(func() { 146 _ = os.Remove(file.Name()) 147 }) 148 149 replacer := NewLrukReplacer(2, 2) 150 diskMgr := disk.NewManager(file) 151 diskScheduler := disk.NewScheduler(diskMgr) 152 bufferMgr := NewBufferpoolManager(2, replacer, diskScheduler) 153 154 content := []string{"1", "2", "3"} 155 for pageId, d := range content { 156 data := make([]byte, disk.PAGE_SIZE) 157 copy(data, []byte(d)) 158 pageGuard, err := bufferMgr.WritePage(int64(pageId + 1)) 159 copy(*pageGuard.GetDataMut(), data) 160 pageGuard.Drop() 161 162 assert.NoError(t, err) 163 } 164 165 for pageId, data := range content { 166 pageGuard, err := bufferMgr.ReadPage(int64(pageId + 1)) 167 pageGuard.Drop() 168 169 assert.NoError(t, err) 170 assert.Equal(t, data, string(bytes.Trim(pageGuard.GetData(), "\x00"))) 171 } 172 }) 173} 174 175func CreateDbFile(t *testing.T) *os.File { 176 t.Helper() 177 dbFile := path.Join(t.TempDir(), "test.db") 178 179 file, err := os.OpenFile(dbFile, os.O_CREATE|os.O_RDWR, 0644) 180 if err != nil { 181 panic(fmt.Sprintf("failed creating db file\n%v", err)) 182 } 183 184 // create 4kb file 185 _ = os.Truncate(file.Name(), disk.PAGE_SIZE) 186 fileInfo, err := os.Stat(file.Name()) 187 assert.NoError(t, err) 188 assert.Equal(t, int64(disk.PAGE_SIZE), fileInfo.Size()) 189 return file 190} 191 192func syncWrite(pageId int, data []byte, diskScheduler *disk.DiskScheduler) { 193 resCh := make(chan disk.DiskResp) 194 195 writeReq := disk.DiskReq{ 196 PageId: pageId, 197 Write: true, 198 Data: data, 199 RespCh: resCh, 200 } 201 202 diskScheduler.Schedule(writeReq) 203 <-resCh 204} 205 206func syncRead(pageId int, diskScheduler *disk.DiskScheduler) []byte { 207 readReq := disk.NewRequest(int64(pageId), nil, false) 208 respCh := diskScheduler.Schedule(readReq) 209 res := <-respCh 210 211 return res.Data 212}