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  }