github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/vfs/syncing_file_test.go (about) 1 // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package vfs 16 17 import ( 18 "bytes" 19 "fmt" 20 "os" 21 "sync/atomic" 22 "testing" 23 24 "github.com/stretchr/testify/require" 25 ) 26 27 func TestSyncingFile(t *testing.T) { 28 const mb = 1 << 20 29 30 tmpf, err := os.CreateTemp("", "bitalosdb-db-syncing-file-") 31 require.NoError(t, err) 32 33 filename := tmpf.Name() 34 require.NoError(t, tmpf.Close()) 35 defer os.Remove(filename) 36 37 f, err := Default.Create(filename) 38 require.NoError(t, err) 39 40 s := NewSyncingFile(f, SyncingFileOptions{}) 41 if s == f { 42 t.Fatalf("failed to wrap: %p != %p", f, s) 43 } 44 s = NewSyncingFile(f, SyncingFileOptions{BytesPerSync: 8 << 10 /* 8 KB */}) 45 s = s.(*fdFileWrapper).File 46 s.(*syncingFile).fd = 1 47 s.(*syncingFile).syncTo = func(offset int64) error { 48 s.(*syncingFile).ratchetSyncOffset(offset) 49 return nil 50 } 51 52 t.Logf("sync_file_range=%t", s.(*syncingFile).useSyncRange) 53 54 testCases := []struct { 55 n int64 56 expectedSyncTo int64 57 }{ 58 {mb, -1}, 59 {mb, mb}, 60 {4 << 10, mb}, 61 {4 << 10, mb + 8<<10}, 62 {8 << 10, mb + 16<<10}, 63 {16 << 10, mb + 32<<10}, 64 } 65 for i, c := range testCases { 66 _, err := s.Write(make([]byte, c.n)) 67 require.NoError(t, err) 68 69 syncTo := atomic.LoadInt64(&s.(*syncingFile).atomic.syncOffset) 70 if c.expectedSyncTo != syncTo { 71 t.Fatalf("%d: expected sync to %d, but found %d", i, c.expectedSyncTo, syncTo) 72 } 73 } 74 } 75 76 func TestSyncingFileClose(t *testing.T) { 77 testCases := []struct { 78 syncToEnabled bool 79 expected string 80 }{ 81 {true, `sync-to(1048576): test [<nil>] 82 sync-to(2097152): test [<nil>] 83 sync-to(3145728): test [<nil>] 84 pre-close: test [offset=4194304 sync-offset=3145728] 85 sync: test [<nil>] 86 close: test [<nil>] 87 `}, 88 // When SyncFileRange is not being used, the last sync call ends up syncing 89 // all of the data causing syncingFile.Close to elide the sync. 90 {false, `sync: test [<nil>] 91 sync: test [<nil>] 92 pre-close: test [offset=4194304 sync-offset=4194304] 93 close: test [<nil>] 94 `}, 95 } 96 for _, c := range testCases { 97 t.Run("", func(t *testing.T) { 98 tmpf, err := os.CreateTemp("", "bitalosdb-db-syncing-file-") 99 require.NoError(t, err) 100 101 filename := tmpf.Name() 102 require.NoError(t, tmpf.Close()) 103 defer os.Remove(filename) 104 105 f, err := Default.Create(filename) 106 require.NoError(t, err) 107 108 var buf bytes.Buffer 109 lf := loggingFile{f, "test", &buf} 110 111 s := NewSyncingFile(lf, SyncingFileOptions{BytesPerSync: 8 << 10 /* 8 KB */}).(*syncingFile) 112 if c.syncToEnabled { 113 s.fd = 1 114 s.syncData = lf.Sync 115 s.syncTo = func(offset int64) error { 116 s.ratchetSyncOffset(offset) 117 fmt.Fprintf(lf.w, "sync-to(%d): %s [%v]\n", offset, lf.name, err) 118 return nil 119 } 120 } else { 121 s.fd = 0 122 } 123 124 write := func(n int64) { 125 t.Helper() 126 _, err := s.Write(make([]byte, n)) 127 require.NoError(t, err) 128 } 129 130 const mb = 1 << 20 131 write(2 * mb) 132 write(mb) 133 write(mb) 134 135 fmt.Fprintf(lf.w, "pre-close: %s [offset=%d sync-offset=%d]\n", 136 lf.name, atomic.LoadInt64(&s.atomic.offset), atomic.LoadInt64(&s.atomic.syncOffset)) 137 require.NoError(t, s.Close()) 138 139 if s := buf.String(); c.expected != s { 140 t.Fatalf("expected\n%s\nbut found\n%s", c.expected, s) 141 } 142 }) 143 } 144 } 145 146 func BenchmarkSyncWrite(b *testing.B) { 147 const targetSize = 16 << 20 148 149 var wsizes []int 150 if testing.Verbose() { 151 wsizes = []int{64, 512, 1 << 10, 2 << 10, 4 << 10, 8 << 10, 16 << 10, 32 << 10} 152 } else { 153 wsizes = []int{64} 154 } 155 156 run := func(b *testing.B, wsize int, newFile func(string) File) { 157 tmpf, err := os.CreateTemp("", "bitalosdb-db-syncing-file-") 158 if err != nil { 159 b.Fatal(err) 160 } 161 filename := tmpf.Name() 162 _ = tmpf.Close() 163 defer os.Remove(filename) 164 165 var f File 166 var size int 167 buf := make([]byte, wsize) 168 169 b.SetBytes(int64(len(buf))) 170 b.ResetTimer() 171 for i := 0; i < b.N; i++ { 172 if f == nil { 173 b.StopTimer() 174 f = newFile(filename) 175 size = 0 176 b.StartTimer() 177 } 178 if _, err := f.Write(buf); err != nil { 179 b.Fatal(err) 180 } 181 if err := f.Sync(); err != nil { 182 b.Fatal(err) 183 } 184 size += len(buf) 185 if size >= targetSize { 186 _ = f.Close() 187 f = nil 188 } 189 } 190 b.StopTimer() 191 } 192 193 b.Run("no-prealloc", func(b *testing.B) { 194 for _, wsize := range wsizes { 195 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 196 run(b, wsize, func(filename string) File { 197 _ = os.Remove(filename) 198 t, err := os.Create(filename) 199 if err != nil { 200 b.Fatal(err) 201 } 202 return NewSyncingFile(t, SyncingFileOptions{PreallocateSize: 0}) 203 }) 204 }) 205 } 206 }) 207 208 b.Run("prealloc-4MB", func(b *testing.B) { 209 for _, wsize := range wsizes { 210 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 211 run(b, wsize, func(filename string) File { 212 _ = os.Remove(filename) 213 t, err := os.Create(filename) 214 if err != nil { 215 b.Fatal(err) 216 } 217 return NewSyncingFile(t, SyncingFileOptions{PreallocateSize: 4 << 20}) 218 }) 219 }) 220 } 221 }) 222 223 b.Run("reuse", func(b *testing.B) { 224 for _, wsize := range wsizes { 225 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 226 init := true 227 run(b, wsize, func(filename string) File { 228 if init { 229 init = false 230 231 t, err := os.OpenFile(filename, os.O_RDWR, 0755) 232 if err != nil { 233 b.Fatal(err) 234 } 235 if _, err := t.Write(make([]byte, targetSize)); err != nil { 236 b.Fatal(err) 237 } 238 if err := t.Sync(); err != nil { 239 b.Fatal(err) 240 } 241 t.Close() 242 } 243 244 t, err := os.OpenFile(filename, os.O_RDWR, 0755) 245 if err != nil { 246 b.Fatal(err) 247 } 248 return NewSyncingFile(t, SyncingFileOptions{PreallocateSize: 0}) 249 }) 250 }) 251 } 252 }) 253 } 254 255 func BenchmarkWriteSyncVsDirect(b *testing.B) { 256 const targetSize = 256 << 20 257 258 var wsizes []int 259 if testing.Verbose() { 260 wsizes = []int{512, 1 << 10, 4 << 10, 8 << 10, 16 << 10, 32 << 10} 261 } else { 262 wsizes = []int{512} 263 } 264 265 run := func(b *testing.B, wsize int, newFile func(string) File) { 266 tmpf, err := os.CreateTemp("", "bitalosdb-db-syncing-file-") 267 if err != nil { 268 b.Fatal(err) 269 } 270 filename := tmpf.Name() 271 _ = tmpf.Close() 272 defer os.Remove(filename) 273 274 var f File 275 var size int 276 buf := make([]byte, wsize) 277 278 b.SetBytes(int64(len(buf))) 279 b.ResetTimer() 280 for i := 0; i < b.N; i++ { 281 if f == nil { 282 b.StopTimer() 283 f = newFile(filename) 284 size = 0 285 b.StartTimer() 286 } 287 if _, err := f.Write(buf); err != nil { 288 b.Fatal(err) 289 } 290 if err := f.Sync(); err != nil { 291 b.Fatal(err) 292 } 293 size += len(buf) 294 if size >= targetSize { 295 _ = f.Close() 296 f = nil 297 } 298 } 299 b.StopTimer() 300 } 301 302 b.Run("sync", func(b *testing.B) { 303 for _, wsize := range wsizes { 304 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 305 run(b, wsize, func(filename string) File { 306 _ = os.Remove(filename) 307 t, err := os.Create(filename) 308 if err != nil { 309 b.Fatal(err) 310 } 311 return NewSyncingFile(t, SyncingFileOptions{BytesPerSync: 512 << 10}) 312 }) 313 }) 314 } 315 }) 316 317 b.Run("direct", func(b *testing.B) { 318 for _, wsize := range wsizes { 319 b.Run(fmt.Sprintf("wsize=%d", wsize), func(b *testing.B) { 320 run(b, wsize, func(filename string) File { 321 _ = os.Remove(filename) 322 t, err := os.Create(filename) 323 if err != nil { 324 b.Fatal(err) 325 } 326 return NewSyncingFile(t, SyncingFileOptions{BytesPerSync: 0}) 327 }) 328 }) 329 } 330 }) 331 }