github.com/thanos-io/thanos@v0.32.5/pkg/block/indexheader/lazy_binary_reader_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package indexheader
     5  
     6  import (
     7  	"context"
     8  	"os"
     9  	"path/filepath"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/go-kit/log"
    15  	"github.com/oklog/ulid"
    16  	promtestutil "github.com/prometheus/client_golang/prometheus/testutil"
    17  	"github.com/prometheus/prometheus/model/labels"
    18  	"github.com/thanos-io/objstore/providers/filesystem"
    19  
    20  	"github.com/efficientgo/core/testutil"
    21  	"github.com/thanos-io/thanos/pkg/block"
    22  	"github.com/thanos-io/thanos/pkg/block/metadata"
    23  	"github.com/thanos-io/thanos/pkg/testutil/e2eutil"
    24  )
    25  
    26  func TestNewLazyBinaryReader_ShouldFailIfUnableToBuildIndexHeader(t *testing.T) {
    27  	ctx := context.Background()
    28  
    29  	tmpDir := t.TempDir()
    30  
    31  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
    32  	testutil.Ok(t, err)
    33  	defer func() { testutil.Ok(t, bkt.Close()) }()
    34  
    35  	_, err = NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, ulid.MustNew(0, nil), 3, NewLazyBinaryReaderMetrics(nil), nil)
    36  	testutil.NotOk(t, err)
    37  }
    38  
    39  func TestNewLazyBinaryReader_ShouldBuildIndexHeaderFromBucket(t *testing.T) {
    40  	ctx := context.Background()
    41  
    42  	tmpDir := t.TempDir()
    43  
    44  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
    45  	testutil.Ok(t, err)
    46  	defer func() { testutil.Ok(t, bkt.Close()) }()
    47  
    48  	// Create block.
    49  	blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{
    50  		{{Name: "a", Value: "1"}},
    51  		{{Name: "a", Value: "2"}},
    52  	}, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc)
    53  	testutil.Ok(t, err)
    54  	testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
    55  
    56  	m := NewLazyBinaryReaderMetrics(nil)
    57  	r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil)
    58  	testutil.Ok(t, err)
    59  	testutil.Assert(t, r.reader == nil)
    60  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadCount))
    61  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount))
    62  
    63  	// Should lazy load the index upon first usage.
    64  	v, err := r.IndexVersion()
    65  	testutil.Ok(t, err)
    66  	testutil.Equals(t, 2, v)
    67  	testutil.Assert(t, r.reader != nil)
    68  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount))
    69  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount))
    70  
    71  	labelNames, err := r.LabelNames()
    72  	testutil.Ok(t, err)
    73  	testutil.Equals(t, []string{"a"}, labelNames)
    74  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount))
    75  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount))
    76  }
    77  
    78  func TestNewLazyBinaryReader_ShouldRebuildCorruptedIndexHeader(t *testing.T) {
    79  	ctx := context.Background()
    80  
    81  	tmpDir := t.TempDir()
    82  
    83  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
    84  	testutil.Ok(t, err)
    85  	defer func() { testutil.Ok(t, bkt.Close()) }()
    86  
    87  	// Create block.
    88  	blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{
    89  		{{Name: "a", Value: "1"}},
    90  		{{Name: "a", Value: "2"}},
    91  	}, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc)
    92  	testutil.Ok(t, err)
    93  	testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
    94  
    95  	// Write a corrupted index-header for the block.
    96  	headerFilename := filepath.Join(tmpDir, blockID.String(), block.IndexHeaderFilename)
    97  	testutil.Ok(t, os.WriteFile(headerFilename, []byte("xxx"), os.ModePerm))
    98  
    99  	m := NewLazyBinaryReaderMetrics(nil)
   100  	r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil)
   101  	testutil.Ok(t, err)
   102  	testutil.Assert(t, r.reader == nil)
   103  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadCount))
   104  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount))
   105  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount))
   106  
   107  	// Ensure it can read data.
   108  	labelNames, err := r.LabelNames()
   109  	testutil.Ok(t, err)
   110  	testutil.Equals(t, []string{"a"}, labelNames)
   111  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount))
   112  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount))
   113  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount))
   114  }
   115  
   116  func TestLazyBinaryReader_ShouldReopenOnUsageAfterClose(t *testing.T) {
   117  	ctx := context.Background()
   118  
   119  	tmpDir := t.TempDir()
   120  
   121  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
   122  	testutil.Ok(t, err)
   123  	defer func() { testutil.Ok(t, bkt.Close()) }()
   124  
   125  	// Create block.
   126  	blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{
   127  		{{Name: "a", Value: "1"}},
   128  		{{Name: "a", Value: "2"}},
   129  	}, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc)
   130  	testutil.Ok(t, err)
   131  	testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
   132  
   133  	m := NewLazyBinaryReaderMetrics(nil)
   134  	r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil)
   135  	testutil.Ok(t, err)
   136  	testutil.Assert(t, r.reader == nil)
   137  
   138  	// Should lazy load the index upon first usage.
   139  	labelNames, err := r.LabelNames()
   140  	testutil.Ok(t, err)
   141  	testutil.Equals(t, []string{"a"}, labelNames)
   142  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount))
   143  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount))
   144  
   145  	// Close it.
   146  	testutil.Ok(t, r.Close())
   147  	testutil.Assert(t, r.reader == nil)
   148  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.unloadCount))
   149  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount))
   150  
   151  	// Should lazy load again upon next usage.
   152  	labelNames, err = r.LabelNames()
   153  	testutil.Ok(t, err)
   154  	testutil.Equals(t, []string{"a"}, labelNames)
   155  	testutil.Equals(t, float64(2), promtestutil.ToFloat64(m.loadCount))
   156  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount))
   157  
   158  	// Closing an already closed lazy reader should be a no-op.
   159  	for i := 0; i < 2; i++ {
   160  		testutil.Ok(t, r.Close())
   161  		testutil.Equals(t, float64(2), promtestutil.ToFloat64(m.unloadCount))
   162  		testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount))
   163  	}
   164  }
   165  
   166  func TestLazyBinaryReader_unload_ShouldReturnErrorIfNotIdle(t *testing.T) {
   167  	ctx := context.Background()
   168  
   169  	tmpDir := t.TempDir()
   170  
   171  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
   172  	testutil.Ok(t, err)
   173  	defer func() { testutil.Ok(t, bkt.Close()) }()
   174  
   175  	// Create block.
   176  	blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{
   177  		{{Name: "a", Value: "1"}},
   178  		{{Name: "a", Value: "2"}},
   179  	}, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc)
   180  	testutil.Ok(t, err)
   181  	testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
   182  
   183  	m := NewLazyBinaryReaderMetrics(nil)
   184  	r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil)
   185  	testutil.Ok(t, err)
   186  	testutil.Assert(t, r.reader == nil)
   187  
   188  	// Should lazy load the index upon first usage.
   189  	labelNames, err := r.LabelNames()
   190  	testutil.Ok(t, err)
   191  	testutil.Equals(t, []string{"a"}, labelNames)
   192  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount))
   193  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount))
   194  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount))
   195  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount))
   196  
   197  	// Try to unload but not idle since enough time.
   198  	testutil.Equals(t, errNotIdle, r.unloadIfIdleSince(time.Now().Add(-time.Minute).UnixNano()))
   199  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount))
   200  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount))
   201  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadCount))
   202  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount))
   203  
   204  	// Try to unload and idle since enough time.
   205  	testutil.Ok(t, r.unloadIfIdleSince(time.Now().UnixNano()))
   206  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.loadCount))
   207  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.loadFailedCount))
   208  	testutil.Equals(t, float64(1), promtestutil.ToFloat64(m.unloadCount))
   209  	testutil.Equals(t, float64(0), promtestutil.ToFloat64(m.unloadFailedCount))
   210  }
   211  
   212  func TestLazyBinaryReader_LoadUnloadRaceCondition(t *testing.T) {
   213  	// Run the test for a fixed amount of time.
   214  	const runDuration = 5 * time.Second
   215  
   216  	ctx := context.Background()
   217  
   218  	tmpDir := t.TempDir()
   219  
   220  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
   221  	testutil.Ok(t, err)
   222  	defer func() { testutil.Ok(t, bkt.Close()) }()
   223  
   224  	// Create block.
   225  	blockID, err := e2eutil.CreateBlock(ctx, tmpDir, []labels.Labels{
   226  		{{Name: "a", Value: "1"}},
   227  		{{Name: "a", Value: "2"}},
   228  	}, 100, 0, 1000, labels.Labels{{Name: "ext1", Value: "1"}}, 124, metadata.NoneFunc)
   229  	testutil.Ok(t, err)
   230  	testutil.Ok(t, block.Upload(ctx, log.NewNopLogger(), bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
   231  
   232  	m := NewLazyBinaryReaderMetrics(nil)
   233  	r, err := NewLazyBinaryReader(ctx, log.NewNopLogger(), bkt, tmpDir, blockID, 3, m, nil)
   234  	testutil.Ok(t, err)
   235  	testutil.Assert(t, r.reader == nil)
   236  	t.Cleanup(func() {
   237  		testutil.Ok(t, r.Close())
   238  	})
   239  
   240  	done := make(chan struct{})
   241  	time.AfterFunc(runDuration, func() { close(done) })
   242  	wg := sync.WaitGroup{}
   243  	wg.Add(2)
   244  
   245  	// Start a goroutine which continuously try to unload the reader.
   246  	go func() {
   247  		defer wg.Done()
   248  
   249  		for {
   250  			select {
   251  			case <-done:
   252  				return
   253  			default:
   254  				testutil.Ok(t, r.unloadIfIdleSince(0))
   255  			}
   256  		}
   257  	}()
   258  
   259  	// Try to read multiple times, while the other goroutine continuously try to unload it.
   260  	go func() {
   261  		defer wg.Done()
   262  
   263  		for {
   264  			select {
   265  			case <-done:
   266  				return
   267  			default:
   268  				_, err := r.PostingsOffset("a", "1")
   269  				testutil.Assert(t, err == nil || err == errUnloadedWhileLoading)
   270  			}
   271  		}
   272  	}()
   273  
   274  	// Wait until both goroutines have done.
   275  	wg.Wait()
   276  }