github.com/weaviate/weaviate@v1.24.6/usecases/replica/finder.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package replica 13 14 import ( 15 "context" 16 "errors" 17 "fmt" 18 19 enterrors "github.com/weaviate/weaviate/entities/errors" 20 21 "github.com/go-openapi/strfmt" 22 "github.com/sirupsen/logrus" 23 "github.com/weaviate/weaviate/entities/additional" 24 "github.com/weaviate/weaviate/entities/search" 25 "github.com/weaviate/weaviate/entities/storobj" 26 "github.com/weaviate/weaviate/usecases/objects" 27 ) 28 29 var ( 30 // msgCLevel consistency level cannot be achieved 31 msgCLevel = "cannot achieve consistency level" 32 33 errReplicas = errors.New("cannot reach enough replicas") 34 errRepair = errors.New("read repair error") 35 errRead = errors.New("read error") 36 ) 37 38 type ( 39 // senderReply is a container for the data received from a replica 40 senderReply[T any] struct { 41 sender string // hostname of the sender 42 Version int64 // sender's current version of the object 43 Data T // the data sent by the sender 44 UpdateTime int64 // sender's current update time 45 DigestRead bool 46 } 47 findOneReply senderReply[objects.Replica] 48 existReply struct { 49 Sender string 50 RepairResponse 51 } 52 ) 53 54 // Finder finds replicated objects 55 type Finder struct { 56 resolver *resolver // host names of replicas 57 finderStream // stream of objects 58 } 59 60 // NewFinder constructs a new finder instance 61 func NewFinder(className string, 62 resolver *resolver, 63 client rClient, 64 l logrus.FieldLogger, 65 ) *Finder { 66 cl := finderClient{client} 67 return &Finder{ 68 resolver: resolver, 69 finderStream: finderStream{ 70 repairer: repairer{ 71 class: className, 72 client: cl, 73 logger: l, 74 }, 75 log: l, 76 }, 77 } 78 } 79 80 // GetOne gets object which satisfies the giving consistency 81 func (f *Finder) GetOne(ctx context.Context, 82 l ConsistencyLevel, shard string, 83 id strfmt.UUID, 84 props search.SelectProperties, 85 adds additional.Properties, 86 ) (*storobj.Object, error) { 87 c := newReadCoordinator[findOneReply](f, shard) 88 op := func(ctx context.Context, host string, fullRead bool) (findOneReply, error) { 89 if fullRead { 90 r, err := f.client.FullRead(ctx, host, f.class, shard, id, props, adds) 91 return findOneReply{host, 0, r, r.UpdateTime(), false}, err 92 } else { 93 xs, err := f.client.DigestReads(ctx, host, f.class, shard, []strfmt.UUID{id}) 94 var x RepairResponse 95 if len(xs) == 1 { 96 x = xs[0] 97 } 98 r := objects.Replica{ID: id, Deleted: x.Deleted} 99 return findOneReply{host, x.Version, r, x.UpdateTime, true}, err 100 } 101 } 102 replyCh, state, err := c.Pull(ctx, l, op, "") 103 if err != nil { 104 f.log.WithField("op", "pull.one").Error(err) 105 return nil, fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas) 106 } 107 result := <-f.readOne(ctx, shard, id, replyCh, state) 108 if err = result.Err; err != nil { 109 err = fmt.Errorf("%s %q: %w", msgCLevel, l, err) 110 } 111 return result.Value, err 112 } 113 114 type ShardDesc struct { 115 Name string 116 Node string 117 } 118 119 // CheckConsistency for objects belonging to different physical shards. 120 // 121 // For each x in xs the fields BelongsToNode and BelongsToShard must be set non empty 122 func (f *Finder) CheckConsistency(ctx context.Context, 123 l ConsistencyLevel, xs []*storobj.Object, 124 ) (retErr error) { 125 if len(xs) == 0 { 126 return nil 127 } 128 for i, x := range xs { // check shard and node name are set 129 if x == nil { 130 return fmt.Errorf("contains nil at object at index %d", i) 131 } 132 if x.BelongsToNode == "" || x.BelongsToShard == "" { 133 return fmt.Errorf("missing node or shard at index %d", i) 134 } 135 } 136 137 if l == One { // already consistent 138 for i := range xs { 139 xs[i].IsConsistent = true 140 } 141 return nil 142 } 143 // check shard consistency concurrently 144 gr, ctx := enterrors.NewErrorGroupWithContextWrapper(f.logger, ctx) 145 for _, part := range cluster(createBatch(xs)) { 146 part := part 147 gr.Go(func() error { 148 _, err := f.checkShardConsistency(ctx, l, part) 149 if err != nil { 150 f.log.WithField("op", "check_shard_consistency"). 151 WithField("shard", part.Shard).Error(err) 152 } 153 return err 154 }, part) 155 } 156 return gr.Wait() 157 } 158 159 // Exists checks if an object exists which satisfies the giving consistency 160 func (f *Finder) Exists(ctx context.Context, 161 l ConsistencyLevel, 162 shard string, 163 id strfmt.UUID, 164 ) (bool, error) { 165 c := newReadCoordinator[existReply](f, shard) 166 op := func(ctx context.Context, host string, _ bool) (existReply, error) { 167 xs, err := f.client.DigestReads(ctx, host, f.class, shard, []strfmt.UUID{id}) 168 var x RepairResponse 169 if len(xs) == 1 { 170 x = xs[0] 171 } 172 return existReply{host, x}, err 173 } 174 replyCh, state, err := c.Pull(ctx, l, op, "") 175 if err != nil { 176 f.log.WithField("op", "pull.exist").Error(err) 177 return false, fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas) 178 } 179 result := <-f.readExistence(ctx, shard, id, replyCh, state) 180 if err = result.Err; err != nil { 181 err = fmt.Errorf("%s %q: %w", msgCLevel, l, err) 182 } 183 return result.Value, err 184 } 185 186 // NodeObject gets object from a specific node. 187 // it is used mainly for debugging purposes 188 func (f *Finder) NodeObject(ctx context.Context, 189 nodeName, 190 shard string, 191 id strfmt.UUID, 192 props search.SelectProperties, adds additional.Properties, 193 ) (*storobj.Object, error) { 194 host, ok := f.resolver.NodeHostname(nodeName) 195 if !ok || host == "" { 196 return nil, fmt.Errorf("cannot resolve node name: %s", nodeName) 197 } 198 r, err := f.client.FullRead(ctx, host, f.class, shard, id, props, adds) 199 return r.Object, err 200 } 201 202 // checkShardConsistency checks consistency for a set of objects belonging to a shard 203 // It returns the most recent objects or and error 204 func (f *Finder) checkShardConsistency(ctx context.Context, 205 l ConsistencyLevel, 206 batch shardPart, 207 ) ([]*storobj.Object, error) { 208 var ( 209 c = newReadCoordinator[batchReply](f, batch.Shard) 210 shard = batch.Shard 211 data, ids = batch.Extract() // extract from current content 212 ) 213 op := func(ctx context.Context, host string, fullRead bool) (batchReply, error) { 214 if fullRead { // we already have the content 215 return batchReply{Sender: host, IsDigest: false, FullData: data}, nil 216 } else { 217 xs, err := f.client.DigestReads(ctx, host, f.class, shard, ids) 218 return batchReply{Sender: host, IsDigest: true, DigestData: xs}, err 219 } 220 } 221 222 replyCh, state, err := c.Pull(ctx, l, op, batch.Node) 223 if err != nil { 224 return nil, fmt.Errorf("pull shard: %w", errReplicas) 225 } 226 result := <-f.readBatchPart(ctx, batch, ids, replyCh, state) 227 return result.Value, result.Err 228 }