github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/storage_delete.go (about)

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/pyroscope-io/pyroscope/pkg/storage/dimension"
     8  	"github.com/pyroscope-io/pyroscope/pkg/storage/segment"
     9  )
    10  
    11  type DeleteInput struct {
    12  	// Key must match exactly one segment.
    13  	Key *segment.Key
    14  }
    15  
    16  func (s *Storage) Delete(_ context.Context, di *DeleteInput) error {
    17  	return s.deleteSegmentAndRelatedData(di.Key)
    18  }
    19  
    20  func (s *Storage) deleteSegmentAndRelatedData(k *segment.Key) error {
    21  	sk := k.SegmentKey()
    22  	if err := s.trees.DiscardPrefix(sk); err != nil {
    23  		return err
    24  	}
    25  	for key, value := range k.Labels() {
    26  		d, ok := s.lookupDimensionKV(key, value)
    27  		if !ok {
    28  			continue
    29  		}
    30  		d.Delete(dimension.Key(sk))
    31  
    32  		if len(d.Keys) > 0 {
    33  			continue
    34  		}
    35  		// There are no more references.
    36  		if err := s.labels.Delete(key, value); err != nil {
    37  			return err
    38  		}
    39  		if key == "__name__" {
    40  			if err := s.dicts.Delete(k.DictKey()); err != nil {
    41  				return err
    42  			}
    43  		}
    44  	}
    45  	return s.segments.Delete(sk)
    46  }
    47  
    48  // DeleteApp fully deletes an app
    49  // It does so by deleting Segments, Dictionaries, Trees, Dimensions and Labels
    50  // It's an idempotent call, ie. if the app already does not exist, no error is triggered.
    51  // TODO cancelation?
    52  func (s *Storage) DeleteApp(_ context.Context, appname string) error {
    53  	/***********************************/
    54  	/*      V a l i d a t i o n s      */
    55  	/***********************************/
    56  	s.logger.Debugf("deleting app '%s' \n", appname)
    57  	key, err := segment.ParseKey(appname)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	// the only label expected is __name__
    63  	s.logger.Debugf("found %d labels\n", len(key.Labels()))
    64  	if len(key.Labels()) != 1 {
    65  		return fmt.Errorf("only app name is supported")
    66  	}
    67  
    68  	s.logger.Debugf("looking for __name__ key\n")
    69  	nameKey := "__name__"
    70  	_, ok := key.Labels()[nameKey]
    71  	if !ok {
    72  		return fmt.Errorf("could not __name__ key")
    73  	}
    74  
    75  	/*****************************/
    76  	/*      D e l e t i o n      */
    77  	/*****************************/
    78  
    79  	//  d is the dimension of the application. Its 'Keys' member lists all
    80  	//  the segments of the application. Every segments describes a series –
    81  	//  a unique combination of the label key-value pairs. These segments have
    82  	//  to be removed.
    83  	//
    84  	//  For example, given the app name "my_application". It's dimension could
    85  	//  be represented as follows (we assume that there were two app instances
    86  	//  with distinct tag sets):
    87  	//    __name__=my_application
    88  	//      my_application{foo=bar,baz=qux}
    89  	//      my_application{foo=bar,baz=wadlo}
    90  	//
    91  	//  Although as far as we drop the whole application, we could delete this
    92  	//  dimension, we also have to take care of the dimensions affected by the app
    93  	//  segments:
    94  	//    my_application{foo=bar,baz=qux}
    95  	//    my_application{foo=bar,baz=wadlo}
    96  	//
    97  	//  In the example these dimensions are 'foo:bar', 'baz:qux', 'baz:wadlo'.
    98  	//  We have to remove app segment keys from all the associated dimensions
    99  	//  and, if the dimension is not referenced anymore, remove it alongside
   100  	//  which the label KV pair itself:
   101  	//    foo=bar
   102  	//      my_application{foo=bar,baz=qux}
   103  	//      my_application{foo=bar,baz=wadlo}
   104  	//    baz=qux
   105  	//      my_application{foo=bar,baz=qux}
   106  	//    baz=wadlo
   107  	//      my_application{foo=bar,baz=wadlo}
   108  	//
   109  	//  As you can see, these dimensions (and labels) are to be removed, simply
   110  	//  because there are no more segments/apps referencing the dimension.
   111  	//  But let's say we have one more app:
   112  	//    __name__=my_another_application
   113  	//      my_another_application{foo=bar}
   114  	//
   115  	//  Thus, our 'foo=bar' dimension would look differently:
   116  	//    foo=bar
   117  	//      my_application{foo=bar,baz=qux}
   118  	//      my_application{foo=bar,baz=wadlo}
   119  	//      my_another_application{foo=bar}
   120  	//
   121  	// In this case, 'foo=bar' dimension (and the corresponding label KV pair)
   122  	// must not be removed when we delete 'my_application' but should only
   123  	// include 'my_another_application':
   124  	//   foo=bar
   125  	//     my_another_application{foo=bar}
   126  	s.logger.Debugf("looking for app dimension '%s'\n", appname)
   127  	d, ok := s.lookupAppDimension(appname)
   128  	if !ok {
   129  		// Technically this does not necessarily mean the dimension does not exist
   130  		// Since this could be triggered by an error
   131  		s.logger.Debugf("dimensions could not be found, exiting early")
   132  		return nil
   133  	}
   134  
   135  	s.logger.Debugf("iterating over dimension keys (aka segments keys)\n")
   136  	for _, segmentKey := range d.Keys {
   137  		sk2, err := segment.ParseKey(string(segmentKey))
   138  		if err != nil {
   139  			return err
   140  		}
   141  
   142  		s.logger.Debugf("iterating over segment %s labels\n", segmentKey)
   143  		for labelKey, labelValue := range sk2.Labels() {
   144  			// skip __name__, since this is the dimension we are already iterating
   145  			if labelKey == "__name__" {
   146  				continue
   147  			}
   148  
   149  			s.logger.Debugf("looking up dimension with key='%s' and value='%s'\n", labelKey, labelValue)
   150  			d2, ok := s.lookupDimensionKV(labelKey, labelValue)
   151  			if !ok {
   152  				s.logger.Debugf("skipping since dimension could not be found\n")
   153  				continue
   154  			}
   155  
   156  			s.logger.Debugf("deleting dimension with key %s\n", segmentKey)
   157  			d2.Delete(dimension.Key(segmentKey))
   158  
   159  			// We can only delete the dimension once it's not pointing to any segments
   160  			if len(d2.Keys) > 0 {
   161  				s.logger.Debugf("dimension is still pointing to valid segments. not deleting it. \n")
   162  				continue
   163  			}
   164  
   165  			s.logger.Debugf("deleting labels %s=%s \n", labelKey, labelValue)
   166  			if err := s.labels.Delete(labelKey, labelValue); err != nil {
   167  				return err
   168  			}
   169  
   170  			s.logger.Debugf("deleting dimension %s=%s \n", labelKey, labelValue)
   171  			if err := s.dimensions.Delete(labelKey + ":" + labelValue); err != nil {
   172  				return err
   173  			}
   174  		}
   175  	}
   176  
   177  	appWithCurlyBrackets := appname + "{"
   178  
   179  	s.logger.Debugf("deleting trees with prefix %s\n", appWithCurlyBrackets)
   180  	if err = s.trees.DiscardPrefix(appWithCurlyBrackets); err != nil {
   181  		return err
   182  	}
   183  
   184  	s.logger.Debugf("deleting segments with prefix %s\n", appWithCurlyBrackets)
   185  	if err = s.segments.DiscardPrefix(appWithCurlyBrackets); err != nil {
   186  		return err
   187  	}
   188  
   189  	s.logger.Debugf("deleting dicts %s\n", key.DictKey())
   190  	if err := s.dicts.Delete(key.DictKey()); err != nil {
   191  		return err
   192  	}
   193  
   194  	s.logger.Debugf("deleting labels\n")
   195  	if err := s.labels.Delete("__name__", appname); err != nil {
   196  		return err
   197  	}
   198  
   199  	s.logger.Debugf("deleting dimensions for __name__=%s\n", appname)
   200  	return s.dimensions.Delete("__name__:" + appname)
   201  }