github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/namespace/schema_registry.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 namespace
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  
    27  	"github.com/m3db/m3/src/x/ident"
    28  	xresource "github.com/m3db/m3/src/x/resource"
    29  	xwatch "github.com/m3db/m3/src/x/watch"
    30  
    31  	"go.uber.org/zap"
    32  )
    33  
    34  func newSchemaHistoryNotFoundError(nsIDStr string) error {
    35  	return &schemaHistoryNotFoundError{nsIDStr}
    36  }
    37  
    38  type schemaHistoryNotFoundError struct {
    39  	nsIDStr string
    40  }
    41  
    42  func (s *schemaHistoryNotFoundError) Error() string {
    43  	return fmt.Sprintf("schema history is not found for %s", s.nsIDStr)
    44  }
    45  
    46  type schemaRegistry struct {
    47  	sync.RWMutex
    48  
    49  	protoEnabled bool
    50  	logger       *zap.Logger
    51  	registry     map[string]xwatch.Watchable
    52  }
    53  
    54  func NewSchemaRegistry(protoEnabled bool, logger *zap.Logger) SchemaRegistry {
    55  	return newSchemaRegistry(protoEnabled, logger)
    56  }
    57  
    58  func newSchemaRegistry(protoEnabled bool, logger *zap.Logger) SchemaRegistry {
    59  	return &schemaRegistry{
    60  		protoEnabled: protoEnabled,
    61  		logger:       logger,
    62  		registry:     make(map[string]xwatch.Watchable),
    63  	}
    64  }
    65  
    66  func (sr *schemaRegistry) SetSchemaHistory(id ident.ID, history SchemaHistory) error {
    67  	if !sr.protoEnabled {
    68  		if sr.logger != nil {
    69  			sr.logger.Warn("proto is not enabled, can not update schema registry",
    70  				zap.Stringer("namespace", id))
    71  		}
    72  		return nil
    73  	}
    74  
    75  	if newSchema, ok := history.GetLatest(); !ok {
    76  		return fmt.Errorf("can not set empty schema history for %v", id.String())
    77  	} else if sr.logger != nil {
    78  		sr.logger.Info("proto is enabled, setting schema",
    79  			zap.Stringer("namespace", id),
    80  			zap.String("version", newSchema.DeployId()))
    81  	}
    82  
    83  	sr.Lock()
    84  	defer sr.Unlock()
    85  
    86  	// TODO [haijun] use generated map for optimized map lookup.
    87  	current, ok := sr.registry[id.String()]
    88  	if ok {
    89  		if !history.Extends(current.Get().(SchemaHistory)) {
    90  			return fmt.Errorf("can not update schema registry to one that does not extends the existing one")
    91  		}
    92  	} else {
    93  		sr.registry[id.String()] = xwatch.NewWatchable()
    94  	}
    95  
    96  	sr.registry[id.String()].Update(history)
    97  	return nil
    98  }
    99  
   100  func (sr *schemaRegistry) GetLatestSchema(id ident.ID) (SchemaDescr, error) {
   101  	if !sr.protoEnabled {
   102  		return nil, nil
   103  	}
   104  
   105  	nsIDStr := id.String()
   106  	history, err := sr.getSchemaHistory(nsIDStr)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	schema, ok := history.GetLatest()
   111  	if !ok {
   112  		return nil, fmt.Errorf("schema history is empty for namespace %v", nsIDStr)
   113  	}
   114  	return schema, nil
   115  }
   116  
   117  func (sr *schemaRegistry) GetSchema(id ident.ID, schemaId string) (SchemaDescr, error) {
   118  	if !sr.protoEnabled {
   119  		return nil, nil
   120  	}
   121  
   122  	nsIDStr := id.String()
   123  	history, err := sr.getSchemaHistory(nsIDStr)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	schema, ok := history.Get(schemaId)
   128  	if !ok {
   129  		return nil, fmt.Errorf("schema of version %v is not found for namespace %v", schemaId, nsIDStr)
   130  	}
   131  	return schema, nil
   132  }
   133  
   134  func (sr *schemaRegistry) getSchemaHistory(nsIDStr string) (SchemaHistory, error) {
   135  	sr.RLock()
   136  	defer sr.RUnlock()
   137  
   138  	history, ok := sr.registry[nsIDStr]
   139  	if !ok {
   140  		return nil, newSchemaHistoryNotFoundError(nsIDStr)
   141  	}
   142  	return history.Get().(SchemaHistory), nil
   143  }
   144  
   145  func (sr *schemaRegistry) RegisterListener(
   146  	nsID ident.ID,
   147  	listener SchemaListener,
   148  ) (xresource.SimpleCloser, error) {
   149  	if !sr.protoEnabled {
   150  		return nil, nil
   151  	}
   152  
   153  	nsIDStr := nsID.String()
   154  	sr.RLock()
   155  	defer sr.RUnlock()
   156  
   157  	watchable, ok := sr.registry[nsIDStr]
   158  	if !ok {
   159  		return nil, fmt.Errorf("schema not found for namespace: %v", nsIDStr)
   160  	}
   161  
   162  	_, watch, _ := watchable.Watch()
   163  
   164  	// We always initialize the watchable so always read
   165  	// the first notification value
   166  	<-watch.C()
   167  
   168  	// Deliver the current schema
   169  	listener.SetSchemaHistory(watchable.Get().(SchemaHistory))
   170  
   171  	// Spawn a new goroutine that will terminate when the
   172  	// watchable terminates on the close of the runtime options manager
   173  	go func() {
   174  		for range watch.C() {
   175  			listener.SetSchemaHistory(watchable.Get().(SchemaHistory))
   176  		}
   177  	}()
   178  
   179  	return watch, nil
   180  }
   181  
   182  func (sr *schemaRegistry) Close() {
   183  	sr.Lock()
   184  	defer sr.Unlock()
   185  	for _, w := range sr.registry {
   186  		w.Close()
   187  	}
   188  }