github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/proxy/observable.go (about)

     1  package proxy
     2  
     3  import (
     4  	"context"
     5  
     6  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
     7  	"github.com/prometheus/client_golang/prometheus"
     8  	"github.com/prometheus/client_golang/prometheus/promauto"
     9  	"go.opentelemetry.io/otel"
    10  	"go.opentelemetry.io/otel/attribute"
    11  	"go.opentelemetry.io/otel/trace"
    12  
    13  	"github.com/authzed/spicedb/internal/datastore/common"
    14  	"github.com/authzed/spicedb/pkg/datastore"
    15  	"github.com/authzed/spicedb/pkg/datastore/options"
    16  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    17  )
    18  
    19  var (
    20  	tracer = otel.Tracer("spicedb/datastore/proxy/observable")
    21  
    22  	loadedRelationshipCount = promauto.NewHistogram(prometheus.HistogramOpts{
    23  		Namespace: "spicedb",
    24  		Subsystem: "datastore",
    25  		Name:      "loaded_relationships_count",
    26  		Buckets:   []float64{0, 1, 3, 10, 32, 100, 316, 1000, 3162, 10000},
    27  		Help:      "total number of relationships loaded for a query",
    28  	})
    29  
    30  	queryLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
    31  		Namespace: "spicedb",
    32  		Subsystem: "datastore",
    33  		Name:      "query_latency",
    34  		Buckets:   []float64{.0005, .001, .002, .005, .01, .02, .05, .1, .2, .5},
    35  		Help:      "response latency for a database query",
    36  	}, []string{
    37  		"operation",
    38  	})
    39  )
    40  
    41  func filterToAttributes(filter *v1.RelationshipFilter) []attribute.KeyValue {
    42  	attrs := []attribute.KeyValue{common.ObjNamespaceNameKey.String(filter.ResourceType)}
    43  	if filter.OptionalResourceId != "" {
    44  		attrs = append(attrs, common.ObjIDKey.String(filter.OptionalResourceId))
    45  	}
    46  	if filter.OptionalRelation != "" {
    47  		attrs = append(attrs, common.ObjRelationNameKey.String(filter.OptionalRelation))
    48  	}
    49  
    50  	if subjectFilter := filter.OptionalSubjectFilter; subjectFilter != nil {
    51  		attrs = append(attrs, common.SubNamespaceNameKey.String(subjectFilter.SubjectType))
    52  		if subjectFilter.OptionalSubjectId != "" {
    53  			attrs = append(attrs, common.SubObjectIDKey.String(subjectFilter.OptionalSubjectId))
    54  		}
    55  		if relationFilter := subjectFilter.OptionalRelation; relationFilter != nil {
    56  			attrs = append(attrs, common.SubRelationNameKey.String(relationFilter.Relation))
    57  		}
    58  	}
    59  	return attrs
    60  }
    61  
    62  // NewObservableDatastoreProxy creates a new datastore proxy which adds tracing
    63  // and metrics to the datastore.
    64  func NewObservableDatastoreProxy(d datastore.Datastore) datastore.Datastore {
    65  	return &observableProxy{delegate: d}
    66  }
    67  
    68  type observableProxy struct{ delegate datastore.Datastore }
    69  
    70  func (p *observableProxy) SnapshotReader(rev datastore.Revision) datastore.Reader {
    71  	delegateReader := p.delegate.SnapshotReader(rev)
    72  	return &observableReader{delegateReader}
    73  }
    74  
    75  func (p *observableProxy) ReadWriteTx(
    76  	ctx context.Context,
    77  	f datastore.TxUserFunc,
    78  	opts ...options.RWTOptionsOption,
    79  ) (datastore.Revision, error) {
    80  	return p.delegate.ReadWriteTx(ctx, func(ctx context.Context, delegateRWT datastore.ReadWriteTransaction) error {
    81  		return f(ctx, &observableRWT{&observableReader{delegateRWT}, delegateRWT})
    82  	}, opts...)
    83  }
    84  
    85  func (p *observableProxy) OptimizedRevision(ctx context.Context) (datastore.Revision, error) {
    86  	ctx, closer := observe(ctx, "OptimizedRevision")
    87  	defer closer()
    88  
    89  	return p.delegate.OptimizedRevision(ctx)
    90  }
    91  
    92  func (p *observableProxy) CheckRevision(ctx context.Context, revision datastore.Revision) error {
    93  	ctx, closer := observe(ctx, "CheckRevision", trace.WithAttributes(
    94  		attribute.String("revision", revision.String()),
    95  	))
    96  	defer closer()
    97  
    98  	return p.delegate.CheckRevision(ctx, revision)
    99  }
   100  
   101  func (p *observableProxy) HeadRevision(ctx context.Context) (datastore.Revision, error) {
   102  	ctx, closer := observe(ctx, "HeadRevision")
   103  	defer closer()
   104  
   105  	return p.delegate.HeadRevision(ctx)
   106  }
   107  
   108  func (p *observableProxy) RevisionFromString(serialized string) (datastore.Revision, error) {
   109  	return p.delegate.RevisionFromString(serialized)
   110  }
   111  
   112  func (p *observableProxy) Watch(ctx context.Context, afterRevision datastore.Revision, options datastore.WatchOptions) (<-chan *datastore.RevisionChanges, <-chan error) {
   113  	return p.delegate.Watch(ctx, afterRevision, options)
   114  }
   115  
   116  func (p *observableProxy) Features(ctx context.Context) (*datastore.Features, error) {
   117  	ctx, closer := observe(ctx, "Features")
   118  	defer closer()
   119  
   120  	return p.delegate.Features(ctx)
   121  }
   122  
   123  func (p *observableProxy) Statistics(ctx context.Context) (datastore.Stats, error) {
   124  	ctx, closer := observe(ctx, "Statistics")
   125  	defer closer()
   126  
   127  	return p.delegate.Statistics(ctx)
   128  }
   129  
   130  func (p *observableProxy) Unwrap() datastore.Datastore {
   131  	return p.delegate
   132  }
   133  
   134  func (p *observableProxy) ReadyState(ctx context.Context) (datastore.ReadyState, error) {
   135  	ctx, closer := observe(ctx, "ReadyState")
   136  	defer closer()
   137  
   138  	return p.delegate.ReadyState(ctx)
   139  }
   140  
   141  func (p *observableProxy) Close() error { return p.delegate.Close() }
   142  
   143  type observableReader struct{ delegate datastore.Reader }
   144  
   145  func (r *observableReader) ReadCaveatByName(ctx context.Context, name string) (*core.CaveatDefinition, datastore.Revision, error) {
   146  	ctx, closer := observe(ctx, "ReadCaveatByName", trace.WithAttributes(
   147  		attribute.String("name", name),
   148  	))
   149  	defer closer()
   150  
   151  	return r.delegate.ReadCaveatByName(ctx, name)
   152  }
   153  
   154  func (r *observableReader) LookupCaveatsWithNames(ctx context.Context, caveatNames []string) ([]datastore.RevisionedCaveat, error) {
   155  	ctx, closer := observe(ctx, "LookupCaveatsWithNames", trace.WithAttributes(
   156  		attribute.StringSlice("names", caveatNames),
   157  	))
   158  	defer closer()
   159  
   160  	return r.delegate.LookupCaveatsWithNames(ctx, caveatNames)
   161  }
   162  
   163  func (r *observableReader) ListAllCaveats(ctx context.Context) ([]datastore.RevisionedCaveat, error) {
   164  	ctx, closer := observe(ctx, "ListAllCaveats")
   165  	defer closer()
   166  
   167  	return r.delegate.ListAllCaveats(ctx)
   168  }
   169  
   170  func (r *observableReader) ListAllNamespaces(ctx context.Context) ([]datastore.RevisionedNamespace, error) {
   171  	ctx, closer := observe(ctx, "ListAllNamespaces")
   172  	defer closer()
   173  
   174  	return r.delegate.ListAllNamespaces(ctx)
   175  }
   176  
   177  func (r *observableReader) LookupNamespacesWithNames(ctx context.Context, nsNames []string) ([]datastore.RevisionedNamespace, error) {
   178  	ctx, closer := observe(ctx, "LookupNamespacesWithNames", trace.WithAttributes(
   179  		attribute.StringSlice("names", nsNames),
   180  	))
   181  	defer closer()
   182  
   183  	return r.delegate.LookupNamespacesWithNames(ctx, nsNames)
   184  }
   185  
   186  func (r *observableReader) ReadNamespaceByName(ctx context.Context, nsName string) (*core.NamespaceDefinition, datastore.Revision, error) {
   187  	ctx, closer := observe(ctx, "ReadNamespaceByName", trace.WithAttributes(
   188  		attribute.String("name", nsName),
   189  	))
   190  	defer closer()
   191  
   192  	return r.delegate.ReadNamespaceByName(ctx, nsName)
   193  }
   194  
   195  func (r *observableReader) QueryRelationships(ctx context.Context, filter datastore.RelationshipsFilter, options ...options.QueryOptionsOption) (datastore.RelationshipIterator, error) {
   196  	ctx, closer := observe(ctx, "QueryRelationships", trace.WithAttributes(
   197  		attribute.String("resourceType", filter.OptionalResourceType),
   198  		attribute.String("resourceRelation", filter.OptionalResourceRelation),
   199  		attribute.String("caveatName", filter.OptionalCaveatName),
   200  	))
   201  
   202  	iterator, err := r.delegate.QueryRelationships(ctx, filter, options...)
   203  	if err != nil {
   204  		return iterator, err
   205  	}
   206  	return &observableRelationshipIterator{closer, iterator, 0}, nil
   207  }
   208  
   209  type observableRelationshipIterator struct {
   210  	closer   func()
   211  	delegate datastore.RelationshipIterator
   212  	count    uint32
   213  }
   214  
   215  func (i *observableRelationshipIterator) Next() *core.RelationTuple {
   216  	if next := i.delegate.Next(); next != nil {
   217  		i.count++
   218  		return next
   219  	}
   220  	return nil
   221  }
   222  
   223  func (i *observableRelationshipIterator) Err() error { return i.delegate.Err() }
   224  
   225  func (i *observableRelationshipIterator) Cursor() (options.Cursor, error) { return i.delegate.Cursor() }
   226  
   227  func (i *observableRelationshipIterator) Close() {
   228  	loadedRelationshipCount.Observe(float64(i.count))
   229  	i.closer()
   230  	i.delegate.Close()
   231  }
   232  
   233  func (r *observableReader) ReverseQueryRelationships(ctx context.Context, subjectFilter datastore.SubjectsFilter, options ...options.ReverseQueryOptionsOption) (datastore.RelationshipIterator, error) {
   234  	ctx, closer := observe(ctx, "ReverseQueryRelationships")
   235  	iterator, err := r.delegate.ReverseQueryRelationships(ctx, subjectFilter, options...)
   236  	if err != nil {
   237  		return iterator, err
   238  	}
   239  	return &observableRelationshipIterator{closer, iterator, 0}, nil
   240  }
   241  
   242  type observableRWT struct {
   243  	*observableReader
   244  	delegate datastore.ReadWriteTransaction
   245  }
   246  
   247  func (rwt *observableRWT) WriteCaveats(ctx context.Context, caveats []*core.CaveatDefinition) error {
   248  	caveatNames := make([]string, 0, len(caveats))
   249  	for _, caveat := range caveats {
   250  		caveatNames = append(caveatNames, caveat.Name)
   251  	}
   252  
   253  	ctx, closer := observe(ctx, "WriteCaveats", trace.WithAttributes(
   254  		attribute.StringSlice("names", caveatNames),
   255  	))
   256  	defer closer()
   257  
   258  	return rwt.delegate.WriteCaveats(ctx, caveats)
   259  }
   260  
   261  func (rwt *observableRWT) DeleteCaveats(ctx context.Context, names []string) error {
   262  	ctx, closer := observe(ctx, "DeleteCaveats", trace.WithAttributes(
   263  		attribute.StringSlice("names", names),
   264  	))
   265  	defer closer()
   266  
   267  	return rwt.delegate.DeleteCaveats(ctx, names)
   268  }
   269  
   270  func (rwt *observableRWT) WriteRelationships(ctx context.Context, mutations []*core.RelationTupleUpdate) error {
   271  	ctx, closer := observe(ctx, "WriteRelationships", trace.WithAttributes(
   272  		attribute.Int("mutations", len(mutations)),
   273  	))
   274  	defer closer()
   275  
   276  	return rwt.delegate.WriteRelationships(ctx, mutations)
   277  }
   278  
   279  func (rwt *observableRWT) WriteNamespaces(ctx context.Context, newConfigs ...*core.NamespaceDefinition) error {
   280  	nsNames := make([]string, 0, len(newConfigs))
   281  	for _, ns := range newConfigs {
   282  		nsNames = append(nsNames, ns.Name)
   283  	}
   284  
   285  	ctx, closer := observe(ctx, "WriteNamespaces", trace.WithAttributes(
   286  		attribute.StringSlice("names", nsNames),
   287  	))
   288  	defer closer()
   289  
   290  	return rwt.delegate.WriteNamespaces(ctx, newConfigs...)
   291  }
   292  
   293  func (rwt *observableRWT) DeleteNamespaces(ctx context.Context, nsNames ...string) error {
   294  	ctx, closer := observe(ctx, "DeleteNamespaces", trace.WithAttributes(
   295  		attribute.StringSlice("names", nsNames),
   296  	))
   297  	defer closer()
   298  
   299  	return rwt.delegate.DeleteNamespaces(ctx, nsNames...)
   300  }
   301  
   302  func (rwt *observableRWT) DeleteRelationships(ctx context.Context, filter *v1.RelationshipFilter, options ...options.DeleteOptionsOption) (bool, error) {
   303  	ctx, closer := observe(ctx, "DeleteRelationships", trace.WithAttributes(
   304  		filterToAttributes(filter)...,
   305  	))
   306  	defer closer()
   307  
   308  	return rwt.delegate.DeleteRelationships(ctx, filter, options...)
   309  }
   310  
   311  func (rwt *observableRWT) BulkLoad(ctx context.Context, iter datastore.BulkWriteRelationshipSource) (uint64, error) {
   312  	ctx, closer := observe(ctx, "BulkLoad")
   313  	defer closer()
   314  
   315  	return rwt.delegate.BulkLoad(ctx, iter)
   316  }
   317  
   318  func observe(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, func()) {
   319  	ctx, span := tracer.Start(ctx, name, opts...)
   320  	timer := prometheus.NewTimer(queryLatency.WithLabelValues(name))
   321  	closed := false
   322  
   323  	return ctx, func() {
   324  		if closed {
   325  			return
   326  		}
   327  
   328  		closed = true
   329  		timer.ObserveDuration()
   330  		span.End()
   331  	}
   332  }
   333  
   334  var (
   335  	_ datastore.Datastore            = (*observableProxy)(nil)
   336  	_ datastore.Reader               = (*observableReader)(nil)
   337  	_ datastore.ReadWriteTransaction = (*observableRWT)(nil)
   338  	_ datastore.RelationshipIterator = (*observableRelationshipIterator)(nil)
   339  )