github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/ingester/transfer.go (about) 1 package ingester 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "time" 8 9 "github.com/go-kit/log/level" 10 "github.com/grafana/dskit/backoff" 11 "github.com/grafana/dskit/ring" 12 "github.com/pkg/errors" 13 "github.com/prometheus/client_golang/prometheus" 14 "github.com/prometheus/client_golang/prometheus/promauto" 15 "github.com/prometheus/prometheus/model/labels" 16 "github.com/weaveworks/common/user" 17 "golang.org/x/net/context" 18 19 "github.com/grafana/loki/pkg/logproto" 20 lokiutil "github.com/grafana/loki/pkg/util" 21 util_log "github.com/grafana/loki/pkg/util/log" 22 ) 23 24 var ( 25 sentChunks = promauto.NewCounter(prometheus.CounterOpts{ 26 Namespace: "loki", 27 Name: "ingester_sent_chunks", 28 Help: "The total number of chunks sent by this ingester whilst leaving.", 29 }) 30 receivedChunks = promauto.NewCounter(prometheus.CounterOpts{ 31 Namespace: "loki", 32 Name: "ingester_received_chunks", 33 Help: "The total number of chunks received by this ingester whilst joining.", 34 }) 35 ) 36 37 // TransferChunks receives all chunks from another ingester. The Ingester 38 // must be in PENDING state or else the call will fail. 39 func (i *Ingester) TransferChunks(stream logproto.Ingester_TransferChunksServer) error { 40 logger := util_log.WithContext(stream.Context(), util_log.Logger) 41 // Prevent a shutdown from happening until we've completely finished a handoff 42 // from a leaving ingester. 43 i.shutdownMtx.Lock() 44 defer i.shutdownMtx.Unlock() 45 46 // Entry JOINING state (only valid from PENDING) 47 if err := i.lifecycler.ChangeState(stream.Context(), ring.JOINING); err != nil { 48 return err 49 } 50 51 // The ingesters state effectively works as a giant mutex around this 52 // whole method, and as such we have to ensure we unlock the mutex. 53 defer func() { 54 state := i.lifecycler.GetState() 55 if i.lifecycler.GetState() == ring.ACTIVE { 56 return 57 } 58 59 level.Error(logger).Log("msg", "TransferChunks failed, not in ACTIVE state.", "state", state) 60 61 // Enter PENDING state (only valid from JOINING) 62 if i.lifecycler.GetState() == ring.JOINING { 63 // Create a new context here to attempt to update the state back to pending to allow 64 // a failed transfer to try again. If we fail to set the state back to PENDING then 65 // exit Loki as we will effectively be hung anyway stuck in a JOINING state and will 66 // never join. 67 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) 68 if err := i.lifecycler.ChangeState(ctx, ring.PENDING); err != nil { 69 level.Error(logger).Log("msg", "failed to update the ring state back to PENDING after "+ 70 "a chunk transfer failure, there is nothing more Loki can do from this state "+ 71 "so the process will exit...", "err", err) 72 os.Exit(1) 73 } 74 cancel() 75 } 76 }() 77 78 fromIngesterID := "" 79 seriesReceived := 0 80 81 for { 82 chunkSet, err := stream.Recv() 83 if err == io.EOF { 84 break 85 } else if err != nil { 86 return err 87 } 88 89 // We can't send "extra" fields with a streaming call, so we repeat 90 // chunkSet.FromIngesterId and assume it is the same every time around 91 // this loop. 92 if fromIngesterID == "" { 93 fromIngesterID = chunkSet.FromIngesterId 94 level.Info(logger).Log("msg", "processing TransferChunks request", "from_ingester", fromIngesterID) 95 96 // Before transfer, make sure 'from' ingester is in correct state to call ClaimTokensFor later 97 err := i.checkFromIngesterIsInLeavingState(stream.Context(), fromIngesterID) 98 if err != nil { 99 return errors.Wrap(err, "TransferChunks: checkFromIngesterIsInLeavingState") 100 } 101 } 102 103 userCtx := user.InjectOrgID(stream.Context(), chunkSet.UserId) 104 105 lbls := make([]labels.Label, 0, len(chunkSet.Labels)) 106 for _, lbl := range chunkSet.Labels { 107 lbls = append(lbls, labels.Label{Name: lbl.Name, Value: lbl.Value}) 108 } 109 110 instance, err := i.GetOrCreateInstance(chunkSet.UserId) 111 if err != nil { 112 return err 113 } 114 for _, chunk := range chunkSet.Chunks { 115 if err := instance.consumeChunk(userCtx, lbls, chunk); err != nil { 116 return err 117 } 118 } 119 120 seriesReceived++ 121 receivedChunks.Add(float64(len(chunkSet.Chunks))) 122 } 123 124 if seriesReceived == 0 { 125 level.Error(logger).Log("msg", "received TransferChunks request with no series", "from_ingester", fromIngesterID) 126 return fmt.Errorf("no series") 127 } else if fromIngesterID == "" { 128 level.Error(logger).Log("msg", "received TransferChunks request with no ID from ingester") 129 return fmt.Errorf("no ingester id") 130 } 131 132 if err := i.lifecycler.ClaimTokensFor(stream.Context(), fromIngesterID); err != nil { 133 return err 134 } 135 136 if err := i.lifecycler.ChangeState(stream.Context(), ring.ACTIVE); err != nil { 137 return err 138 } 139 140 // Close the stream last, as this is what tells the "from" ingester that 141 // it's OK to shut down. 142 if err := stream.SendAndClose(&logproto.TransferChunksResponse{}); err != nil { 143 level.Error(logger).Log("msg", "Error closing TransferChunks stream", "from_ingester", fromIngesterID, "err", err) 144 return err 145 } 146 level.Info(logger).Log("msg", "Successfully transferred chunks", "from_ingester", fromIngesterID, "series_received", seriesReceived) 147 return nil 148 } 149 150 // Ring gossiping: check if "from" ingester is in LEAVING state. It should be, but we may not see that yet 151 // when using gossip ring. If we cannot see ingester is the LEAVING state yet, we don't accept this 152 // transfer, as claiming tokens would possibly end up with this ingester owning no tokens, due to conflict 153 // resolution in ring merge function. Hopefully the leaving ingester will retry transfer again. 154 func (i *Ingester) checkFromIngesterIsInLeavingState(ctx context.Context, fromIngesterID string) error { 155 v, err := i.lifecycler.KVStore.Get(ctx, RingKey) 156 if err != nil { 157 return errors.Wrap(err, "get ring") 158 } 159 if v == nil { 160 return fmt.Errorf("ring not found when checking state of source ingester") 161 } 162 r, ok := v.(*ring.Desc) 163 if !ok || r == nil { 164 return fmt.Errorf("ring not found, got %T", v) 165 } 166 167 if r.Ingesters == nil || r.Ingesters[fromIngesterID].State != ring.LEAVING { 168 return fmt.Errorf("source ingester is not in a LEAVING state, found state=%v", r.Ingesters[fromIngesterID].State) 169 } 170 171 // all fine 172 return nil 173 } 174 175 // stopIncomingRequests is called when ingester is stopping 176 func (i *Ingester) stopIncomingRequests() { 177 i.shutdownMtx.Lock() 178 defer i.shutdownMtx.Unlock() 179 180 i.instancesMtx.Lock() 181 defer i.instancesMtx.Unlock() 182 183 i.readonly = true 184 } 185 186 // TransferOut implements ring.Lifecycler. 187 func (i *Ingester) TransferOut(ctx context.Context) error { 188 if i.cfg.MaxTransferRetries <= 0 { 189 return ring.ErrTransferDisabled 190 } 191 192 backoff := backoff.New(ctx, backoff.Config{ 193 MinBackoff: 100 * time.Millisecond, 194 MaxBackoff: 5 * time.Second, 195 MaxRetries: i.cfg.MaxTransferRetries, 196 }) 197 198 for backoff.Ongoing() { 199 err := i.transferOut(ctx) 200 if err == nil { 201 return nil 202 } 203 204 level.Error(util_log.WithContext(ctx, util_log.Logger)).Log("msg", "transfer failed", "err", err) 205 backoff.Wait() 206 } 207 208 return backoff.Err() 209 } 210 211 func (i *Ingester) transferOut(ctx context.Context) error { 212 logger := util_log.WithContext(ctx, util_log.Logger) 213 targetIngester, err := i.findTransferTarget(ctx) 214 if err != nil { 215 return fmt.Errorf("cannot find ingester to transfer chunks to: %v", err) 216 } 217 218 level.Info(logger).Log("msg", "sending chunks", "to_ingester", targetIngester.Addr) 219 c, err := i.cfg.ingesterClientFactory(i.clientConfig, targetIngester.Addr) 220 if err != nil { 221 return err 222 } 223 if c, ok := c.(io.Closer); ok { 224 defer lokiutil.LogErrorWithContext(ctx, "closing client", c.Close) 225 } 226 ic := c.(logproto.IngesterClient) 227 228 ctx = user.InjectOrgID(ctx, "-1") 229 s, err := ic.TransferChunks(ctx) 230 if err != nil { 231 return errors.Wrap(err, "TransferChunks") 232 } 233 234 for instanceID, inst := range i.instances { 235 err := inst.streams.ForEach(func(istream *stream) (bool, error) { 236 err = func() error { 237 istream.chunkMtx.Lock() 238 defer istream.chunkMtx.Unlock() 239 lbls := []*logproto.LabelPair{} 240 for _, lbl := range istream.labels { 241 lbls = append(lbls, &logproto.LabelPair{Name: lbl.Name, Value: lbl.Value}) 242 } 243 244 // We moved to sending one chunk at a time in a stream instead of sending all chunks for a stream 245 // as large chunks can create large payloads of >16MB which can hit GRPC limits, 246 // typically streams won't have many chunks in memory so sending one at a time 247 // shouldn't add too much overhead. 248 for _, c := range istream.chunks { 249 // Close the chunk first, writing any data in the headblock to a new block. 250 err := c.chunk.Close() 251 if err != nil { 252 return err 253 } 254 255 bb, err := c.chunk.Bytes() 256 if err != nil { 257 return err 258 } 259 260 chunks := make([]*logproto.Chunk, 1) 261 chunks[0] = &logproto.Chunk{ 262 Data: bb, 263 } 264 265 err = s.Send(&logproto.TimeSeriesChunk{ 266 Chunks: chunks, 267 UserId: instanceID, 268 Labels: lbls, 269 FromIngesterId: i.lifecycler.ID, 270 }) 271 if err != nil { 272 level.Error(logger).Log("msg", "failed sending stream's chunks to ingester", "to_ingester", targetIngester.Addr, "err", err) 273 return err 274 } 275 276 sentChunks.Add(float64(len(chunks))) 277 } 278 return nil 279 }() 280 if err != nil { 281 return false, err 282 } 283 return true, nil 284 }) 285 if err != nil { 286 return err 287 } 288 } 289 290 _, err = s.CloseAndRecv() 291 if err != nil { 292 return errors.Wrap(err, "CloseAndRecv") 293 } 294 295 for _, flushQueue := range i.flushQueues { 296 flushQueue.DiscardAndClose() 297 } 298 i.flushQueuesDone.Wait() 299 300 level.Info(logger).Log("msg", "successfully sent chunks", "to_ingester", targetIngester.Addr) 301 return nil 302 } 303 304 // findTransferTarget finds an ingester in a PENDING state to use for transferring 305 // chunks to. 306 func (i *Ingester) findTransferTarget(ctx context.Context) (*ring.InstanceDesc, error) { 307 ringDesc, err := i.lifecycler.KVStore.Get(ctx, RingKey) 308 if err != nil { 309 return nil, err 310 } 311 312 ingesters := ringDesc.(*ring.Desc).FindIngestersByState(ring.PENDING) 313 if len(ingesters) == 0 { 314 return nil, fmt.Errorf("no pending ingesters") 315 } 316 317 return &ingesters[0], nil 318 }