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 )