github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/client/replicated_session.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package client
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"time"
    27  
    28  	"github.com/uber-go/tally"
    29  	"go.uber.org/zap"
    30  
    31  	"github.com/m3db/m3/src/dbnode/encoding"
    32  	"github.com/m3db/m3/src/dbnode/generated/thrift/rpc"
    33  	"github.com/m3db/m3/src/dbnode/namespace"
    34  	"github.com/m3db/m3/src/dbnode/storage/block"
    35  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/result"
    36  	"github.com/m3db/m3/src/dbnode/storage/index"
    37  	"github.com/m3db/m3/src/dbnode/topology"
    38  	"github.com/m3db/m3/src/x/ident"
    39  	m3sync "github.com/m3db/m3/src/x/sync"
    40  	xtime "github.com/m3db/m3/src/x/time"
    41  )
    42  
    43  type newSessionFn func(Options) (clientSession, error)
    44  
    45  // replicatedSession is an implementation of clientSession which replicates
    46  // session read/writes to a set of clusters asynchronously.
    47  type replicatedSession struct {
    48  	session              clientSession
    49  	asyncSessions        []clientSession
    50  	newSessionFn         newSessionFn
    51  	identifierPool       ident.Pool
    52  	workerPool           m3sync.PooledWorkerPool
    53  	replicationSemaphore chan struct{}
    54  	scope                tally.Scope
    55  	log                  *zap.Logger
    56  	metrics              replicatedSessionMetrics
    57  	outCh                chan error
    58  	writeTimestampOffset time.Duration
    59  }
    60  
    61  type replicatedSessionMetrics struct {
    62  	replicateExecuted    tally.Counter
    63  	replicateNotExecuted tally.Counter
    64  	replicateError       tally.Counter
    65  	replicateSuccess     tally.Counter
    66  }
    67  
    68  func newReplicatedSessionMetrics(scope tally.Scope) replicatedSessionMetrics {
    69  	return replicatedSessionMetrics{
    70  		replicateExecuted:    scope.Counter("replicate.executed"),
    71  		replicateNotExecuted: scope.Counter("replicate.not-executed"),
    72  		replicateError:       scope.Counter("replicate.error"),
    73  		replicateSuccess:     scope.Counter("replicate.success"),
    74  	}
    75  }
    76  
    77  // Ensure replicatedSession implements the clientSession interface.
    78  var _ clientSession = (*replicatedSession)(nil)
    79  
    80  type replicatedSessionOption func(*replicatedSession)
    81  
    82  func withNewSessionFn(fn newSessionFn) replicatedSessionOption {
    83  	return func(session *replicatedSession) {
    84  		session.newSessionFn = fn
    85  	}
    86  }
    87  
    88  func newReplicatedSession(
    89  	opts Options, asyncOpts []Options, options ...replicatedSessionOption,
    90  ) (clientSession, error) {
    91  	workerPool := opts.AsyncWriteWorkerPool()
    92  
    93  	scope := opts.InstrumentOptions().MetricsScope()
    94  
    95  	session := replicatedSession{
    96  		newSessionFn:         newSession,
    97  		identifierPool:       opts.IdentifierPool(),
    98  		workerPool:           workerPool,
    99  		replicationSemaphore: make(chan struct{}, opts.AsyncWriteMaxConcurrency()),
   100  		scope:                scope,
   101  		log:                  opts.InstrumentOptions().Logger(),
   102  		metrics:              newReplicatedSessionMetrics(scope),
   103  		writeTimestampOffset: opts.WriteTimestampOffset(),
   104  	}
   105  
   106  	// Apply options
   107  	for _, option := range options {
   108  		option(&session)
   109  	}
   110  
   111  	if err := session.setSession(opts); err != nil {
   112  		return nil, err
   113  	}
   114  	if err := session.setAsyncSessions(asyncOpts); err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	return &session, nil
   119  }
   120  
   121  func (s *replicatedSession) setSession(opts Options) error {
   122  	if opts.TopologyInitializer() == nil {
   123  		return nil
   124  	}
   125  
   126  	session, err := s.newSessionFn(opts)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	s.session = session
   131  	return nil
   132  }
   133  
   134  func (s *replicatedSession) setAsyncSessions(opts []Options) error {
   135  	sessions := make([]clientSession, 0, len(opts))
   136  	for i, oo := range opts {
   137  		subscope := oo.InstrumentOptions().MetricsScope().SubScope(fmt.Sprintf("async-%d", i))
   138  		oo = oo.SetInstrumentOptions(oo.InstrumentOptions().SetMetricsScope(subscope))
   139  
   140  		session, err := s.newSessionFn(oo)
   141  		if err != nil {
   142  			return err
   143  		}
   144  		sessions = append(sessions, session)
   145  	}
   146  	s.asyncSessions = sessions
   147  	return nil
   148  }
   149  
   150  type replicatedParams struct {
   151  	namespace  ident.ID
   152  	id         ident.ID
   153  	t          xtime.UnixNano
   154  	value      float64
   155  	unit       xtime.Unit
   156  	annotation []byte
   157  	tags       ident.TagIterator
   158  	useTags    bool
   159  }
   160  
   161  // NB(srobb): it would be a nicer to accept a lambda which is the fn to
   162  // be performed on all sessions, however this causes an extra allocation.
   163  func (s replicatedSession) replicate(params replicatedParams) error {
   164  	for _, asyncSession := range s.asyncSessions {
   165  		asyncSession := asyncSession // capture var
   166  
   167  		var (
   168  			clonedID   = s.identifierPool.Clone(params.id)
   169  			clonedNS   = s.identifierPool.Clone(params.namespace)
   170  			clonedTags ident.TagIterator
   171  		)
   172  		if params.useTags {
   173  			clonedTags = params.tags.Duplicate()
   174  		}
   175  
   176  		select {
   177  		case s.replicationSemaphore <- struct{}{}:
   178  			s.workerPool.Go(func() {
   179  				var err error
   180  				if params.useTags {
   181  					err = asyncSession.WriteTagged(
   182  						clonedNS, clonedID, clonedTags, params.t,
   183  						params.value, params.unit, params.annotation,
   184  					)
   185  				} else {
   186  					err = asyncSession.Write(
   187  						clonedNS, clonedID, params.t,
   188  						params.value, params.unit, params.annotation,
   189  					)
   190  				}
   191  				if err != nil {
   192  					s.metrics.replicateError.Inc(1)
   193  					s.log.Error("could not replicate write", zap.Error(err))
   194  				} else {
   195  					s.metrics.replicateSuccess.Inc(1)
   196  				}
   197  				if s.outCh != nil {
   198  					s.outCh <- err
   199  				}
   200  				<-s.replicationSemaphore
   201  			})
   202  			s.metrics.replicateExecuted.Inc(1)
   203  		default:
   204  			s.metrics.replicateNotExecuted.Inc(1)
   205  		}
   206  	}
   207  
   208  	if params.useTags {
   209  		return s.session.WriteTagged(
   210  			params.namespace, params.id, params.tags, params.t,
   211  			params.value, params.unit, params.annotation,
   212  		)
   213  	}
   214  
   215  	return s.session.Write(
   216  		params.namespace, params.id, params.t,
   217  		params.value, params.unit, params.annotation,
   218  	)
   219  }
   220  
   221  func (s *replicatedSession) ReadClusterAvailability() (bool, error) {
   222  	return s.session.ReadClusterAvailability()
   223  }
   224  
   225  func (s *replicatedSession) WriteClusterAvailability() (bool, error) {
   226  	return s.session.WriteClusterAvailability()
   227  }
   228  
   229  // Write value to the database for an ID.
   230  func (s replicatedSession) Write(
   231  	namespace, id ident.ID, t xtime.UnixNano, value float64,
   232  	unit xtime.Unit, annotation []byte,
   233  ) error {
   234  	return s.replicate(replicatedParams{
   235  		namespace:  namespace,
   236  		id:         id,
   237  		t:          t.Add(-s.writeTimestampOffset),
   238  		value:      value,
   239  		unit:       unit,
   240  		annotation: annotation,
   241  	})
   242  }
   243  
   244  // WriteTagged value to the database for an ID and given tags.
   245  func (s replicatedSession) WriteTagged(
   246  	namespace, id ident.ID, tags ident.TagIterator, t xtime.UnixNano,
   247  	value float64, unit xtime.Unit, annotation []byte,
   248  ) error {
   249  	return s.replicate(replicatedParams{
   250  		namespace:  namespace,
   251  		id:         id,
   252  		t:          t.Add(-s.writeTimestampOffset),
   253  		value:      value,
   254  		unit:       unit,
   255  		annotation: annotation,
   256  		tags:       tags,
   257  		useTags:    true,
   258  	})
   259  }
   260  
   261  // Fetch values from the database for an ID.
   262  func (s replicatedSession) Fetch(
   263  	namespace, id ident.ID, startInclusive, endExclusive xtime.UnixNano,
   264  ) (encoding.SeriesIterator, error) {
   265  	return s.session.Fetch(namespace, id, startInclusive, endExclusive)
   266  }
   267  
   268  // FetchIDs values from the database for a set of IDs.
   269  func (s replicatedSession) FetchIDs(
   270  	namespace ident.ID, ids ident.Iterator, startInclusive, endExclusive xtime.UnixNano,
   271  ) (encoding.SeriesIterators, error) {
   272  	return s.session.FetchIDs(namespace, ids, startInclusive, endExclusive)
   273  }
   274  
   275  // Aggregate aggregates values from the database for the given set of constraints.
   276  func (s replicatedSession) Aggregate(
   277  	ctx context.Context,
   278  	ns ident.ID,
   279  	q index.Query,
   280  	opts index.AggregationOptions,
   281  ) (AggregatedTagsIterator, FetchResponseMetadata, error) {
   282  	return s.session.Aggregate(ctx, ns, q, opts)
   283  }
   284  
   285  // FetchTagged resolves the provided query to known IDs, and fetches the data for them.
   286  func (s replicatedSession) FetchTagged(
   287  	ctx context.Context,
   288  	namespace ident.ID,
   289  	q index.Query,
   290  	opts index.QueryOptions,
   291  ) (encoding.SeriesIterators, FetchResponseMetadata, error) {
   292  	return s.session.FetchTagged(ctx, namespace, q, opts)
   293  }
   294  
   295  // FetchTaggedIDs resolves the provided query to known IDs.
   296  func (s replicatedSession) FetchTaggedIDs(
   297  	ctx context.Context,
   298  	namespace ident.ID,
   299  	q index.Query,
   300  	opts index.QueryOptions,
   301  ) (TaggedIDsIterator, FetchResponseMetadata, error) {
   302  	return s.session.FetchTaggedIDs(ctx, namespace, q, opts)
   303  }
   304  
   305  // ShardID returns the given shard for an ID for callers
   306  // to easily discern what shard is failing when operations
   307  // for given IDs begin failing.
   308  func (s replicatedSession) ShardID(id ident.ID) (uint32, error) {
   309  	return s.session.ShardID(id)
   310  }
   311  
   312  // IteratorPools exposes the internal iterator pools used by the session to clients.
   313  func (s replicatedSession) IteratorPools() (encoding.IteratorPools, error) {
   314  	return s.session.IteratorPools()
   315  }
   316  
   317  // Close the session.
   318  func (s replicatedSession) Close() error {
   319  	err := s.session.Close()
   320  	for _, as := range s.asyncSessions {
   321  		if err := as.Close(); err != nil {
   322  			s.log.Error("could not close async session: %v", zap.Error(err))
   323  		}
   324  	}
   325  	return err
   326  }
   327  
   328  // Origin returns the host that initiated the session.
   329  func (s replicatedSession) Origin() topology.Host {
   330  	return s.session.Origin()
   331  }
   332  
   333  // Replicas returns the replication factor.
   334  func (s replicatedSession) Replicas() int {
   335  	return s.session.Replicas()
   336  }
   337  
   338  // TopologyMap returns the current topology map. Note that the session
   339  // has a separate topology watch than the database itself, so the two
   340  // values can be out of sync and this method should not be relied upon
   341  // if the current view of the topology as seen by the database is required.
   342  func (s replicatedSession) TopologyMap() (topology.Map, error) {
   343  	return s.session.TopologyMap()
   344  }
   345  
   346  // Truncate will truncate the namespace for a given shard.
   347  func (s replicatedSession) Truncate(namespace ident.ID) (int64, error) {
   348  	return s.session.Truncate(namespace)
   349  }
   350  
   351  // FetchBootstrapBlocksFromPeers will fetch the most fulfilled block
   352  // for each series using the runtime configurable bootstrap level consistency.
   353  func (s replicatedSession) FetchBootstrapBlocksFromPeers(
   354  	namespace namespace.Metadata,
   355  	shard uint32,
   356  	start, end xtime.UnixNano,
   357  	opts result.Options,
   358  ) (result.ShardResult, error) {
   359  	return s.session.FetchBootstrapBlocksFromPeers(namespace, shard, start, end, opts)
   360  }
   361  
   362  // FetchBootstrapBlocksMetadataFromPeers will fetch the blocks metadata from
   363  // available peers using the runtime configurable bootstrap level consistency.
   364  func (s replicatedSession) FetchBootstrapBlocksMetadataFromPeers(
   365  	namespace ident.ID,
   366  	shard uint32,
   367  	start, end xtime.UnixNano,
   368  	result result.Options,
   369  ) (PeerBlockMetadataIter, error) {
   370  	return s.session.FetchBootstrapBlocksMetadataFromPeers(namespace, shard, start, end, result)
   371  }
   372  
   373  // FetchBlocksMetadataFromPeers will fetch the blocks metadata from
   374  // available peers.
   375  func (s replicatedSession) FetchBlocksMetadataFromPeers(
   376  	namespace ident.ID,
   377  	shard uint32,
   378  	start, end xtime.UnixNano,
   379  	consistencyLevel topology.ReadConsistencyLevel,
   380  	result result.Options,
   381  ) (PeerBlockMetadataIter, error) {
   382  	return s.session.FetchBlocksMetadataFromPeers(namespace, shard, start, end, consistencyLevel, result)
   383  }
   384  
   385  // FetchBlocksFromPeers will fetch the required blocks from the
   386  // peers specified.
   387  func (s replicatedSession) FetchBlocksFromPeers(
   388  	namespace namespace.Metadata,
   389  	shard uint32,
   390  	consistencyLevel topology.ReadConsistencyLevel,
   391  	metadatas []block.ReplicaMetadata,
   392  	opts result.Options,
   393  ) (PeerBlocksIter, error) {
   394  	return s.session.FetchBlocksFromPeers(namespace, shard, consistencyLevel, metadatas, opts)
   395  }
   396  
   397  func (s *replicatedSession) BorrowConnections(
   398  	shardID uint32,
   399  	fn WithBorrowConnectionFn,
   400  	opts BorrowConnectionOptions,
   401  ) (BorrowConnectionsResult, error) {
   402  	return s.session.BorrowConnections(shardID, fn, opts)
   403  }
   404  
   405  func (s *replicatedSession) DedicatedConnection(
   406  	shardID uint32,
   407  	opts DedicatedConnectionOptions,
   408  ) (rpc.TChanNode, Channel, error) {
   409  	return s.session.DedicatedConnection(shardID, opts)
   410  }
   411  
   412  // Open the client session.
   413  func (s replicatedSession) Open() error {
   414  	if err := s.session.Open(); err != nil {
   415  		return err
   416  	}
   417  	for _, asyncSession := range s.asyncSessions {
   418  		if err := asyncSession.Open(); err != nil {
   419  			s.log.Error("could not open session to async cluster: %v", zap.Error(err))
   420  		}
   421  	}
   422  	return nil
   423  }