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  }