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  }