github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/storage/tsdb/bucketindex/loader_test.go (about)

     1  package bucketindex
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"path"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/go-kit/log"
    12  	"github.com/grafana/dskit/services"
    13  	"github.com/oklog/ulid"
    14  	"github.com/prometheus/client_golang/prometheus"
    15  	"github.com/prometheus/client_golang/prometheus/testutil"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	cortex_testutil "github.com/cortexproject/cortex/pkg/storage/tsdb/testutil"
    20  	"github.com/cortexproject/cortex/pkg/util/test"
    21  )
    22  
    23  func TestLoader_GetIndex_ShouldLazyLoadBucketIndex(t *testing.T) {
    24  	ctx := context.Background()
    25  	reg := prometheus.NewPedanticRegistry()
    26  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
    27  
    28  	// Create a bucket index.
    29  	idx := &Index{
    30  		Version: IndexVersion1,
    31  		Blocks: Blocks{
    32  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
    33  		},
    34  		BlockDeletionMarks: nil,
    35  		UpdatedAt:          time.Now().Unix(),
    36  	}
    37  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
    38  
    39  	// Create the loader.
    40  	loader := NewLoader(prepareLoaderConfig(), bkt, nil, log.NewNopLogger(), reg)
    41  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
    42  	t.Cleanup(func() {
    43  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
    44  	})
    45  
    46  	// Ensure no index has been loaded yet.
    47  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
    48  		# HELP cortex_bucket_index_load_failures_total Total number of bucket index loading failures.
    49  		# TYPE cortex_bucket_index_load_failures_total counter
    50  		cortex_bucket_index_load_failures_total 0
    51  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
    52  		# TYPE cortex_bucket_index_loaded gauge
    53  		cortex_bucket_index_loaded 0
    54  		# HELP cortex_bucket_index_loads_total Total number of bucket index loading attempts.
    55  		# TYPE cortex_bucket_index_loads_total counter
    56  		cortex_bucket_index_loads_total 0
    57  	`),
    58  		"cortex_bucket_index_loads_total",
    59  		"cortex_bucket_index_load_failures_total",
    60  		"cortex_bucket_index_loaded",
    61  	))
    62  
    63  	// Request the index multiple times.
    64  	for i := 0; i < 10; i++ {
    65  		actualIdx, err := loader.GetIndex(ctx, "user-1")
    66  		require.NoError(t, err)
    67  		assert.Equal(t, idx, actualIdx)
    68  	}
    69  
    70  	// Ensure metrics have been updated accordingly.
    71  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
    72  		# HELP cortex_bucket_index_load_failures_total Total number of bucket index loading failures.
    73  		# TYPE cortex_bucket_index_load_failures_total counter
    74  		cortex_bucket_index_load_failures_total 0
    75  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
    76  		# TYPE cortex_bucket_index_loaded gauge
    77  		cortex_bucket_index_loaded 1
    78  		# HELP cortex_bucket_index_loads_total Total number of bucket index loading attempts.
    79  		# TYPE cortex_bucket_index_loads_total counter
    80  		cortex_bucket_index_loads_total 1
    81  	`),
    82  		"cortex_bucket_index_loads_total",
    83  		"cortex_bucket_index_load_failures_total",
    84  		"cortex_bucket_index_loaded",
    85  	))
    86  }
    87  
    88  func TestLoader_GetIndex_ShouldCacheError(t *testing.T) {
    89  	ctx := context.Background()
    90  	reg := prometheus.NewPedanticRegistry()
    91  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
    92  
    93  	// Create the loader.
    94  	loader := NewLoader(prepareLoaderConfig(), bkt, nil, log.NewNopLogger(), reg)
    95  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
    96  	t.Cleanup(func() {
    97  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
    98  	})
    99  
   100  	// Write a corrupted index.
   101  	require.NoError(t, bkt.Upload(ctx, path.Join("user-1", IndexCompressedFilename), strings.NewReader("invalid!}")))
   102  
   103  	// Request the index multiple times.
   104  	for i := 0; i < 10; i++ {
   105  		_, err := loader.GetIndex(ctx, "user-1")
   106  		require.Equal(t, ErrIndexCorrupted, err)
   107  	}
   108  
   109  	// Ensure metrics have been updated accordingly.
   110  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   111  		# HELP cortex_bucket_index_load_failures_total Total number of bucket index loading failures.
   112  		# TYPE cortex_bucket_index_load_failures_total counter
   113  		cortex_bucket_index_load_failures_total 1
   114  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   115  		# TYPE cortex_bucket_index_loaded gauge
   116  		cortex_bucket_index_loaded 0
   117  		# HELP cortex_bucket_index_loads_total Total number of bucket index loading attempts.
   118  		# TYPE cortex_bucket_index_loads_total counter
   119  		cortex_bucket_index_loads_total 1
   120  	`),
   121  		"cortex_bucket_index_loads_total",
   122  		"cortex_bucket_index_load_failures_total",
   123  		"cortex_bucket_index_loaded",
   124  	))
   125  }
   126  
   127  func TestLoader_GetIndex_ShouldCacheIndexNotFoundError(t *testing.T) {
   128  	ctx := context.Background()
   129  	reg := prometheus.NewPedanticRegistry()
   130  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   131  
   132  	// Create the loader.
   133  	loader := NewLoader(prepareLoaderConfig(), bkt, nil, log.NewNopLogger(), reg)
   134  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   135  	t.Cleanup(func() {
   136  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   137  	})
   138  
   139  	// Request the index multiple times.
   140  	for i := 0; i < 10; i++ {
   141  		_, err := loader.GetIndex(ctx, "user-1")
   142  		require.Equal(t, ErrIndexNotFound, err)
   143  	}
   144  
   145  	// Ensure metrics have been updated accordingly.
   146  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   147  		# HELP cortex_bucket_index_load_failures_total Total number of bucket index loading failures.
   148  		# TYPE cortex_bucket_index_load_failures_total counter
   149  		cortex_bucket_index_load_failures_total 0
   150  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   151  		# TYPE cortex_bucket_index_loaded gauge
   152  		cortex_bucket_index_loaded 0
   153  		# HELP cortex_bucket_index_loads_total Total number of bucket index loading attempts.
   154  		# TYPE cortex_bucket_index_loads_total counter
   155  		cortex_bucket_index_loads_total 1
   156  	`),
   157  		"cortex_bucket_index_loads_total",
   158  		"cortex_bucket_index_load_failures_total",
   159  		"cortex_bucket_index_loaded",
   160  	))
   161  }
   162  
   163  func TestLoader_ShouldUpdateIndexInBackgroundOnPreviousLoadSuccess(t *testing.T) {
   164  	ctx := context.Background()
   165  	reg := prometheus.NewPedanticRegistry()
   166  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   167  
   168  	// Create a bucket index.
   169  	idx := &Index{
   170  		Version: IndexVersion1,
   171  		Blocks: Blocks{
   172  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
   173  		},
   174  		BlockDeletionMarks: nil,
   175  		UpdatedAt:          time.Now().Unix(),
   176  	}
   177  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   178  
   179  	// Create the loader.
   180  	cfg := LoaderConfig{
   181  		CheckInterval:         time.Second,
   182  		UpdateOnStaleInterval: time.Second,
   183  		UpdateOnErrorInterval: time.Hour, // Intentionally high to not hit it.
   184  		IdleTimeout:           time.Hour, // Intentionally high to not hit it.
   185  	}
   186  
   187  	loader := NewLoader(cfg, bkt, nil, log.NewNopLogger(), reg)
   188  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   189  	t.Cleanup(func() {
   190  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   191  	})
   192  
   193  	actualIdx, err := loader.GetIndex(ctx, "user-1")
   194  	require.NoError(t, err)
   195  	assert.Equal(t, idx, actualIdx)
   196  
   197  	// Update the bucket index.
   198  	idx.Blocks = append(idx.Blocks, &Block{ID: ulid.MustNew(2, nil), MinTime: 20, MaxTime: 30})
   199  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   200  
   201  	// Wait until the index has been updated in background.
   202  	test.Poll(t, 3*time.Second, 2, func() interface{} {
   203  		actualIdx, err := loader.GetIndex(ctx, "user-1")
   204  		if err != nil {
   205  			return 0
   206  		}
   207  		return len(actualIdx.Blocks)
   208  	})
   209  
   210  	actualIdx, err = loader.GetIndex(ctx, "user-1")
   211  	require.NoError(t, err)
   212  	assert.Equal(t, idx, actualIdx)
   213  
   214  	// Ensure metrics have been updated accordingly.
   215  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   216  		# HELP cortex_bucket_index_load_failures_total Total number of bucket index loading failures.
   217  		# TYPE cortex_bucket_index_load_failures_total counter
   218  		cortex_bucket_index_load_failures_total 0
   219  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   220  		# TYPE cortex_bucket_index_loaded gauge
   221  		cortex_bucket_index_loaded 1
   222  	`),
   223  		"cortex_bucket_index_load_failures_total",
   224  		"cortex_bucket_index_loaded",
   225  	))
   226  }
   227  
   228  func TestLoader_ShouldUpdateIndexInBackgroundOnPreviousLoadFailure(t *testing.T) {
   229  	ctx := context.Background()
   230  	reg := prometheus.NewPedanticRegistry()
   231  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   232  
   233  	// Write a corrupted index.
   234  	require.NoError(t, bkt.Upload(ctx, path.Join("user-1", IndexCompressedFilename), strings.NewReader("invalid!}")))
   235  
   236  	// Create the loader.
   237  	cfg := LoaderConfig{
   238  		CheckInterval:         time.Second,
   239  		UpdateOnStaleInterval: time.Hour, // Intentionally high to not hit it.
   240  		UpdateOnErrorInterval: time.Second,
   241  		IdleTimeout:           time.Hour, // Intentionally high to not hit it.
   242  	}
   243  
   244  	loader := NewLoader(cfg, bkt, nil, log.NewNopLogger(), reg)
   245  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   246  	t.Cleanup(func() {
   247  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   248  	})
   249  
   250  	_, err := loader.GetIndex(ctx, "user-1")
   251  	assert.Equal(t, ErrIndexCorrupted, err)
   252  
   253  	// Upload the bucket index.
   254  	idx := &Index{
   255  		Version: IndexVersion1,
   256  		Blocks: Blocks{
   257  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
   258  		},
   259  		BlockDeletionMarks: nil,
   260  		UpdatedAt:          time.Now().Unix(),
   261  	}
   262  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   263  
   264  	// Wait until the index has been updated in background.
   265  	test.Poll(t, 3*time.Second, nil, func() interface{} {
   266  		_, err := loader.GetIndex(ctx, "user-1")
   267  		return err
   268  	})
   269  
   270  	actualIdx, err := loader.GetIndex(ctx, "user-1")
   271  	require.NoError(t, err)
   272  	assert.Equal(t, idx, actualIdx)
   273  
   274  	// Ensure metrics have been updated accordingly.
   275  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   276  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   277  		# TYPE cortex_bucket_index_loaded gauge
   278  		cortex_bucket_index_loaded 1
   279  	`),
   280  		"cortex_bucket_index_loaded",
   281  	))
   282  }
   283  
   284  func TestLoader_ShouldUpdateIndexInBackgroundOnPreviousIndexNotFound(t *testing.T) {
   285  	ctx := context.Background()
   286  	reg := prometheus.NewPedanticRegistry()
   287  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   288  
   289  	// Create the loader.
   290  	cfg := LoaderConfig{
   291  		CheckInterval:         time.Second,
   292  		UpdateOnStaleInterval: time.Second,
   293  		UpdateOnErrorInterval: time.Hour, // Intentionally high to not hit it.
   294  		IdleTimeout:           time.Hour, // Intentionally high to not hit it.
   295  	}
   296  
   297  	loader := NewLoader(cfg, bkt, nil, log.NewNopLogger(), reg)
   298  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   299  	t.Cleanup(func() {
   300  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   301  	})
   302  
   303  	_, err := loader.GetIndex(ctx, "user-1")
   304  	assert.Equal(t, ErrIndexNotFound, err)
   305  
   306  	// Upload the bucket index.
   307  	idx := &Index{
   308  		Version: IndexVersion1,
   309  		Blocks: Blocks{
   310  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
   311  		},
   312  		BlockDeletionMarks: nil,
   313  		UpdatedAt:          time.Now().Unix(),
   314  	}
   315  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   316  
   317  	// Wait until the index has been updated in background.
   318  	test.Poll(t, 3*time.Second, nil, func() interface{} {
   319  		_, err := loader.GetIndex(ctx, "user-1")
   320  		return err
   321  	})
   322  
   323  	actualIdx, err := loader.GetIndex(ctx, "user-1")
   324  	require.NoError(t, err)
   325  	assert.Equal(t, idx, actualIdx)
   326  
   327  	// Ensure metrics have been updated accordingly.
   328  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   329  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   330  		# TYPE cortex_bucket_index_loaded gauge
   331  		cortex_bucket_index_loaded 1
   332  	`),
   333  		"cortex_bucket_index_loaded",
   334  	))
   335  }
   336  
   337  func TestLoader_ShouldNotCacheCriticalErrorOnBackgroundUpdates(t *testing.T) {
   338  	ctx := context.Background()
   339  	reg := prometheus.NewPedanticRegistry()
   340  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   341  
   342  	// Create a bucket index.
   343  	idx := &Index{
   344  		Version: IndexVersion1,
   345  		Blocks: Blocks{
   346  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
   347  		},
   348  		BlockDeletionMarks: nil,
   349  		UpdatedAt:          time.Now().Unix(),
   350  	}
   351  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   352  
   353  	// Create the loader.
   354  	cfg := LoaderConfig{
   355  		CheckInterval:         time.Second,
   356  		UpdateOnStaleInterval: time.Second,
   357  		UpdateOnErrorInterval: time.Second,
   358  		IdleTimeout:           time.Hour, // Intentionally high to not hit it.
   359  	}
   360  
   361  	loader := NewLoader(cfg, bkt, nil, log.NewNopLogger(), reg)
   362  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   363  	t.Cleanup(func() {
   364  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   365  	})
   366  
   367  	actualIdx, err := loader.GetIndex(ctx, "user-1")
   368  	require.NoError(t, err)
   369  	assert.Equal(t, idx, actualIdx)
   370  
   371  	// Write a corrupted index.
   372  	require.NoError(t, bkt.Upload(ctx, path.Join("user-1", IndexCompressedFilename), strings.NewReader("invalid!}")))
   373  
   374  	// Wait until the first failure has been tracked.
   375  	test.Poll(t, 3*time.Second, true, func() interface{} {
   376  		return testutil.ToFloat64(loader.loadFailures) > 0
   377  	})
   378  
   379  	actualIdx, err = loader.GetIndex(ctx, "user-1")
   380  	require.NoError(t, err)
   381  	assert.Equal(t, idx, actualIdx)
   382  
   383  	// Ensure metrics have been updated accordingly.
   384  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   385  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   386  		# TYPE cortex_bucket_index_loaded gauge
   387  		cortex_bucket_index_loaded 1
   388  	`),
   389  		"cortex_bucket_index_loaded",
   390  	))
   391  }
   392  
   393  func TestLoader_ShouldCacheIndexNotFoundOnBackgroundUpdates(t *testing.T) {
   394  	ctx := context.Background()
   395  	reg := prometheus.NewPedanticRegistry()
   396  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   397  
   398  	// Create a bucket index.
   399  	idx := &Index{
   400  		Version: IndexVersion1,
   401  		Blocks: Blocks{
   402  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
   403  		},
   404  		BlockDeletionMarks: nil,
   405  		UpdatedAt:          time.Now().Unix(),
   406  	}
   407  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   408  
   409  	// Create the loader.
   410  	cfg := LoaderConfig{
   411  		CheckInterval:         time.Second,
   412  		UpdateOnStaleInterval: time.Second,
   413  		UpdateOnErrorInterval: time.Second,
   414  		IdleTimeout:           time.Hour, // Intentionally high to not hit it.
   415  	}
   416  
   417  	loader := NewLoader(cfg, bkt, nil, log.NewNopLogger(), reg)
   418  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   419  	t.Cleanup(func() {
   420  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   421  	})
   422  
   423  	actualIdx, err := loader.GetIndex(ctx, "user-1")
   424  	require.NoError(t, err)
   425  	assert.Equal(t, idx, actualIdx)
   426  
   427  	// Delete the bucket index.
   428  	require.NoError(t, DeleteIndex(ctx, bkt, "user-1", nil))
   429  
   430  	// Wait until the next index load attempt occurs.
   431  	prevLoads := testutil.ToFloat64(loader.loadAttempts)
   432  	test.Poll(t, 3*time.Second, true, func() interface{} {
   433  		return testutil.ToFloat64(loader.loadAttempts) > prevLoads
   434  	})
   435  
   436  	// We expect the bucket index is not considered loaded because of the error.
   437  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   438  			# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   439  			# TYPE cortex_bucket_index_loaded gauge
   440  			cortex_bucket_index_loaded 0
   441  		`),
   442  		"cortex_bucket_index_loaded",
   443  	))
   444  
   445  	// Try to get the index again. We expect no load attempt because the error has been cached.
   446  	prevLoads = testutil.ToFloat64(loader.loadAttempts)
   447  	actualIdx, err = loader.GetIndex(ctx, "user-1")
   448  	assert.Equal(t, ErrIndexNotFound, err)
   449  	assert.Nil(t, actualIdx)
   450  	assert.Equal(t, prevLoads, testutil.ToFloat64(loader.loadAttempts))
   451  }
   452  
   453  func TestLoader_ShouldOffloadIndexIfNotFoundDuringBackgroundUpdates(t *testing.T) {
   454  	ctx := context.Background()
   455  	reg := prometheus.NewPedanticRegistry()
   456  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   457  
   458  	// Create a bucket index.
   459  	idx := &Index{
   460  		Version: IndexVersion1,
   461  		Blocks: Blocks{
   462  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
   463  		},
   464  		BlockDeletionMarks: nil,
   465  		UpdatedAt:          time.Now().Unix(),
   466  	}
   467  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   468  
   469  	// Create the loader.
   470  	cfg := LoaderConfig{
   471  		CheckInterval:         time.Second,
   472  		UpdateOnStaleInterval: time.Second,
   473  		UpdateOnErrorInterval: time.Second,
   474  		IdleTimeout:           time.Hour, // Intentionally high to not hit it.
   475  	}
   476  
   477  	loader := NewLoader(cfg, bkt, nil, log.NewNopLogger(), reg)
   478  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   479  	t.Cleanup(func() {
   480  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   481  	})
   482  
   483  	actualIdx, err := loader.GetIndex(ctx, "user-1")
   484  	require.NoError(t, err)
   485  	assert.Equal(t, idx, actualIdx)
   486  
   487  	// Delete the index
   488  	require.NoError(t, DeleteIndex(ctx, bkt, "user-1", nil))
   489  
   490  	// Wait until the index is offloaded.
   491  	test.Poll(t, 3*time.Second, float64(0), func() interface{} {
   492  		return testutil.ToFloat64(loader.loaded)
   493  	})
   494  
   495  	_, err = loader.GetIndex(ctx, "user-1")
   496  	require.Equal(t, ErrIndexNotFound, err)
   497  
   498  	// Ensure metrics have been updated accordingly.
   499  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   500  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   501  		# TYPE cortex_bucket_index_loaded gauge
   502  		cortex_bucket_index_loaded 0
   503  	`),
   504  		"cortex_bucket_index_loaded",
   505  	))
   506  }
   507  
   508  func TestLoader_ShouldOffloadIndexIfIdleTimeoutIsReachedDuringBackgroundUpdates(t *testing.T) {
   509  	ctx := context.Background()
   510  	reg := prometheus.NewPedanticRegistry()
   511  	bkt, _ := cortex_testutil.PrepareFilesystemBucket(t)
   512  
   513  	// Create a bucket index.
   514  	idx := &Index{
   515  		Version: IndexVersion1,
   516  		Blocks: Blocks{
   517  			{ID: ulid.MustNew(1, nil), MinTime: 10, MaxTime: 20},
   518  		},
   519  		BlockDeletionMarks: nil,
   520  		UpdatedAt:          time.Now().Unix(),
   521  	}
   522  	require.NoError(t, WriteIndex(ctx, bkt, "user-1", nil, idx))
   523  
   524  	// Create the loader.
   525  	cfg := LoaderConfig{
   526  		CheckInterval:         time.Second,
   527  		UpdateOnStaleInterval: time.Second,
   528  		UpdateOnErrorInterval: time.Second,
   529  		IdleTimeout:           0, // Offload at first check.
   530  	}
   531  
   532  	loader := NewLoader(cfg, bkt, nil, log.NewNopLogger(), reg)
   533  	require.NoError(t, services.StartAndAwaitRunning(ctx, loader))
   534  	t.Cleanup(func() {
   535  		require.NoError(t, services.StopAndAwaitTerminated(ctx, loader))
   536  	})
   537  
   538  	actualIdx, err := loader.GetIndex(ctx, "user-1")
   539  	require.NoError(t, err)
   540  	assert.Equal(t, idx, actualIdx)
   541  
   542  	// Wait until the index is offloaded.
   543  	test.Poll(t, 3*time.Second, float64(0), func() interface{} {
   544  		return testutil.ToFloat64(loader.loaded)
   545  	})
   546  
   547  	// Ensure metrics have been updated accordingly.
   548  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   549  		# HELP cortex_bucket_index_loaded Number of bucket indexes currently loaded in-memory.
   550  		# TYPE cortex_bucket_index_loaded gauge
   551  		cortex_bucket_index_loaded 0
   552  		# HELP cortex_bucket_index_loads_total Total number of bucket index loading attempts.
   553  		# TYPE cortex_bucket_index_loads_total counter
   554  		cortex_bucket_index_loads_total 1
   555  	`),
   556  		"cortex_bucket_index_loaded",
   557  		"cortex_bucket_index_loads_total",
   558  	))
   559  
   560  	// Load it again.
   561  	actualIdx, err = loader.GetIndex(ctx, "user-1")
   562  	require.NoError(t, err)
   563  	assert.Equal(t, idx, actualIdx)
   564  
   565  	// Ensure metrics have been updated accordingly.
   566  	assert.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(`
   567  		# HELP cortex_bucket_index_loads_total Total number of bucket index loading attempts.
   568  		# TYPE cortex_bucket_index_loads_total counter
   569  		cortex_bucket_index_loads_total 2
   570  	`),
   571  		"cortex_bucket_index_loads_total",
   572  	))
   573  }
   574  
   575  func prepareLoaderConfig() LoaderConfig {
   576  	return LoaderConfig{
   577  		CheckInterval:         time.Minute,
   578  		UpdateOnStaleInterval: 15 * time.Minute,
   579  		UpdateOnErrorInterval: time.Minute,
   580  		IdleTimeout:           time.Hour,
   581  	}
   582  }