github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/migration.go (about) 1 package storage 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 9 "github.com/dgraph-io/badger/v2" 10 11 "github.com/pyroscope-io/pyroscope/pkg/storage/dict" 12 "github.com/pyroscope-io/pyroscope/pkg/storage/segment" 13 ) 14 15 var migrations = []migration{ 16 migrateDictionaryKeys, 17 } 18 19 type migration func(*Storage) error 20 21 const dbVersionKey = "db-version" 22 23 func (s *Storage) migrate() error { 24 ver, err := s.dbVersion() 25 if err != nil { 26 return err 27 } 28 switch { 29 case ver == len(migrations): 30 return nil 31 case ver > len(migrations): 32 return fmt.Errorf("db version %d: future versions are not supported", ver) 33 } 34 for v, m := range migrations[ver:] { 35 if err = m(s); err != nil { 36 return fmt.Errorf("migration %d: %w", v, err) 37 } 38 } 39 return s.setDbVersion(len(migrations)) 40 } 41 42 // dbVersion returns the number of migrations applied to the storage. 43 func (s *Storage) dbVersion() (int, error) { 44 var version int 45 err := s.main.View(func(txn *badger.Txn) error { 46 item, err := txn.Get([]byte(dbVersionKey)) 47 if err != nil { 48 return err 49 } 50 return item.Value(func(val []byte) error { 51 version, err = strconv.Atoi(string(val)) 52 return err 53 }) 54 }) 55 if errors.Is(err, badger.ErrKeyNotFound) { 56 return 0, nil 57 } 58 return version, err 59 } 60 61 func (s *Storage) setDbVersion(v int) error { 62 return s.main.Update(func(txn *badger.Txn) error { 63 return txn.SetEntry(&badger.Entry{ 64 Key: []byte(dbVersionKey), 65 Value: []byte(strconv.Itoa(v)), 66 }) 67 }) 68 } 69 70 // In 0.0.34 we changed dictionary key format from normalized segment key 71 // (e.g, app.name{foo=bar}) to just app name. See e756a200a for details. 72 // On deserialization, when a dictionary is loaded from disk to cache, the 73 // logic was to check both keys: if app name key exists, use the found 74 // dictionary, otherwise lookup the dictionary using normalized segment key. 75 // The problem is that the check never reported false, returning an empty 76 // dictionary instead. Thus, dictionaries created in 0.0.34 and 0.0.35 may be 77 // incomplete, which results in "label not found" nodes in rendered trees. 78 // 79 // Depending on the version, migration has different impact: 80 // * < 0.0.34: 81 // Dictionary keys to be renamed to app name format. No negative impact. 82 // * > 0.0.33: 83 // No impact. Data ingested prior to the update to 0.0.34/0.0.35 is 84 // corrupted, which results in "label not found" nodes. 85 func migrateDictionaryKeys(s *Storage) error { 86 appNameKeys := map[string]struct{}{} 87 segmentNameKeys := map[string][]byte{} 88 return s.dicts.Update(func(txn *badger.Txn) error { 89 opts := badger.DefaultIteratorOptions 90 opts.Prefix = dictionaryPrefix.bytes() 91 it := txn.NewIterator(opts) 92 defer it.Close() 93 // Find all dicts with keys: 94 // - in normalized segment key format. 95 // - in application name format. 96 for it.Rewind(); it.Valid(); it.Next() { 97 item := it.Item() 98 k := item.Key() 99 item.ExpiresAt() 100 k, ok := dictionaryPrefix.trim(k) 101 if !ok { 102 continue 103 } 104 // Make sure the dictionary is valid. 105 b, err := item.ValueCopy(nil) 106 if err != nil { 107 return err 108 } 109 d, err := dict.FromBytes(b) 110 if err != nil { 111 return err 112 } 113 if d == nil { 114 continue 115 } 116 if !strings.Contains(string(k), "{") { 117 appNameKeys[string(k)] = struct{}{} 118 } else { 119 segmentNameKeys[string(k)] = b 120 } 121 } 122 123 for k, v := range segmentNameKeys { 124 dictKey := segment.FromTreeToDictKey(k) 125 if _, ok := appNameKeys[dictKey]; ok { 126 // The dictionary is most likely incomplete and causes 127 // the problem described in the function comment. 128 continue 129 } 130 // Migration from version before 0.0.34. 131 if err := txn.Set(dictionaryPrefix.key(dictKey), v); err != nil { 132 return err 133 } 134 // Remove dict stored with old keys. 135 if err := txn.Delete(dictionaryPrefix.key(k)); err != nil { 136 return err 137 } 138 } 139 140 return nil 141 }) 142 }