github.com/m3db/m3@v1.5.0/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 }