github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/collection/range.go (about)

     1  package collection
     2  
     3  import (
     4  	"sort"
     5  	"strings"
     6  	"sync/atomic"
     7  
     8  	etcd "github.com/coreos/etcd/clientv3"
     9  	"github.com/coreos/etcd/mvcc/mvccpb"
    10  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    11  	"github.com/pachyderm/pachyderm/src/server/pkg/errutil"
    12  
    13  	"google.golang.org/grpc/codes"
    14  	"google.golang.org/grpc/status"
    15  )
    16  
    17  // Options are the sort options when iterating through etcd key/values.
    18  // Currently implemented sort targets are CreateRevision and ModRevision.
    19  // The sorting can be done in the calling process by setting SelfSort to true.
    20  type Options struct {
    21  	Target   etcd.SortTarget
    22  	Order    etcd.SortOrder
    23  	SelfSort bool
    24  }
    25  
    26  // DefaultOptions are the default sort options when iterating through etcd key/values.
    27  var DefaultOptions = &Options{etcd.SortByCreateRevision, etcd.SortDescend, false}
    28  
    29  func listFuncs(opts *Options) (func(*mvccpb.KeyValue) etcd.OpOption, func(kv1 *mvccpb.KeyValue, kv2 *mvccpb.KeyValue) int) {
    30  	var from func(*mvccpb.KeyValue) etcd.OpOption
    31  	var compare func(kv1 *mvccpb.KeyValue, kv2 *mvccpb.KeyValue) int
    32  	switch opts.Target {
    33  	case etcd.SortByCreateRevision:
    34  		switch opts.Order {
    35  		case etcd.SortAscend:
    36  			from = func(fromKey *mvccpb.KeyValue) etcd.OpOption { return etcd.WithMinCreateRev(fromKey.CreateRevision) }
    37  		case etcd.SortDescend:
    38  			from = func(fromKey *mvccpb.KeyValue) etcd.OpOption { return etcd.WithMaxCreateRev(fromKey.CreateRevision) }
    39  		}
    40  		compare = func(kv1 *mvccpb.KeyValue, kv2 *mvccpb.KeyValue) int {
    41  			return int(kv1.CreateRevision - kv2.CreateRevision)
    42  		}
    43  	case etcd.SortByModRevision:
    44  		switch opts.Order {
    45  		case etcd.SortAscend:
    46  			from = func(fromKey *mvccpb.KeyValue) etcd.OpOption { return etcd.WithMinModRev(fromKey.ModRevision) }
    47  		case etcd.SortDescend:
    48  			from = func(fromKey *mvccpb.KeyValue) etcd.OpOption { return etcd.WithMaxModRev(fromKey.ModRevision) }
    49  		}
    50  		compare = func(kv1 *mvccpb.KeyValue, kv2 *mvccpb.KeyValue) int {
    51  			return int(kv1.ModRevision - kv2.ModRevision)
    52  		}
    53  	}
    54  	return from, compare
    55  }
    56  
    57  func listRevision(c *readonlyCollection, prefix string, limitPtr *int64, opts *Options, f func(*mvccpb.KeyValue) error) error {
    58  	etcdOpts := []etcd.OpOption{etcd.WithPrefix(), etcd.WithSort(opts.Target, opts.Order)}
    59  	var fromKey *mvccpb.KeyValue
    60  	from, compare := listFuncs(opts)
    61  	for {
    62  		if fromKey != nil {
    63  			etcdOpts = append(etcdOpts, from(fromKey))
    64  		}
    65  		resp, done, err := getWithLimit(c, prefix, limitPtr, etcdOpts)
    66  		if err != nil {
    67  			return err
    68  		}
    69  		kvs := getNewKeys(resp.Kvs, fromKey)
    70  		for _, kv := range kvs {
    71  			if strings.Contains(strings.TrimPrefix(string(kv.Key), prefix), indexIdentifier) {
    72  				continue
    73  			}
    74  			if err := f(kv); err != nil {
    75  				if errors.Is(err, errutil.ErrBreak) {
    76  					return nil
    77  				}
    78  				return err
    79  			}
    80  		}
    81  		if done {
    82  			return nil
    83  		}
    84  		if compare(resp.Kvs[0], resp.Kvs[len(resp.Kvs)-1]) == 0 {
    85  			return errors.Errorf("revision contains too many objects to fit in one batch (this is likely a bug)")
    86  		}
    87  		fromKey = kvs[len(kvs)-1]
    88  	}
    89  }
    90  
    91  func getNewKeys(respKvs []*mvccpb.KeyValue, fromKey *mvccpb.KeyValue) []*mvccpb.KeyValue {
    92  	if fromKey == nil {
    93  		return respKvs
    94  	}
    95  	for i, kv := range respKvs {
    96  		if string(kv.Key) == string(fromKey.Key) {
    97  			return respKvs[i+1:]
    98  		}
    99  	}
   100  	return nil
   101  }
   102  
   103  type kvSort struct {
   104  	kvs     []*mvccpb.KeyValue
   105  	compare func(kv1 *mvccpb.KeyValue, kv2 *mvccpb.KeyValue) int
   106  }
   107  
   108  func listSelfSortRevision(c *readonlyCollection, prefix string, limitPtr *int64, opts *Options, f func(*mvccpb.KeyValue) error) error {
   109  	etcdOpts := []etcd.OpOption{etcd.WithFromKey(), etcd.WithRange(endKeyFromPrefix(prefix))}
   110  	fromKey := prefix
   111  	kvs := []*mvccpb.KeyValue{}
   112  	for {
   113  		resp, done, err := getWithLimit(c, fromKey, limitPtr, etcdOpts)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		if fromKey == prefix {
   118  			kvs = append(kvs, resp.Kvs...)
   119  		} else {
   120  			kvs = append(kvs, resp.Kvs[1:]...)
   121  		}
   122  		if done {
   123  			break
   124  		}
   125  		fromKey = string(kvs[len(kvs)-1].Key)
   126  	}
   127  	_, compare := listFuncs(opts)
   128  	sorter := &kvSort{kvs, compare}
   129  	switch opts.Order {
   130  	case etcd.SortAscend:
   131  		sort.Sort(sorter)
   132  	case etcd.SortDescend:
   133  		sort.Sort(sort.Reverse(sorter))
   134  	}
   135  	for _, kv := range kvs {
   136  		if strings.Contains(strings.TrimPrefix(string(kv.Key), prefix), indexIdentifier) {
   137  			continue
   138  		}
   139  		if err := f(kv); err != nil {
   140  			if errors.Is(err, errutil.ErrBreak) {
   141  				return nil
   142  			}
   143  			return err
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  func endKeyFromPrefix(prefix string) string {
   150  	// Lexicographically increment the last character
   151  	return prefix[0:len(prefix)-1] + string(byte(prefix[len(prefix)-1])+1)
   152  }
   153  
   154  func (s *kvSort) Len() int {
   155  	return len(s.kvs)
   156  }
   157  
   158  func (s *kvSort) Less(i, j int) bool {
   159  	return s.compare(s.kvs[i], s.kvs[j]) < 0
   160  }
   161  
   162  func (s *kvSort) Swap(i, j int) {
   163  	t := s.kvs[i]
   164  	s.kvs[i] = s.kvs[j]
   165  	s.kvs[j] = t
   166  }
   167  
   168  func getWithLimit(c *readonlyCollection, key string, limitPtr *int64, opts []etcd.OpOption) (*etcd.GetResponse, bool, error) {
   169  	for {
   170  		limit := atomic.LoadInt64(limitPtr)
   171  		resp, err := c.etcdClient.Get(c.ctx, key, append(opts, etcd.WithLimit(limit))...)
   172  		if err != nil {
   173  			if status.Convert(err).Code() == codes.ResourceExhausted && limit > 1 {
   174  				atomic.CompareAndSwapInt64(limitPtr, limit, limit/2)
   175  				continue
   176  			}
   177  			return nil, false, err
   178  		}
   179  		if len(resp.Kvs) < int(limit) {
   180  			return resp, true, nil
   181  		}
   182  		return resp, false, nil
   183  	}
   184  }