github.com/weaviate/weaviate@v1.24.6/adapters/clients/replication.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 clients 13 14 import ( 15 "bytes" 16 "context" 17 "encoding/base64" 18 "encoding/json" 19 "fmt" 20 "io" 21 "math/rand" 22 "net/http" 23 "net/url" 24 "time" 25 26 "github.com/go-openapi/strfmt" 27 "github.com/weaviate/weaviate/adapters/handlers/rest/clusterapi" 28 "github.com/weaviate/weaviate/entities/additional" 29 "github.com/weaviate/weaviate/entities/search" 30 "github.com/weaviate/weaviate/entities/storobj" 31 "github.com/weaviate/weaviate/usecases/objects" 32 "github.com/weaviate/weaviate/usecases/replica" 33 ) 34 35 // ReplicationClient is to coordinate operations among replicas 36 37 type replicationClient retryClient 38 39 func NewReplicationClient(httpClient *http.Client) replica.Client { 40 return &replicationClient{ 41 client: httpClient, 42 retryer: newRetryer(), 43 } 44 } 45 46 // FetchObject fetches one object it exits 47 func (c *replicationClient) FetchObject(ctx context.Context, host, index, 48 shard string, id strfmt.UUID, selectProps search.SelectProperties, 49 additional additional.Properties, 50 ) (objects.Replica, error) { 51 resp := objects.Replica{} 52 req, err := newHttpReplicaRequest(ctx, http.MethodGet, host, index, shard, "", id.String(), nil) 53 if err != nil { 54 return resp, fmt.Errorf("create http request: %w", err) 55 } 56 err = c.doCustomUnmarshal(c.timeoutUnit*20, req, nil, resp.UnmarshalBinary) 57 return resp, err 58 } 59 60 func (c *replicationClient) DigestObjects(ctx context.Context, 61 host, index, shard string, ids []strfmt.UUID, 62 ) (result []replica.RepairResponse, err error) { 63 var resp []replica.RepairResponse 64 body, err := json.Marshal(ids) 65 if err != nil { 66 return nil, fmt.Errorf("marshal digest objects input: %w", err) 67 } 68 req, err := newHttpReplicaRequest( 69 ctx, http.MethodGet, host, index, shard, 70 "", "_digest", bytes.NewReader(body)) 71 if err != nil { 72 return resp, fmt.Errorf("create http request: %w", err) 73 } 74 err = c.do(c.timeoutUnit*20, req, body, &resp) 75 return resp, err 76 } 77 78 func (c *replicationClient) OverwriteObjects(ctx context.Context, 79 host, index, shard string, vobjects []*objects.VObject, 80 ) ([]replica.RepairResponse, error) { 81 var resp []replica.RepairResponse 82 body, err := clusterapi.IndicesPayloads.VersionedObjectList.Marshal(vobjects) 83 if err != nil { 84 return nil, fmt.Errorf("encode request: %w", err) 85 } 86 req, err := newHttpReplicaRequest( 87 ctx, http.MethodPut, host, index, shard, 88 "", "_overwrite", bytes.NewReader(body)) 89 if err != nil { 90 return resp, fmt.Errorf("create http request: %w", err) 91 } 92 err = c.do(c.timeoutUnit*90, req, body, &resp) 93 return resp, err 94 } 95 96 func (c *replicationClient) FetchObjects(ctx context.Context, host, 97 index, shard string, ids []strfmt.UUID, 98 ) ([]objects.Replica, error) { 99 resp := make(objects.Replicas, len(ids)) 100 idsBytes, err := json.Marshal(ids) 101 if err != nil { 102 return nil, fmt.Errorf("marshal ids: %w", err) 103 } 104 105 idsEncoded := base64.StdEncoding.EncodeToString(idsBytes) 106 107 req, err := newHttpReplicaRequest(ctx, http.MethodGet, host, index, shard, "", "", nil) 108 if err != nil { 109 return nil, fmt.Errorf("create http request: %w", err) 110 } 111 112 req.URL.RawQuery = url.Values{"ids": []string{idsEncoded}}.Encode() 113 err = c.doCustomUnmarshal(c.timeoutUnit*90, req, nil, resp.UnmarshalBinary) 114 return resp, err 115 } 116 117 func (c *replicationClient) PutObject(ctx context.Context, host, index, 118 shard, requestID string, obj *storobj.Object, 119 ) (replica.SimpleResponse, error) { 120 var resp replica.SimpleResponse 121 body, err := clusterapi.IndicesPayloads.SingleObject.Marshal(obj) 122 if err != nil { 123 return resp, fmt.Errorf("encode request: %w", err) 124 } 125 126 req, err := newHttpReplicaRequest(ctx, http.MethodPost, host, index, shard, requestID, "", nil) 127 if err != nil { 128 return resp, fmt.Errorf("create http request: %w", err) 129 } 130 131 clusterapi.IndicesPayloads.SingleObject.SetContentTypeHeaderReq(req) 132 err = c.do(c.timeoutUnit*90, req, body, &resp) 133 return resp, err 134 } 135 136 func (c *replicationClient) DeleteObject(ctx context.Context, host, index, 137 shard, requestID string, uuid strfmt.UUID, 138 ) (replica.SimpleResponse, error) { 139 var resp replica.SimpleResponse 140 req, err := newHttpReplicaRequest(ctx, http.MethodDelete, host, index, shard, requestID, uuid.String(), nil) 141 if err != nil { 142 return resp, fmt.Errorf("create http request: %w", err) 143 } 144 145 err = c.do(c.timeoutUnit*90, req, nil, &resp) 146 return resp, err 147 } 148 149 func (c *replicationClient) PutObjects(ctx context.Context, host, index, 150 shard, requestID string, objects []*storobj.Object, 151 ) (replica.SimpleResponse, error) { 152 var resp replica.SimpleResponse 153 body, err := clusterapi.IndicesPayloads.ObjectList.Marshal(objects) 154 if err != nil { 155 return resp, fmt.Errorf("encode request: %w", err) 156 } 157 req, err := newHttpReplicaRequest(ctx, http.MethodPost, host, index, shard, requestID, "", nil) 158 if err != nil { 159 return resp, fmt.Errorf("create http request: %w", err) 160 } 161 162 clusterapi.IndicesPayloads.ObjectList.SetContentTypeHeaderReq(req) 163 err = c.do(c.timeoutUnit*90, req, body, &resp) 164 return resp, err 165 } 166 167 func (c *replicationClient) MergeObject(ctx context.Context, host, index, shard, requestID string, 168 doc *objects.MergeDocument, 169 ) (replica.SimpleResponse, error) { 170 var resp replica.SimpleResponse 171 body, err := clusterapi.IndicesPayloads.MergeDoc.Marshal(*doc) 172 if err != nil { 173 return resp, fmt.Errorf("encode request: %w", err) 174 } 175 176 req, err := newHttpReplicaRequest(ctx, http.MethodPatch, host, index, shard, 177 requestID, doc.ID.String(), nil) 178 if err != nil { 179 return resp, fmt.Errorf("create http request: %w", err) 180 } 181 182 clusterapi.IndicesPayloads.MergeDoc.SetContentTypeHeaderReq(req) 183 err = c.do(c.timeoutUnit*90, req, body, &resp) 184 return resp, err 185 } 186 187 func (c *replicationClient) AddReferences(ctx context.Context, host, index, 188 shard, requestID string, refs []objects.BatchReference, 189 ) (replica.SimpleResponse, error) { 190 var resp replica.SimpleResponse 191 body, err := clusterapi.IndicesPayloads.ReferenceList.Marshal(refs) 192 if err != nil { 193 return resp, fmt.Errorf("encode request: %w", err) 194 } 195 req, err := newHttpReplicaRequest(ctx, http.MethodPost, host, index, shard, 196 requestID, "references", nil) 197 if err != nil { 198 return resp, fmt.Errorf("create http request: %w", err) 199 } 200 201 clusterapi.IndicesPayloads.ReferenceList.SetContentTypeHeaderReq(req) 202 err = c.do(c.timeoutUnit*90, req, body, &resp) 203 return resp, err 204 } 205 206 func (c *replicationClient) DeleteObjects(ctx context.Context, host, index, shard, requestID string, 207 uuids []strfmt.UUID, dryRun bool, 208 ) (resp replica.SimpleResponse, err error) { 209 body, err := clusterapi.IndicesPayloads.BatchDeleteParams.Marshal(uuids, dryRun) 210 if err != nil { 211 return resp, fmt.Errorf("encode request: %w", err) 212 } 213 req, err := newHttpReplicaRequest(ctx, http.MethodDelete, host, index, shard, requestID, "", nil) 214 if err != nil { 215 return resp, fmt.Errorf("create http request: %w", err) 216 } 217 218 clusterapi.IndicesPayloads.BatchDeleteParams.SetContentTypeHeaderReq(req) 219 err = c.do(c.timeoutUnit*90, req, body, &resp) 220 return resp, err 221 } 222 223 // Commit asks a host to commit and stores the response in the value pointed to by resp 224 func (c *replicationClient) Commit(ctx context.Context, host, index, shard string, requestID string, resp interface{}) error { 225 req, err := newHttpReplicaCMD(host, "commit", index, shard, requestID, nil) 226 if err != nil { 227 return fmt.Errorf("create http request: %w", err) 228 } 229 230 return c.do(c.timeoutUnit*90, req, nil, resp) 231 } 232 233 func (c *replicationClient) Abort(ctx context.Context, host, index, shard, requestID string) ( 234 resp replica.SimpleResponse, err error, 235 ) { 236 req, err := newHttpReplicaCMD(host, "abort", index, shard, requestID, nil) 237 if err != nil { 238 return resp, fmt.Errorf("create http request: %w", err) 239 } 240 241 err = c.do(c.timeoutUnit*5, req, nil, &resp) 242 return resp, err 243 } 244 245 func newHttpReplicaRequest(ctx context.Context, method, host, index, shard, requestId, suffix string, body io.Reader) (*http.Request, error) { 246 path := fmt.Sprintf("/replicas/indices/%s/shards/%s/objects", index, shard) 247 if suffix != "" { 248 path = fmt.Sprintf("%s/%s", path, suffix) 249 } 250 u := url.URL{ 251 Scheme: "http", 252 Host: host, 253 Path: path, 254 } 255 256 if requestId != "" { 257 u.RawQuery = url.Values{replica.RequestKey: []string{requestId}}.Encode() 258 } 259 260 return http.NewRequestWithContext(ctx, method, u.String(), body) 261 } 262 263 func newHttpReplicaCMD(host, cmd, index, shard, requestId string, body io.Reader) (*http.Request, error) { 264 path := fmt.Sprintf("/replicas/indices/%s/shards/%s:%s", index, shard, cmd) 265 q := url.Values{replica.RequestKey: []string{requestId}}.Encode() 266 url := url.URL{Scheme: "http", Host: host, Path: path, RawQuery: q} 267 return http.NewRequest(http.MethodPost, url.String(), body) 268 } 269 270 func (c *replicationClient) do(timeout time.Duration, req *http.Request, body []byte, resp interface{}) (err error) { 271 ctx, cancel := context.WithTimeout(req.Context(), timeout) 272 defer cancel() 273 try := func(ctx context.Context) (bool, error) { 274 if body != nil { 275 req.Body = io.NopCloser(bytes.NewReader(body)) 276 } 277 res, err := c.client.Do(req) 278 if err != nil { 279 return ctx.Err() == nil, fmt.Errorf("connect: %w", err) 280 } 281 defer res.Body.Close() 282 283 if code := res.StatusCode; code != http.StatusOK { 284 b, _ := io.ReadAll(res.Body) 285 return shouldRetry(code), fmt.Errorf("status code: %v, error: %s", code, b) 286 } 287 if err := json.NewDecoder(res.Body).Decode(resp); err != nil { 288 return false, fmt.Errorf("decode response: %w", err) 289 } 290 return false, nil 291 } 292 return c.retry(ctx, 9, try) 293 } 294 295 func (c *replicationClient) doCustomUnmarshal(timeout time.Duration, 296 req *http.Request, body []byte, decode func([]byte) error, 297 ) (err error) { 298 return (*retryClient)(c).doWithCustomMarshaller(timeout, req, body, decode) 299 } 300 301 // backOff return a new random duration in the interval [d, 3d]. 302 // It implements truncated exponential back-off with introduced jitter. 303 func backOff(d time.Duration) time.Duration { 304 return time.Duration(float64(d.Nanoseconds()*2) * (0.5 + rand.Float64())) 305 } 306 307 func shouldRetry(code int) bool { 308 return code == http.StatusInternalServerError || 309 code == http.StatusTooManyRequests || 310 code == http.StatusServiceUnavailable 311 }