github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/benchmark/driver_bench_test.go (about) 1 //go:build ci && docker 2 // +build ci,docker 3 4 package benchmark 5 6 import ( 7 "context" 8 "fmt" 9 "math" 10 "math/rand" 11 "strconv" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/require" 16 17 "github.com/authzed/spicedb/internal/datastore/crdb" 18 "github.com/authzed/spicedb/internal/datastore/mysql" 19 "github.com/authzed/spicedb/internal/datastore/postgres" 20 "github.com/authzed/spicedb/internal/datastore/spanner" 21 "github.com/authzed/spicedb/internal/testfixtures" 22 testdatastore "github.com/authzed/spicedb/internal/testserver/datastore" 23 "github.com/authzed/spicedb/internal/testserver/datastore/config" 24 dsconfig "github.com/authzed/spicedb/pkg/cmd/datastore" 25 "github.com/authzed/spicedb/pkg/datastore" 26 "github.com/authzed/spicedb/pkg/datastore/options" 27 core "github.com/authzed/spicedb/pkg/proto/core/v1" 28 "github.com/authzed/spicedb/pkg/tuple" 29 ) 30 31 const ( 32 numDocuments = 1000 33 usersPerDoc = 5 34 35 revisionQuantization = 5 * time.Second 36 gcWindow = 2 * time.Hour 37 gcInterval = 1 * time.Hour 38 watchBufferLength = 1000 39 ) 40 41 var drivers = []struct { 42 name string 43 suffix string 44 extraConfig []dsconfig.ConfigOption 45 }{ 46 {"memory", "", nil}, 47 {postgres.Engine, "", nil}, 48 {crdb.Engine, "-overlap-static", []dsconfig.ConfigOption{dsconfig.WithOverlapStrategy("static")}}, 49 {crdb.Engine, "-overlap-insecure", []dsconfig.ConfigOption{dsconfig.WithOverlapStrategy("insecure")}}, 50 {mysql.Engine, "", nil}, 51 } 52 53 var skipped = []string{ 54 spanner.Engine, // Not useful to benchmark a simulator 55 } 56 57 var sortOrders = map[string]options.SortOrder{ 58 "ByResource": options.ByResource, 59 "BySubject": options.BySubject, 60 } 61 62 func BenchmarkDatastoreDriver(b *testing.B) { 63 for _, driver := range drivers { 64 b.Run(driver.name+driver.suffix, func(b *testing.B) { 65 engine := testdatastore.RunDatastoreEngine(b, driver.name) 66 ds := engine.NewDatastore(b, config.DatastoreConfigInitFunc( 67 b, 68 append(driver.extraConfig, 69 dsconfig.WithRevisionQuantization(revisionQuantization), 70 dsconfig.WithGCWindow(gcWindow), 71 dsconfig.WithGCInterval(gcInterval), 72 dsconfig.WithWatchBufferLength(watchBufferLength))..., 73 )) 74 75 ctx := context.Background() 76 77 // Write the standard schema 78 ds, _ = testfixtures.StandardDatastoreWithSchema(ds, require.New(b)) 79 80 // Write a fair amount of data, much more than a functional test 81 for docNum := 0; docNum < numDocuments; docNum++ { 82 _, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 83 var updates []*core.RelationTupleUpdate 84 for userNum := 0; userNum < usersPerDoc; userNum++ { 85 updates = append(updates, &core.RelationTupleUpdate{ 86 Operation: core.RelationTupleUpdate_CREATE, 87 Tuple: docViewer(strconv.Itoa(docNum), strconv.Itoa(userNum)), 88 }) 89 } 90 91 return rwt.WriteRelationships(ctx, updates) 92 }) 93 require.NoError(b, err) 94 } 95 96 // Sleep to give the datastore time to stabilize after all the writes 97 time.Sleep(1 * time.Second) 98 99 headRev, err := ds.HeadRevision(ctx) 100 require.NoError(b, err) 101 102 b.Run("TestTuple", func(b *testing.B) { 103 b.Run("SnapshotRead", func(b *testing.B) { 104 for n := 0; n < b.N; n++ { 105 randDocNum := rand.Intn(numDocuments) 106 iter, err := ds.SnapshotReader(headRev).QueryRelationships(ctx, datastore.RelationshipsFilter{ 107 OptionalResourceType: testfixtures.DocumentNS.Name, 108 OptionalResourceIds: []string{strconv.Itoa(randDocNum)}, 109 OptionalResourceRelation: "viewer", 110 }) 111 require.NoError(b, err) 112 var count int 113 for rel := iter.Next(); rel != nil; rel = iter.Next() { 114 count++ 115 } 116 require.NoError(b, iter.Err()) 117 iter.Close() 118 require.Equal(b, usersPerDoc, count) 119 } 120 }) 121 b.Run("SortedSnapshotReadOnlyNamespace", func(b *testing.B) { 122 for orderName, order := range sortOrders { 123 order := order 124 b.Run(orderName, func(b *testing.B) { 125 for n := 0; n < b.N; n++ { 126 iter, err := ds.SnapshotReader(headRev).QueryRelationships(ctx, datastore.RelationshipsFilter{ 127 OptionalResourceType: testfixtures.DocumentNS.Name, 128 }, options.WithSort(order)) 129 require.NoError(b, err) 130 var count int 131 for rel := iter.Next(); rel != nil; rel = iter.Next() { 132 count++ 133 } 134 require.NoError(b, iter.Err()) 135 iter.Close() 136 } 137 }) 138 } 139 }) 140 b.Run("SortedSnapshotReadWithRelation", func(b *testing.B) { 141 for orderName, order := range sortOrders { 142 order := order 143 b.Run(orderName, func(b *testing.B) { 144 for n := 0; n < b.N; n++ { 145 iter, err := ds.SnapshotReader(headRev).QueryRelationships(ctx, datastore.RelationshipsFilter{ 146 OptionalResourceType: testfixtures.DocumentNS.Name, 147 OptionalResourceRelation: "viewer", 148 }, options.WithSort(order)) 149 require.NoError(b, err) 150 var count int 151 for rel := iter.Next(); rel != nil; rel = iter.Next() { 152 count++ 153 } 154 require.NoError(b, iter.Err()) 155 iter.Close() 156 } 157 }) 158 } 159 }) 160 b.Run("SortedSnapshotReadAllResourceFields", func(b *testing.B) { 161 for orderName, order := range sortOrders { 162 order := order 163 b.Run(orderName, func(b *testing.B) { 164 for n := 0; n < b.N; n++ { 165 randDocNum := rand.Intn(numDocuments) 166 iter, err := ds.SnapshotReader(headRev).QueryRelationships(ctx, datastore.RelationshipsFilter{ 167 OptionalResourceType: testfixtures.DocumentNS.Name, 168 OptionalResourceIds: []string{strconv.Itoa(randDocNum)}, 169 OptionalResourceRelation: "viewer", 170 }, options.WithSort(order)) 171 require.NoError(b, err) 172 var count int 173 for rel := iter.Next(); rel != nil; rel = iter.Next() { 174 count++ 175 } 176 require.NoError(b, iter.Err()) 177 iter.Close() 178 } 179 }) 180 } 181 }) 182 b.Run("SnapshotReverseRead", func(b *testing.B) { 183 for n := 0; n < b.N; n++ { 184 iter, err := ds.SnapshotReader(headRev).ReverseQueryRelationships(ctx, datastore.SubjectsFilter{ 185 SubjectType: testfixtures.UserNS.Name, 186 }, options.WithSortForReverse(options.ByResource)) 187 require.NoError(b, err) 188 var count int 189 for rel := iter.Next(); rel != nil; rel = iter.Next() { 190 count++ 191 } 192 require.NoError(b, iter.Err()) 193 iter.Close() 194 } 195 }) 196 b.Run("Touch", buildTupleTest(ctx, ds, core.RelationTupleUpdate_TOUCH)) 197 b.Run("Create", buildTupleTest(ctx, ds, core.RelationTupleUpdate_CREATE)) 198 b.Run("CreateAndTouch", func(b *testing.B) { 199 const totalRelationships = 1000 200 for _, portionCreate := range []float64{0, 0.10, 0.25, 0.50, 1} { 201 portionCreate := portionCreate 202 b.Run(fmt.Sprintf("%v_", portionCreate), func(b *testing.B) { 203 for n := 0; n < b.N; n++ { 204 portionCreateIndex := int(math.Floor(portionCreate * totalRelationships)) 205 mutations := make([]*core.RelationTupleUpdate, 0, totalRelationships) 206 for index := 0; index < totalRelationships; index++ { 207 if index >= portionCreateIndex { 208 stableID := fmt.Sprintf("id-%d", index) 209 tpl := docViewer(stableID, stableID) 210 mutations = append(mutations, tuple.Touch(tpl)) 211 } else { 212 randomID := testfixtures.RandomObjectID(32) 213 tpl := docViewer(randomID, randomID) 214 mutations = append(mutations, tuple.Create(tpl)) 215 } 216 } 217 218 _, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 219 return rwt.WriteRelationships(ctx, mutations) 220 }) 221 require.NoError(b, err) 222 } 223 }) 224 } 225 }) 226 }) 227 }) 228 } 229 } 230 231 func TestAllDriversBenchmarkedOrSkipped(t *testing.T) { 232 notBenchmarked := make(map[string]struct{}, len(datastore.Engines)) 233 for _, name := range datastore.Engines { 234 notBenchmarked[name] = struct{}{} 235 } 236 237 for _, driver := range drivers { 238 delete(notBenchmarked, driver.name) 239 } 240 for _, skippedEngine := range skipped { 241 delete(notBenchmarked, skippedEngine) 242 } 243 244 require.Empty(t, notBenchmarked) 245 } 246 247 func buildTupleTest(ctx context.Context, ds datastore.Datastore, op core.RelationTupleUpdate_Operation) func(b *testing.B) { 248 return func(b *testing.B) { 249 for n := 0; n < b.N; n++ { 250 _, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 251 randomID := testfixtures.RandomObjectID(32) 252 return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{ 253 { 254 Operation: op, 255 Tuple: docViewer(randomID, randomID), 256 }, 257 }) 258 }) 259 require.NoError(b, err) 260 } 261 } 262 } 263 264 func docViewer(documentID, userID string) *core.RelationTuple { 265 return &core.RelationTuple{ 266 ResourceAndRelation: &core.ObjectAndRelation{ 267 Namespace: testfixtures.DocumentNS.Name, 268 ObjectId: documentID, 269 Relation: "viewer", 270 }, 271 Subject: &core.ObjectAndRelation{ 272 Namespace: testfixtures.UserNS.Name, 273 ObjectId: userID, 274 Relation: datastore.Ellipsis, 275 }, 276 } 277 }