| /* | |
| Copyright 2020 Google LLC | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| package spanner | |
| import ( | |
| "context" | |
| "fmt" | |
| "math/rand" | |
| "reflect" | |
| "sync" | |
| "testing" | |
| "time" | |
| sppb "cloud.google.com/go/spanner/apiv1/spannerpb" | |
| . "cloud.google.com/go/spanner/internal/testutil" | |
| "google.golang.org/api/iterator" | |
| ) | |
| const networkLatencyTime = 10 * time.Millisecond | |
| const batchCreateSessionsMinTime = 10 * time.Millisecond | |
| const batchCreateSessionsRndTime = 10 * time.Millisecond | |
| const beginTransactionMinTime = 1 * time.Millisecond | |
| const beginTransactionRndTime = 1 * time.Millisecond | |
| const commitTransactionMinTime = 5 * time.Millisecond | |
| const commitTransactionRndTime = 5 * time.Millisecond | |
| const executeStreamingSqlMinTime = 10 * time.Millisecond | |
| const executeStreamingSqlRndTime = 10 * time.Millisecond | |
| const executeSqlMinTime = 10 * time.Millisecond | |
| const executeSqlRndTime = 10 * time.Millisecond | |
| const holdSessionTime = 100 | |
| const rndWaitTimeBetweenRequests = 10 | |
| var mu sync.Mutex | |
| var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) | |
| func createBenchmarkServer(incStep uint64) (server *MockedSpannerInMemTestServer, client *Client, teardown func()) { | |
| t := &testing.T{} | |
| server, client, teardown = setupMockedTestServerWithConfig(t, ClientConfig{ | |
| SessionPoolConfig: SessionPoolConfig{ | |
| MinOpened: 100, | |
| MaxOpened: 400, | |
| WriteSessions: 0.2, | |
| incStep: incStep, | |
| }, | |
| }) | |
| server.TestSpanner.PutExecutionTime(MethodBatchCreateSession, SimulatedExecutionTime{ | |
| MinimumExecutionTime: networkLatencyTime + batchCreateSessionsMinTime, | |
| RandomExecutionTime: batchCreateSessionsRndTime, | |
| }) | |
| server.TestSpanner.PutExecutionTime(MethodCreateSession, SimulatedExecutionTime{ | |
| MinimumExecutionTime: networkLatencyTime + batchCreateSessionsMinTime, | |
| RandomExecutionTime: batchCreateSessionsRndTime, | |
| }) | |
| server.TestSpanner.PutExecutionTime(MethodExecuteStreamingSql, SimulatedExecutionTime{ | |
| MinimumExecutionTime: networkLatencyTime + executeStreamingSqlMinTime, | |
| RandomExecutionTime: executeStreamingSqlRndTime, | |
| }) | |
| server.TestSpanner.PutExecutionTime(MethodBeginTransaction, SimulatedExecutionTime{ | |
| MinimumExecutionTime: networkLatencyTime + beginTransactionMinTime, | |
| RandomExecutionTime: beginTransactionRndTime, | |
| }) | |
| server.TestSpanner.PutExecutionTime(MethodCommitTransaction, SimulatedExecutionTime{ | |
| MinimumExecutionTime: networkLatencyTime + commitTransactionMinTime, | |
| RandomExecutionTime: commitTransactionRndTime, | |
| }) | |
| server.TestSpanner.PutExecutionTime(MethodExecuteSql, SimulatedExecutionTime{ | |
| MinimumExecutionTime: networkLatencyTime + executeSqlMinTime, | |
| RandomExecutionTime: executeSqlRndTime, | |
| }) | |
| // Wait until the session pool has been initialized. | |
| waitFor(t, func() error { | |
| if uint64(client.idleSessions.idleList.Len()) == client.idleSessions.MinOpened { | |
| return nil | |
| } | |
| return fmt.Errorf("not yet initialized") | |
| }) | |
| return | |
| } | |
| func readWorker(client *Client, b *testing.B, jobs <-chan int, results chan<- int) { | |
| for range jobs { | |
| mu.Lock() | |
| d := time.Millisecond * time.Duration(rnd.Int63n(rndWaitTimeBetweenRequests)) | |
| mu.Unlock() | |
| time.Sleep(d) | |
| iter := client.Single().Query(context.Background(), NewStatement(SelectSingerIDAlbumIDAlbumTitleFromAlbums)) | |
| row := 0 | |
| for { | |
| _, err := iter.Next() | |
| if err == iterator.Done { | |
| break | |
| } | |
| if err != nil { | |
| b.Fatal(err) | |
| } | |
| row++ | |
| if row == 1 { | |
| mu.Lock() | |
| d := time.Millisecond * time.Duration(rnd.Int63n(holdSessionTime)) | |
| mu.Unlock() | |
| time.Sleep(d) | |
| } | |
| } | |
| iter.Stop() | |
| results <- row | |
| } | |
| } | |
| func writeWorker(client *Client, b *testing.B, jobs <-chan int, results chan<- int64) { | |
| for range jobs { | |
| mu.Lock() | |
| d := time.Millisecond * time.Duration(rnd.Int63n(rndWaitTimeBetweenRequests)) | |
| mu.Unlock() | |
| time.Sleep(d) | |
| var updateCount int64 | |
| var err error | |
| if _, err = client.ReadWriteTransaction(context.Background(), func(ctx context.Context, transaction *ReadWriteTransaction) error { | |
| if updateCount, err = transaction.Update(ctx, NewStatement(UpdateBarSetFoo)); err != nil { | |
| return err | |
| } | |
| return nil | |
| }); err != nil { | |
| b.Fatal(err) | |
| } | |
| results <- updateCount | |
| } | |
| } | |
| func Benchmark_Client_BurstRead_IncStep01(b *testing.B) { | |
| benchmarkClientBurstRead(b, 1) | |
| } | |
| func Benchmark_Client_BurstRead_IncStep10(b *testing.B) { | |
| benchmarkClientBurstRead(b, 10) | |
| } | |
| func Benchmark_Client_BurstRead_IncStep20(b *testing.B) { | |
| benchmarkClientBurstRead(b, 20) | |
| } | |
| func Benchmark_Client_BurstRead_IncStep25(b *testing.B) { | |
| benchmarkClientBurstRead(b, 25) | |
| } | |
| func Benchmark_Client_BurstRead_IncStep30(b *testing.B) { | |
| benchmarkClientBurstRead(b, 30) | |
| } | |
| func Benchmark_Client_BurstRead_IncStep40(b *testing.B) { | |
| benchmarkClientBurstRead(b, 40) | |
| } | |
| func Benchmark_Client_BurstRead_IncStep50(b *testing.B) { | |
| benchmarkClientBurstRead(b, 50) | |
| } | |
| func Benchmark_Client_BurstRead_IncStep100(b *testing.B) { | |
| benchmarkClientBurstRead(b, 100) | |
| } | |
| func benchmarkClientBurstRead(b *testing.B, incStep uint64) { | |
| for n := 0; n < b.N; n++ { | |
| server, client, teardown := createBenchmarkServer(incStep) | |
| sp := client.idleSessions | |
| if uint64(sp.idleList.Len()) != sp.MinOpened { | |
| b.Fatalf("session count mismatch\nGot: %d\nWant: %d", sp.idleList.Len(), sp.MinOpened) | |
| } | |
| totalQueries := int(sp.MaxOpened * 8) | |
| jobs := make(chan int, totalQueries) | |
| results := make(chan int, totalQueries) | |
| parallel := int(sp.MaxOpened * 2) | |
| for w := 0; w < parallel; w++ { | |
| go readWorker(client, b, jobs, results) | |
| } | |
| for j := 0; j < totalQueries; j++ { | |
| jobs <- j | |
| } | |
| close(jobs) | |
| totalRows := 0 | |
| for a := 0; a < totalQueries; a++ { | |
| totalRows = totalRows + <-results | |
| } | |
| reportBenchmark(b, sp, server) | |
| teardown() | |
| } | |
| } | |
| func Benchmark_Client_BurstWrite01(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 1) | |
| } | |
| func Benchmark_Client_BurstWrite10(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 10) | |
| } | |
| func Benchmark_Client_BurstWrite20(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 20) | |
| } | |
| func Benchmark_Client_BurstWrite25(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 25) | |
| } | |
| func Benchmark_Client_BurstWrite30(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 30) | |
| } | |
| func Benchmark_Client_BurstWrite40(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 40) | |
| } | |
| func Benchmark_Client_BurstWrite50(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 50) | |
| } | |
| func Benchmark_Client_BurstWrite100(b *testing.B) { | |
| benchmarkClientBurstWrite(b, 100) | |
| } | |
| func benchmarkClientBurstWrite(b *testing.B, incStep uint64) { | |
| for n := 0; n < b.N; n++ { | |
| server, client, teardown := createBenchmarkServer(incStep) | |
| sp := client.idleSessions | |
| if uint64(sp.idleList.Len()) != sp.MinOpened { | |
| b.Fatalf("session count mismatch\nGot: %d\nWant: %d", sp.idleList.Len(), sp.MinOpened) | |
| } | |
| totalUpdates := int(sp.MaxOpened * 8) | |
| jobs := make(chan int, totalUpdates) | |
| results := make(chan int64, totalUpdates) | |
| parallel := int(sp.MaxOpened * 2) | |
| for w := 0; w < parallel; w++ { | |
| go writeWorker(client, b, jobs, results) | |
| } | |
| for j := 0; j < totalUpdates; j++ { | |
| jobs <- j | |
| } | |
| close(jobs) | |
| totalRows := int64(0) | |
| for a := 0; a < totalUpdates; a++ { | |
| totalRows = totalRows + <-results | |
| } | |
| reportBenchmark(b, sp, server) | |
| teardown() | |
| } | |
| } | |
| func Benchmark_Client_BurstReadAndWrite01(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 1) | |
| } | |
| func Benchmark_Client_BurstReadAndWrite10(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 10) | |
| } | |
| func Benchmark_Client_BurstReadAndWrite20(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 20) | |
| } | |
| func Benchmark_Client_BurstReadAndWrite25(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 25) | |
| } | |
| func Benchmark_Client_BurstReadAndWrite30(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 30) | |
| } | |
| func Benchmark_Client_BurstReadAndWrite40(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 40) | |
| } | |
| func Benchmark_Client_BurstReadAndWrite50(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 50) | |
| } | |
| func Benchmark_Client_BurstReadAndWrite100(b *testing.B) { | |
| benchmarkClientBurstReadAndWrite(b, 100) | |
| } | |
| func benchmarkClientBurstReadAndWrite(b *testing.B, incStep uint64) { | |
| for n := 0; n < b.N; n++ { | |
| server, client, teardown := createBenchmarkServer(incStep) | |
| sp := client.idleSessions | |
| if uint64(sp.idleList.Len()) != sp.MinOpened { | |
| b.Fatalf("session count mismatch\nGot: %d\nWant: %d", sp.idleList.Len(), sp.MinOpened) | |
| } | |
| totalUpdates := int(sp.MaxOpened * 4) | |
| writeJobs := make(chan int, totalUpdates) | |
| writeResults := make(chan int64, totalUpdates) | |
| parallelWrites := int(sp.MaxOpened) | |
| totalQueries := int(sp.MaxOpened * 4) | |
| readJobs := make(chan int, totalQueries) | |
| readResults := make(chan int, totalQueries) | |
| parallelReads := int(sp.MaxOpened) | |
| for w := 0; w < parallelWrites; w++ { | |
| go writeWorker(client, b, writeJobs, writeResults) | |
| } | |
| for j := 0; j < totalUpdates; j++ { | |
| writeJobs <- j | |
| } | |
| for w := 0; w < parallelReads; w++ { | |
| go readWorker(client, b, readJobs, readResults) | |
| } | |
| for j := 0; j < totalQueries; j++ { | |
| readJobs <- j | |
| } | |
| close(writeJobs) | |
| close(readJobs) | |
| totalUpdatedRows := int64(0) | |
| for a := 0; a < totalUpdates; a++ { | |
| totalUpdatedRows = totalUpdatedRows + <-writeResults | |
| } | |
| totalReadRows := 0 | |
| for a := 0; a < totalQueries; a++ { | |
| totalReadRows = totalReadRows + <-readResults | |
| } | |
| reportBenchmark(b, sp, server) | |
| teardown() | |
| } | |
| } | |
| func Benchmark_Client_SteadyIncrease01(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 1) | |
| } | |
| func Benchmark_Client_SteadyIncrease10(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 10) | |
| } | |
| func Benchmark_Client_SteadyIncrease20(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 20) | |
| } | |
| func Benchmark_Client_SteadyIncrease25(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 25) | |
| } | |
| func Benchmark_Client_SteadyIncrease30(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 30) | |
| } | |
| func Benchmark_Client_SteadyIncrease40(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 40) | |
| } | |
| func Benchmark_Client_SteadyIncrease50(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 50) | |
| } | |
| func Benchmark_Client_SteadyIncrease100(b *testing.B) { | |
| benchmarkClientSteadyIncrease(b, 100) | |
| } | |
| func benchmarkClientSteadyIncrease(b *testing.B, incStep uint64) { | |
| for n := 0; n < b.N; n++ { | |
| server, client, teardown := createBenchmarkServer(incStep) | |
| sp := client.idleSessions | |
| if uint64(sp.idleList.Len()) != sp.MinOpened { | |
| b.Fatalf("session count mismatch\nGot: %d\nWant: %d", sp.idleList.Len(), sp.MinOpened) | |
| } | |
| transactions := make([]*ReadOnlyTransaction, sp.MaxOpened) | |
| for i := uint64(0); i < sp.MaxOpened; i++ { | |
| transactions[i] = client.ReadOnlyTransaction() | |
| transactions[i].Query(context.Background(), NewStatement(SelectSingerIDAlbumIDAlbumTitleFromAlbums)) | |
| } | |
| for i := uint64(0); i < sp.MaxOpened; i++ { | |
| transactions[i].Close() | |
| } | |
| reportBenchmark(b, sp, server) | |
| teardown() | |
| } | |
| } | |
| func reportBenchmark(b *testing.B, sp *sessionPool, server *MockedSpannerInMemTestServer) { | |
| sp.mu.Lock() | |
| defer sp.mu.Unlock() | |
| requests := drainRequestsFromServer(server.TestSpanner) | |
| // TODO(loite): Use b.ReportMetric when Go1.13 is the minimum required. | |
| b.Logf("BatchCreateSessions: %d\t", countRequests(requests, reflect.TypeOf(&sppb.BatchCreateSessionsRequest{}))) | |
| b.Logf("CreateSession: %d\t", countRequests(requests, reflect.TypeOf(&sppb.CreateSessionRequest{}))) | |
| b.Logf("BeginTransaction: %d\t", countRequests(requests, reflect.TypeOf(&sppb.BeginTransactionRequest{}))) | |
| b.Logf("Commit: %d\t", countRequests(requests, reflect.TypeOf(&sppb.CommitRequest{}))) | |
| b.Logf("NumSessions: %d\t", sp.idleList.Len()) | |
| } | |
| func countRequests(requests []interface{}, tp reflect.Type) (count int) { | |
| for _, req := range requests { | |
| if tp == reflect.TypeOf(req) { | |
| count++ | |
| } | |
| } | |
| return count | |
| } |