github.com/grafana/pyroscope@v1.18.0/pkg/metastore/index/store/index_store_test.go (about) 1 package store 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 "go.etcd.io/bbolt" 10 11 metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" 12 "github.com/grafana/pyroscope/pkg/test" 13 ) 14 15 const testTenant = "test-tenant" 16 17 func TestShard_Overlaps(t *testing.T) { 18 db := test.BoltDB(t) 19 20 store := NewIndexStore() 21 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 22 return store.CreateBuckets(tx) 23 })) 24 25 partitionKey := NewPartition(test.Time("2024-09-11T06:00:00.000Z"), 6*time.Hour) 26 shardID := uint32(1) 27 28 blockMinTime := test.UnixMilli("2024-09-11T07:00:00.000Z") 29 blockMaxTime := test.UnixMilli("2024-09-11T09:00:00.000Z") 30 31 blockMeta := &metastorev1.BlockMeta{ 32 FormatVersion: 1, 33 Id: "test-block-123", 34 Tenant: 1, // Index 1 in StringTable ("test-tenant") 35 Shard: shardID, 36 MinTime: blockMinTime, 37 MaxTime: blockMaxTime, 38 Datasets: []*metastorev1.Dataset{ 39 { 40 Tenant: 1, // Index 1 in StringTable ("test-tenant") 41 Name: 3, // Index 3 in StringTable ("test-dataset") 42 MinTime: blockMinTime, 43 MaxTime: blockMaxTime, 44 // Labels format: [count, name_idx, value_idx, name_idx, value_idx, ...] 45 // 2 labels: service_name="service", __profile_type__="cpu" 46 Labels: []int32{2, 3, 5, 4, 6}, 47 }, 48 }, 49 StringTable: []string{ 50 "", // Index 0 51 "test-tenant", // Index 1 52 "test-dataset", // Index 2 53 "service_name", // Index 3 54 "__profile_type__", // Index 4 55 "service", // Index 5 56 "cpu", // Index 6 57 }, 58 } 59 60 // store a block 61 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 62 return NewShard(partitionKey, testTenant, shardID).Store(tx, blockMeta) 63 })) 64 65 require.NoError(t, db.View(func(tx *bbolt.Tx) error { 66 shard, err := store.LoadShard(tx, partitionKey, testTenant, shardID) 67 require.NoError(t, err) 68 require.NotNil(t, shard) 69 70 assert.Equal(t, blockMinTime, shard.ShardIndex.MinTime) 71 assert.Equal(t, blockMaxTime, shard.ShardIndex.MaxTime) 72 73 testCases := []struct { 74 name string 75 startTime time.Time 76 endTime time.Time 77 expected bool 78 }{ 79 { 80 name: "complete overlap - query contains block range", 81 startTime: test.Time("2024-09-11T06:30:00.000Z"), 82 endTime: test.Time("2024-09-11T10:00:00.000Z"), 83 expected: true, 84 }, 85 { 86 name: "block contains query range", 87 startTime: test.Time("2024-09-11T07:30:00.000Z"), 88 endTime: test.Time("2024-09-11T08:30:00.000Z"), 89 expected: true, 90 }, 91 { 92 name: "partial overlap - start before block, end within block", 93 startTime: test.Time("2024-09-11T06:30:00.000Z"), 94 endTime: test.Time("2024-09-11T08:00:00.000Z"), 95 expected: true, 96 }, 97 { 98 name: "partial overlap - start within block, end after block", 99 startTime: test.Time("2024-09-11T08:00:00.000Z"), 100 endTime: test.Time("2024-09-11T10:00:00.000Z"), 101 expected: true, 102 }, 103 { 104 name: "edge case - query ends exactly at block start", 105 startTime: test.Time("2024-09-11T06:00:00.000Z"), 106 endTime: test.Time("2024-09-11T07:00:00.000Z"), 107 expected: true, // Inclusive boundary check 108 }, 109 { 110 name: "edge case - query starts exactly at block end", 111 startTime: test.Time("2024-09-11T09:00:00.000Z"), 112 endTime: test.Time("2024-09-11T10:00:00.000Z"), 113 expected: true, // Inclusive boundary check 114 }, 115 { 116 name: "no overlap - query before block", 117 startTime: test.Time("2024-09-11T05:00:00.000Z"), 118 endTime: test.Time("2024-09-11T06:59:58.999Z"), 119 expected: false, 120 }, 121 { 122 name: "no overlap - query after block", 123 startTime: test.Time("2024-09-11T09:00:00.001Z"), 124 endTime: test.Time("2024-09-11T11:00:00.000Z"), 125 expected: false, 126 }, 127 { 128 name: "exact match - same start and end times", 129 startTime: test.Time("2024-09-11T07:00:00.000Z"), 130 endTime: test.Time("2024-09-11T09:00:00.000Z"), 131 expected: true, 132 }, 133 } 134 135 for _, tc := range testCases { 136 t.Run(tc.name, func(t *testing.T) { 137 result := shard.ShardIndex.Overlaps(tc.startTime, tc.endTime) 138 assert.Equal(t, tc.expected, result, 139 "Overlaps(%v, %v) = %v, expected %v", 140 tc.startTime, tc.endTime, result, tc.expected) 141 }) 142 } 143 144 return nil 145 })) 146 } 147 148 func TestIndexStore_DeleteShard(t *testing.T) { 149 createBlock := func(id, tenant string, shard uint32) *metastorev1.BlockMeta { 150 return &metastorev1.BlockMeta{ 151 Id: id, 152 Tenant: 1, 153 Shard: shard, 154 MinTime: test.UnixMilli("2024-01-01T10:00:00.000Z"), 155 MaxTime: test.UnixMilli("2024-01-01T11:00:00.000Z"), 156 StringTable: []string{"", tenant}, 157 } 158 } 159 160 storeBlock := func(t *testing.T, db *bbolt.DB, p Partition, tenant string, shard uint32, block *metastorev1.BlockMeta) { 161 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 162 return NewShard(p, tenant, shard).Store(tx, block) 163 })) 164 } 165 166 assertShard := func(t *testing.T, db *bbolt.DB, store *IndexStore, p Partition, tenant string, shard uint32, exists bool) { 167 require.NoError(t, db.View(func(tx *bbolt.Tx) error { 168 s, err := store.LoadShard(tx, p, tenant, shard) 169 if exists { 170 assert.NoError(t, err) 171 assert.NotNil(t, s) 172 } else { 173 assert.Nil(t, s) 174 } 175 return nil 176 })) 177 } 178 179 assertPartition := func(t *testing.T, db *bbolt.DB, _ *IndexStore, p Partition, exists bool) { 180 require.NoError(t, db.View(func(tx *bbolt.Tx) error { 181 q := p.Query(tx) 182 if exists { 183 assert.NotNil(t, q) 184 } else { 185 assert.Nil(t, q) 186 } 187 return nil 188 })) 189 } 190 191 t.Run("basic deletion", func(t *testing.T) { 192 db := test.BoltDB(t) 193 store := NewIndexStore() 194 require.NoError(t, db.Update(store.CreateBuckets)) 195 196 p := NewPartition(test.Time("2024-01-01T10:00:00.000Z"), 6*time.Hour) 197 198 storeBlock(t, db, p, testTenant, 1, createBlock("block1", testTenant, 1)) 199 storeBlock(t, db, p, testTenant, 2, createBlock("block2", testTenant, 2)) 200 201 assertShard(t, db, store, p, testTenant, 1, true) 202 assertShard(t, db, store, p, testTenant, 2, true) 203 204 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 205 return store.DeleteShard(tx, p, testTenant, 1) 206 })) 207 208 assertShard(t, db, store, p, testTenant, 1, false) 209 assertShard(t, db, store, p, testTenant, 2, true) 210 }) 211 212 t.Run("delete non-existent shard", func(t *testing.T) { 213 db := test.BoltDB(t) 214 store := NewIndexStore() 215 require.NoError(t, db.Update(store.CreateBuckets)) 216 217 p := NewPartition(test.Time("2024-01-01T10:00:00.000Z"), 6*time.Hour) 218 219 err := db.Update(func(tx *bbolt.Tx) error { 220 return store.DeleteShard(tx, p, "non-existent", 999) 221 }) 222 assert.NoError(t, err) 223 }) 224 225 t.Run("tenant bucket cleanup", func(t *testing.T) { 226 db := test.BoltDB(t) 227 store := NewIndexStore() 228 require.NoError(t, db.Update(store.CreateBuckets)) 229 230 p := NewPartition(test.Time("2024-01-01T10:00:00.000Z"), 6*time.Hour) 231 232 storeBlock(t, db, p, testTenant, 1, createBlock("block1", testTenant, 1)) 233 234 assertShard(t, db, store, p, testTenant, 1, true) 235 assertPartition(t, db, store, p, true) 236 237 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 238 return store.DeleteShard(tx, p, testTenant, 1) 239 })) 240 241 assertShard(t, db, store, p, testTenant, 1, false) 242 assertPartition(t, db, store, p, false) 243 }) 244 245 t.Run("partition bucket cleanup with multiple tenants", func(t *testing.T) { 246 db := test.BoltDB(t) 247 store := NewIndexStore() 248 require.NoError(t, db.Update(store.CreateBuckets)) 249 250 p := NewPartition(test.Time("2024-01-01T10:00:00.000Z"), 6*time.Hour) 251 tenant1, tenant2 := "tenant-1", "tenant-2" 252 253 storeBlock(t, db, p, tenant1, 1, createBlock("block1", tenant1, 1)) 254 storeBlock(t, db, p, tenant2, 1, createBlock("block2", tenant2, 1)) 255 256 assertShard(t, db, store, p, tenant1, 1, true) 257 assertShard(t, db, store, p, tenant2, 1, true) 258 assertPartition(t, db, store, p, true) 259 260 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 261 return store.DeleteShard(tx, p, tenant1, 1) 262 })) 263 264 assertShard(t, db, store, p, tenant1, 1, false) 265 assertShard(t, db, store, p, tenant2, 1, true) 266 assertPartition(t, db, store, p, true) 267 268 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 269 return store.DeleteShard(tx, p, tenant2, 1) 270 })) 271 272 assertShard(t, db, store, p, tenant1, 1, false) 273 assertShard(t, db, store, p, tenant2, 1, false) 274 assertPartition(t, db, store, p, false) 275 }) 276 277 t.Run("multiple shards same tenant", func(t *testing.T) { 278 db := test.BoltDB(t) 279 store := NewIndexStore() 280 require.NoError(t, db.Update(store.CreateBuckets)) 281 282 p := NewPartition(test.Time("2024-01-01T10:00:00.000Z"), 6*time.Hour) 283 284 storeBlock(t, db, p, testTenant, 1, createBlock("block1", testTenant, 1)) 285 storeBlock(t, db, p, testTenant, 2, createBlock("block2", testTenant, 2)) 286 storeBlock(t, db, p, testTenant, 3, createBlock("block3", testTenant, 3)) 287 288 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 289 return store.DeleteShard(tx, p, testTenant, 2) 290 })) 291 292 assertShard(t, db, store, p, testTenant, 1, true) 293 assertShard(t, db, store, p, testTenant, 2, false) 294 assertShard(t, db, store, p, testTenant, 3, true) 295 assertPartition(t, db, store, p, true) 296 297 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 298 return store.DeleteShard(tx, p, testTenant, 1) 299 })) 300 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 301 return store.DeleteShard(tx, p, testTenant, 3) 302 })) 303 304 assertShard(t, db, store, p, testTenant, 1, false) 305 assertShard(t, db, store, p, testTenant, 2, false) 306 assertShard(t, db, store, p, testTenant, 3, false) 307 assertPartition(t, db, store, p, false) 308 }) 309 310 t.Run("multiple partitions isolation", func(t *testing.T) { 311 db := test.BoltDB(t) 312 store := NewIndexStore() 313 require.NoError(t, db.Update(store.CreateBuckets)) 314 315 p1 := NewPartition(test.Time("2024-01-01T10:00:00.000Z"), 6*time.Hour) 316 p2 := NewPartition(test.Time("2024-01-01T16:00:00.000Z"), 6*time.Hour) 317 318 storeBlock(t, db, p1, testTenant, 1, createBlock("block1", testTenant, 1)) 319 storeBlock(t, db, p2, testTenant, 1, createBlock("block2", testTenant, 1)) 320 321 assertShard(t, db, store, p1, testTenant, 1, true) 322 assertShard(t, db, store, p2, testTenant, 1, true) 323 324 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 325 return store.DeleteShard(tx, p1, testTenant, 1) 326 })) 327 328 assertShard(t, db, store, p1, testTenant, 1, false) 329 assertShard(t, db, store, p2, testTenant, 1, true) 330 assertPartition(t, db, store, p1, false) 331 assertPartition(t, db, store, p2, true) 332 }) 333 }