github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/series/index/table_manager_test.go (about)

     1  package index
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/prometheus/common/model"
    10  	"github.com/stretchr/testify/require"
    11  	"github.com/weaveworks/common/mtime"
    12  
    13  	"github.com/grafana/loki/pkg/storage/config"
    14  )
    15  
    16  const (
    17  	baseTableName     = "cortex_base"
    18  	tablePrefix       = "cortex_"
    19  	table2Prefix      = "cortex2_"
    20  	chunkTablePrefix  = "chunks_"
    21  	chunkTable2Prefix = "chunks2_"
    22  	tableRetention    = 2 * 7 * 24 * time.Hour
    23  	tablePeriod       = 7 * 24 * time.Hour
    24  	gracePeriod       = 15 * time.Minute
    25  	maxChunkAge       = 12 * time.Hour
    26  	inactiveWrite     = 1
    27  	inactiveRead      = 2
    28  	write             = 200
    29  	read              = 100
    30  	autoScaleLastN    = 2
    31  	autoScaleMin      = 50
    32  	autoScaleMax      = 500
    33  	autoScaleTarget   = 80
    34  )
    35  
    36  var (
    37  	baseTableStart    = time.Unix(0, 0)
    38  	weeklyTableStart  = baseTableStart.Add(tablePeriod * 3)
    39  	weeklyTable2Start = baseTableStart.Add(tablePeriod * 5)
    40  	week1Suffix       = "3"
    41  	week2Suffix       = "4"
    42  )
    43  
    44  type mockTableClient struct {
    45  	sync.Mutex
    46  	tables map[string]config.TableDesc
    47  }
    48  
    49  func newMockTableClient() *mockTableClient {
    50  	return &mockTableClient{
    51  		tables: map[string]config.TableDesc{},
    52  	}
    53  }
    54  
    55  func (m *mockTableClient) ListTables(_ context.Context) ([]string, error) {
    56  	m.Lock()
    57  	defer m.Unlock()
    58  
    59  	result := []string{}
    60  	for name := range m.tables {
    61  		result = append(result, name)
    62  	}
    63  	return result, nil
    64  }
    65  
    66  func (m *mockTableClient) CreateTable(_ context.Context, desc config.TableDesc) error {
    67  	m.Lock()
    68  	defer m.Unlock()
    69  
    70  	m.tables[desc.Name] = desc
    71  	return nil
    72  }
    73  
    74  func (m *mockTableClient) DeleteTable(_ context.Context, name string) error {
    75  	m.Lock()
    76  	defer m.Unlock()
    77  
    78  	delete(m.tables, name)
    79  	return nil
    80  }
    81  
    82  func (m *mockTableClient) DescribeTable(_ context.Context, name string) (desc config.TableDesc, isActive bool, err error) {
    83  	m.Lock()
    84  	defer m.Unlock()
    85  
    86  	return m.tables[name], true, nil
    87  }
    88  
    89  func (m *mockTableClient) UpdateTable(_ context.Context, current, expected config.TableDesc) error {
    90  	m.Lock()
    91  	defer m.Unlock()
    92  
    93  	m.tables[current.Name] = expected
    94  	return nil
    95  }
    96  
    97  func (*mockTableClient) Stop() {}
    98  
    99  // nolint
   100  func tmTest(t *testing.T, client *mockTableClient, tableManager *TableManager, name string, tm time.Time, expected []config.TableDesc) {
   101  	t.Run(name, func(t *testing.T) {
   102  		ctx := context.Background()
   103  		mtime.NowForce(tm)
   104  		defer mtime.NowReset()
   105  		if err := tableManager.SyncTables(ctx); err != nil {
   106  			t.Fatal(err)
   107  		}
   108  		err := ExpectTables(ctx, client, expected)
   109  		require.NoError(t, err)
   110  	})
   111  }
   112  
   113  var activeScalingConfig = config.AutoScalingConfig{
   114  	Enabled:     true,
   115  	MinCapacity: autoScaleMin * 2,
   116  	MaxCapacity: autoScaleMax * 2,
   117  	TargetValue: autoScaleTarget,
   118  }
   119  
   120  var inactiveScalingConfig = config.AutoScalingConfig{
   121  	Enabled:     true,
   122  	MinCapacity: autoScaleMin,
   123  	MaxCapacity: autoScaleMax,
   124  	TargetValue: autoScaleTarget,
   125  }
   126  
   127  func TestTableManager(t *testing.T) {
   128  	client := newMockTableClient()
   129  
   130  	cfg := config.SchemaConfig{
   131  		Configs: []config.PeriodConfig{
   132  			{
   133  				From: config.DayTime{Time: model.TimeFromUnix(baseTableStart.Unix())},
   134  				IndexTables: config.PeriodicTableConfig{
   135  					Prefix: baseTableName,
   136  				},
   137  			},
   138  			{
   139  				From: config.DayTime{Time: model.TimeFromUnix(weeklyTableStart.Unix())},
   140  				IndexTables: config.PeriodicTableConfig{
   141  					Prefix: tablePrefix,
   142  					Period: tablePeriod,
   143  				},
   144  
   145  				ChunkTables: config.PeriodicTableConfig{
   146  					Prefix: chunkTablePrefix,
   147  					Period: tablePeriod,
   148  				},
   149  			},
   150  			{
   151  				From: config.DayTime{Time: model.TimeFromUnix(weeklyTable2Start.Unix())},
   152  				IndexTables: config.PeriodicTableConfig{
   153  					Prefix: table2Prefix,
   154  					Period: tablePeriod,
   155  				},
   156  
   157  				ChunkTables: config.PeriodicTableConfig{
   158  					Prefix: chunkTable2Prefix,
   159  					Period: tablePeriod,
   160  				},
   161  			},
   162  		},
   163  	}
   164  	tbmConfig := TableManagerConfig{
   165  		CreationGracePeriod: gracePeriod,
   166  		IndexTables: config.ProvisionConfig{
   167  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   168  				ProvisionedWriteThroughput: write,
   169  				ProvisionedReadThroughput:  read,
   170  				WriteScale:                 activeScalingConfig,
   171  			},
   172  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   173  				InactiveWriteThroughput: inactiveWrite,
   174  				InactiveReadThroughput:  inactiveRead,
   175  				InactiveWriteScale:      inactiveScalingConfig,
   176  				InactiveWriteScaleLastN: autoScaleLastN,
   177  			},
   178  		},
   179  		ChunkTables: config.ProvisionConfig{
   180  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   181  				ProvisionedWriteThroughput: write,
   182  				ProvisionedReadThroughput:  read,
   183  			},
   184  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   185  				InactiveWriteThroughput: inactiveWrite,
   186  				InactiveReadThroughput:  inactiveRead,
   187  			},
   188  		},
   189  	}
   190  	tableManager, err := NewTableManager(tbmConfig, cfg, maxChunkAge, client, nil, nil, nil)
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  
   195  	// Check at time zero, we have the base table only
   196  	tmTest(t, client, tableManager,
   197  		"Initial test",
   198  		baseTableStart,
   199  		[]config.TableDesc{
   200  			{Name: baseTableName, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   201  		},
   202  	)
   203  
   204  	// Check at start of weekly tables, we have the base table and one weekly table
   205  	tmTest(t, client, tableManager,
   206  		"Initial test weekly",
   207  		weeklyTableStart,
   208  		[]config.TableDesc{
   209  			{Name: baseTableName, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   210  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   211  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   212  		},
   213  	)
   214  
   215  	// Check running twice doesn't change anything
   216  	tmTest(t, client, tableManager,
   217  		"Nothing changed",
   218  		weeklyTableStart,
   219  		[]config.TableDesc{
   220  			{Name: baseTableName, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   221  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   222  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   223  		},
   224  	)
   225  
   226  	// Fast forward grace period, check we still have write throughput on base table
   227  	tmTest(t, client, tableManager,
   228  		"Move forward by grace period",
   229  		weeklyTableStart.Add(gracePeriod),
   230  		[]config.TableDesc{
   231  			{Name: baseTableName, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   232  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   233  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   234  		},
   235  	)
   236  
   237  	// Fast forward max chunk age + grace period, check write throughput on base table has gone
   238  	// (and we don't put inactive auto-scaling on base table)
   239  	tmTest(t, client, tableManager,
   240  		"Move forward by max chunk age + grace period",
   241  		weeklyTableStart.Add(maxChunkAge).Add(gracePeriod),
   242  		[]config.TableDesc{
   243  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   244  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   245  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   246  		},
   247  	)
   248  
   249  	// Fast forward table period - grace period, check we add another weekly table
   250  	tmTest(t, client, tableManager,
   251  		"Move forward by table period - grace period",
   252  		weeklyTableStart.Add(tablePeriod).Add(-gracePeriod+time.Second),
   253  		[]config.TableDesc{
   254  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   255  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   256  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   257  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   258  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   259  		},
   260  	)
   261  
   262  	// Fast forward table period + grace period, check we still have provisioned throughput
   263  	tmTest(t, client, tableManager,
   264  		"Move forward by table period + grace period",
   265  		weeklyTableStart.Add(tablePeriod).Add(gracePeriod),
   266  		[]config.TableDesc{
   267  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   268  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   269  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   270  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   271  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   272  		},
   273  	)
   274  
   275  	// Fast forward table period + max chunk age + grace period, check we remove provisioned throughput
   276  	tmTest(t, client, tableManager,
   277  		"Move forward by table period + max chunk age + grace period",
   278  		weeklyTableStart.Add(tablePeriod).Add(maxChunkAge).Add(gracePeriod),
   279  		[]config.TableDesc{
   280  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   281  			{Name: tablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   282  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   283  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   284  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   285  		},
   286  	)
   287  
   288  	// Check running twice doesn't change anything
   289  	tmTest(t, client, tableManager,
   290  		"Nothing changed",
   291  		weeklyTableStart.Add(tablePeriod).Add(maxChunkAge).Add(gracePeriod),
   292  		[]config.TableDesc{
   293  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   294  			{Name: tablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   295  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   296  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   297  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   298  		},
   299  	)
   300  
   301  	// Move ahead where we are short by just grace period before hitting next section
   302  	tmTest(t, client, tableManager,
   303  		"Move ahead where we are short by just grace period before hitting next section",
   304  		weeklyTable2Start.Add(-gracePeriod+time.Second),
   305  		[]config.TableDesc{
   306  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   307  			{Name: tablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   308  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   309  			{Name: table2Prefix + "5", ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   310  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   311  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   312  			{Name: chunkTable2Prefix + "5", ProvisionedRead: read, ProvisionedWrite: write},
   313  		},
   314  	)
   315  
   316  	// Move to the next section of the config
   317  	tmTest(t, client, tableManager,
   318  		"Move forward to next section of schema config",
   319  		weeklyTable2Start,
   320  		[]config.TableDesc{
   321  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   322  			{Name: tablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   323  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   324  			{Name: table2Prefix + "5", ProvisionedRead: read, ProvisionedWrite: write, WriteScale: activeScalingConfig},
   325  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   326  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   327  			{Name: chunkTable2Prefix + "5", ProvisionedRead: read, ProvisionedWrite: write},
   328  		},
   329  	)
   330  }
   331  
   332  func TestTableManagerAutoscaleInactiveOnly(t *testing.T) {
   333  	client := newMockTableClient()
   334  
   335  	cfg := config.SchemaConfig{
   336  		Configs: []config.PeriodConfig{
   337  			{
   338  				From: config.DayTime{Time: model.TimeFromUnix(baseTableStart.Unix())},
   339  				IndexTables: config.PeriodicTableConfig{
   340  					Prefix: baseTableName,
   341  				},
   342  			},
   343  			{
   344  				From: config.DayTime{Time: model.TimeFromUnix(weeklyTableStart.Unix())},
   345  				IndexTables: config.PeriodicTableConfig{
   346  					Prefix: tablePrefix,
   347  					Period: tablePeriod,
   348  				},
   349  
   350  				ChunkTables: config.PeriodicTableConfig{
   351  					Prefix: chunkTablePrefix,
   352  					Period: tablePeriod,
   353  				},
   354  			},
   355  		},
   356  	}
   357  	tbmConfig := TableManagerConfig{
   358  		CreationGracePeriod: gracePeriod,
   359  		IndexTables: config.ProvisionConfig{
   360  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   361  				ProvisionedWriteThroughput: write,
   362  				ProvisionedReadThroughput:  read,
   363  			},
   364  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   365  				InactiveWriteThroughput: inactiveWrite,
   366  				InactiveReadThroughput:  inactiveRead,
   367  				InactiveWriteScale:      inactiveScalingConfig,
   368  				InactiveWriteScaleLastN: autoScaleLastN,
   369  			},
   370  		},
   371  		ChunkTables: config.ProvisionConfig{
   372  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   373  				ProvisionedWriteThroughput: write,
   374  				ProvisionedReadThroughput:  read,
   375  			},
   376  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   377  				InactiveWriteThroughput: inactiveWrite,
   378  				InactiveReadThroughput:  inactiveRead,
   379  			},
   380  		},
   381  	}
   382  	tableManager, err := NewTableManager(tbmConfig, cfg, maxChunkAge, client, nil, nil, nil)
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  
   387  	// Check at time zero, we have the base table and one weekly table
   388  	tmTest(t, client, tableManager,
   389  		"Initial test",
   390  		weeklyTableStart,
   391  		[]config.TableDesc{
   392  			{Name: baseTableName, ProvisionedRead: read, ProvisionedWrite: write},
   393  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   394  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   395  		},
   396  	)
   397  
   398  	// Fast forward table period + grace period, check we still have provisioned throughput
   399  	tmTest(t, client, tableManager,
   400  		"Move forward by table period + grace period",
   401  		weeklyTableStart.Add(tablePeriod).Add(gracePeriod),
   402  		[]config.TableDesc{
   403  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   404  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   405  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   406  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   407  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   408  		},
   409  	)
   410  
   411  	// Fast forward table period + max chunk age + grace period, check we remove provisioned throughput
   412  
   413  	tmTest(t, client, tableManager,
   414  		"Move forward by table period + max chunk age + grace period",
   415  		weeklyTableStart.Add(tablePeriod).Add(maxChunkAge).Add(gracePeriod),
   416  		[]config.TableDesc{
   417  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   418  			{Name: tablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   419  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   420  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   421  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   422  		},
   423  	)
   424  }
   425  
   426  func TestTableManagerDynamicIOModeInactiveOnly(t *testing.T) {
   427  	client := newMockTableClient()
   428  
   429  	cfg := config.SchemaConfig{
   430  		Configs: []config.PeriodConfig{
   431  			{
   432  				From: config.DayTime{Time: model.TimeFromUnix(baseTableStart.Unix())},
   433  				IndexTables: config.PeriodicTableConfig{
   434  					Prefix: baseTableName,
   435  				},
   436  			},
   437  			{
   438  				From: config.DayTime{Time: model.TimeFromUnix(weeklyTableStart.Unix())},
   439  				IndexTables: config.PeriodicTableConfig{
   440  					Prefix: tablePrefix,
   441  					Period: tablePeriod,
   442  				},
   443  
   444  				ChunkTables: config.PeriodicTableConfig{
   445  					Prefix: chunkTablePrefix,
   446  					Period: tablePeriod,
   447  				},
   448  			},
   449  		},
   450  	}
   451  	tbmConfig := TableManagerConfig{
   452  		CreationGracePeriod: gracePeriod,
   453  		IndexTables: config.ProvisionConfig{
   454  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   455  				ProvisionedWriteThroughput: write,
   456  				ProvisionedReadThroughput:  read,
   457  			},
   458  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   459  				InactiveWriteThroughput:        inactiveWrite,
   460  				InactiveReadThroughput:         inactiveRead,
   461  				InactiveWriteScale:             inactiveScalingConfig,
   462  				InactiveThroughputOnDemandMode: true,
   463  				InactiveWriteScaleLastN:        1,
   464  			},
   465  		},
   466  		ChunkTables: config.ProvisionConfig{
   467  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   468  				ProvisionedWriteThroughput: write,
   469  				ProvisionedReadThroughput:  read,
   470  			},
   471  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   472  				InactiveWriteThroughput:        inactiveWrite,
   473  				InactiveReadThroughput:         inactiveRead,
   474  				InactiveThroughputOnDemandMode: true,
   475  			},
   476  		},
   477  	}
   478  	tableManager, err := NewTableManager(tbmConfig, cfg, maxChunkAge, client, nil, nil, nil)
   479  	if err != nil {
   480  		t.Fatal(err)
   481  	}
   482  
   483  	// Check at time zero, we have the base table and one weekly table
   484  	tmTest(t, client, tableManager,
   485  		"Initial test",
   486  		weeklyTableStart,
   487  		[]config.TableDesc{
   488  			{Name: baseTableName, ProvisionedRead: read, ProvisionedWrite: write},
   489  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   490  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   491  		},
   492  	)
   493  
   494  	// Fast forward table period + grace period, check we still have provisioned throughput
   495  	tmTest(t, client, tableManager,
   496  		"Move forward by table period + grace period",
   497  		weeklyTableStart.Add(tablePeriod).Add(gracePeriod),
   498  		[]config.TableDesc{
   499  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, UseOnDemandIOMode: true},
   500  			{Name: tablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   501  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   502  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   503  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   504  		},
   505  	)
   506  
   507  	// Fast forward table period + max chunk age + grace period, check we remove provisioned throughput
   508  	// Week 1 index table will not have dynamic mode enabled, since it has an active autoscale config for
   509  	// a managed provisioning mode. However the week 1 chunk table will flip to the DynamicIO mode.
   510  	tmTest(t, client, tableManager,
   511  		"Move forward by table period + max chunk age + grace period",
   512  		weeklyTableStart.Add(tablePeriod).Add(maxChunkAge).Add(gracePeriod),
   513  		[]config.TableDesc{
   514  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, UseOnDemandIOMode: true},
   515  			{Name: tablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig, UseOnDemandIOMode: false},
   516  			{Name: tablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   517  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, UseOnDemandIOMode: true},
   518  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: read, ProvisionedWrite: write},
   519  		},
   520  	)
   521  
   522  	// fast forward to another table period. Now week 1's dynamic mode will flip to true, as the managed autoscaling config is no longer active
   523  	tmTest(t, client, tableManager,
   524  		"Move forward by table period + max chunk age + grace period",
   525  		weeklyTableStart.Add(tablePeriod*2).Add(maxChunkAge).Add(gracePeriod),
   526  		[]config.TableDesc{
   527  			{Name: baseTableName, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, UseOnDemandIOMode: true},
   528  			{Name: tablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, UseOnDemandIOMode: true},
   529  			{Name: tablePrefix + week2Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig, UseOnDemandIOMode: false},
   530  			{Name: tablePrefix + "5", ProvisionedRead: read, ProvisionedWrite: write},
   531  			{Name: chunkTablePrefix + week1Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, UseOnDemandIOMode: true},
   532  			{Name: chunkTablePrefix + week2Suffix, ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, UseOnDemandIOMode: true},
   533  			{Name: chunkTablePrefix + "5", ProvisionedRead: read, ProvisionedWrite: write},
   534  		},
   535  	)
   536  }
   537  
   538  func TestTableManagerTags(t *testing.T) {
   539  	client := newMockTableClient()
   540  
   541  	test := func(tableManager *TableManager, name string, tm time.Time, expected []config.TableDesc) {
   542  		t.Run(name, func(t *testing.T) {
   543  			ctx := context.Background()
   544  			mtime.NowForce(tm)
   545  			defer mtime.NowReset()
   546  			if err := tableManager.SyncTables(ctx); err != nil {
   547  				t.Fatal(err)
   548  			}
   549  			err := ExpectTables(ctx, client, expected)
   550  			require.NoError(t, err)
   551  		})
   552  	}
   553  
   554  	// Check at time zero, we have the base table with no tags.
   555  	{
   556  		cfg := config.SchemaConfig{
   557  			Configs: []config.PeriodConfig{{
   558  				IndexTables: config.PeriodicTableConfig{},
   559  			}},
   560  		}
   561  		tableManager, err := NewTableManager(TableManagerConfig{}, cfg, maxChunkAge, client, nil, nil, nil)
   562  		if err != nil {
   563  			t.Fatal(err)
   564  		}
   565  
   566  		test(
   567  			tableManager,
   568  			"Initial test",
   569  			baseTableStart,
   570  			[]config.TableDesc{
   571  				{Name: ""},
   572  			},
   573  		)
   574  	}
   575  
   576  	// Check after restarting table manager we get some tags.
   577  	{
   578  		cfg := config.SchemaConfig{
   579  			Configs: []config.PeriodConfig{{
   580  				IndexTables: config.PeriodicTableConfig{
   581  					Tags: config.Tags{"foo": "bar"},
   582  				},
   583  			}},
   584  		}
   585  		tableManager, err := NewTableManager(TableManagerConfig{}, cfg, maxChunkAge, client, nil, nil, nil)
   586  		if err != nil {
   587  			t.Fatal(err)
   588  		}
   589  
   590  		test(
   591  			tableManager,
   592  			"Tagged test",
   593  			baseTableStart,
   594  			[]config.TableDesc{
   595  				{Name: "", Tags: config.Tags{"foo": "bar"}},
   596  			},
   597  		)
   598  	}
   599  }
   600  
   601  func TestTableManagerRetentionOnly(t *testing.T) {
   602  	client := newMockTableClient()
   603  
   604  	cfg := config.SchemaConfig{
   605  		Configs: []config.PeriodConfig{
   606  			{
   607  				From: config.DayTime{Time: model.TimeFromUnix(baseTableStart.Unix())},
   608  				IndexTables: config.PeriodicTableConfig{
   609  					Prefix: tablePrefix,
   610  					Period: tablePeriod,
   611  				},
   612  
   613  				ChunkTables: config.PeriodicTableConfig{
   614  					Prefix: chunkTablePrefix,
   615  					Period: tablePeriod,
   616  				},
   617  			},
   618  		},
   619  	}
   620  	tbmConfig := TableManagerConfig{
   621  		RetentionPeriod:         tableRetention,
   622  		RetentionDeletesEnabled: true,
   623  		CreationGracePeriod:     gracePeriod,
   624  		IndexTables: config.ProvisionConfig{
   625  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   626  				ProvisionedWriteThroughput: write,
   627  				ProvisionedReadThroughput:  read,
   628  			},
   629  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   630  				InactiveWriteThroughput: inactiveWrite,
   631  				InactiveReadThroughput:  inactiveRead,
   632  				InactiveWriteScale:      inactiveScalingConfig,
   633  				InactiveWriteScaleLastN: autoScaleLastN,
   634  			},
   635  		},
   636  		ChunkTables: config.ProvisionConfig{
   637  			ActiveTableProvisionConfig: config.ActiveTableProvisionConfig{
   638  				ProvisionedWriteThroughput: write,
   639  				ProvisionedReadThroughput:  read,
   640  			},
   641  			InactiveTableProvisionConfig: config.InactiveTableProvisionConfig{
   642  				InactiveWriteThroughput: inactiveWrite,
   643  				InactiveReadThroughput:  inactiveRead,
   644  			},
   645  		},
   646  	}
   647  	tableManager, err := NewTableManager(tbmConfig, cfg, maxChunkAge, client, nil, nil, nil)
   648  	if err != nil {
   649  		t.Fatal(err)
   650  	}
   651  
   652  	// Check at time zero, we have one weekly table
   653  	tmTest(t, client, tableManager,
   654  		"Initial test",
   655  		baseTableStart,
   656  		[]config.TableDesc{
   657  			{Name: tablePrefix + "0", ProvisionedRead: read, ProvisionedWrite: write},
   658  			{Name: chunkTablePrefix + "0", ProvisionedRead: read, ProvisionedWrite: write},
   659  		},
   660  	)
   661  
   662  	// Check after one week, we have two weekly tables
   663  	tmTest(t, client, tableManager,
   664  		"Move forward by one table period",
   665  		baseTableStart.Add(tablePeriod),
   666  		[]config.TableDesc{
   667  			{Name: tablePrefix + "0", ProvisionedRead: read, ProvisionedWrite: write},
   668  			{Name: tablePrefix + "1", ProvisionedRead: read, ProvisionedWrite: write},
   669  			{Name: chunkTablePrefix + "0", ProvisionedRead: read, ProvisionedWrite: write},
   670  			{Name: chunkTablePrefix + "1", ProvisionedRead: read, ProvisionedWrite: write},
   671  		},
   672  	)
   673  
   674  	// Check after two weeks, we have three tables (two previous periods and the new one)
   675  	tmTest(t, client, tableManager,
   676  		"Move forward by two table periods",
   677  		baseTableStart.Add(tablePeriod*2),
   678  		[]config.TableDesc{
   679  			{Name: tablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   680  			{Name: tablePrefix + "1", ProvisionedRead: read, ProvisionedWrite: write},
   681  			{Name: tablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   682  			{Name: chunkTablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   683  			{Name: chunkTablePrefix + "1", ProvisionedRead: read, ProvisionedWrite: write},
   684  			{Name: chunkTablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   685  		},
   686  	)
   687  
   688  	// Check after three weeks, we have three tables (two previous periods and the new one), table 0 was deleted
   689  	tmTest(t, client, tableManager,
   690  		"Move forward by three table periods",
   691  		baseTableStart.Add(tablePeriod*3),
   692  		[]config.TableDesc{
   693  			{Name: tablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   694  			{Name: tablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   695  			{Name: tablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   696  			{Name: chunkTablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   697  			{Name: chunkTablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   698  			{Name: chunkTablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   699  		},
   700  	)
   701  
   702  	// Check after three weeks and a day short by grace period, we have three tables (two previous periods and the new one), table 0 was deleted
   703  	tmTest(t, client, tableManager,
   704  		"Move forward by three table periods and a day short by grace period",
   705  		baseTableStart.Add(tablePeriod*3+24*time.Hour-gracePeriod),
   706  		[]config.TableDesc{
   707  			{Name: tablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   708  			{Name: tablePrefix + "2", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   709  			{Name: tablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   710  			{Name: chunkTablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   711  			{Name: chunkTablePrefix + "2", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   712  			{Name: chunkTablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   713  		},
   714  	)
   715  
   716  	// Verify that without RetentionDeletesEnabled no tables are removed
   717  	tableManager.cfg.RetentionDeletesEnabled = false
   718  	// Retention > 0 will prevent older tables from being created so we need to create the old tables manually for the test
   719  	err = client.CreateTable(context.Background(), config.TableDesc{Name: tablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig})
   720  	require.NoError(t, err)
   721  
   722  	err = client.CreateTable(context.Background(), config.TableDesc{Name: chunkTablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite})
   723  	require.NoError(t, err)
   724  
   725  	tmTest(t, client, tableManager,
   726  		"Move forward by three table periods (no deletes)",
   727  		baseTableStart.Add(tablePeriod*3),
   728  		[]config.TableDesc{
   729  			{Name: tablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   730  			{Name: tablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   731  			{Name: tablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   732  			{Name: tablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   733  			{Name: chunkTablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   734  			{Name: chunkTablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   735  			{Name: chunkTablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   736  			{Name: chunkTablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   737  		},
   738  	)
   739  
   740  	// Re-enable table deletions
   741  	tableManager.cfg.RetentionDeletesEnabled = true
   742  
   743  	// Verify that with a retention period of zero no tables outside the configs 'From' range are removed
   744  	tableManager.cfg.RetentionPeriod = 0
   745  	tableManager.schemaCfg.Configs[0].From = config.DayTime{Time: model.TimeFromUnix(baseTableStart.Add(tablePeriod).Unix())}
   746  	// Retention > 0 will prevent older tables from being created so we need to create the old tables manually for the test
   747  	err = client.CreateTable(context.Background(), config.TableDesc{Name: tablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig})
   748  	require.NoError(t, err)
   749  
   750  	err = client.CreateTable(context.Background(), config.TableDesc{Name: chunkTablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite})
   751  	require.NoError(t, err)
   752  
   753  	tmTest(t, client, tableManager,
   754  		"Move forward by three table periods (no deletes) and move From one table forward",
   755  		baseTableStart.Add(tablePeriod*3),
   756  		[]config.TableDesc{
   757  			{Name: tablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   758  			{Name: tablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite, WriteScale: inactiveScalingConfig},
   759  			{Name: tablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   760  			{Name: tablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   761  			{Name: chunkTablePrefix + "0", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   762  			{Name: chunkTablePrefix + "1", ProvisionedRead: inactiveRead, ProvisionedWrite: inactiveWrite},
   763  			{Name: chunkTablePrefix + "2", ProvisionedRead: read, ProvisionedWrite: write},
   764  			{Name: chunkTablePrefix + "3", ProvisionedRead: read, ProvisionedWrite: write},
   765  		},
   766  	)
   767  
   768  	// Test table manager retention not multiple of periodic config
   769  	tbmConfig.RetentionPeriod++
   770  	_, err = NewTableManager(tbmConfig, cfg, maxChunkAge, client, nil, nil, nil)
   771  	require.Error(t, err)
   772  }