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 }