Skip to content

Commit f2dd922

Browse files
bench: add the ability to set a load profile
Patch for load profile with additional select and update operations and with support for benchmark space pre-filling. Added: - Flag "insert" - percentage of insert operations to bench space. - Flag "select" - percentage of select operations from bench space. - Flag "update" - percentage of update operations in bench space. - Flag "fill" - number of records to pre-fill the space. ``` user@cartridge-cli % ./cartridge bench --select=30 --update=30 --insert=40 --fill=100000 Tarantool 2.8.3 (Binary) f4897ffe-98dd-40fc-a6f2-21ca8bb52fe7 Parameters: URL: 127.0.0.1:3301 user: guest connections: 10 simultaneous requests: 10 duration: 10 seconds key size: 10 bytes data size: 20 bytes insert: 40 percentages select: 30 percentages update: 30 percentages Data schema | key | value | ------------------------------ | ------------------------------ | random(10) | random(20) The pre-filling of the space has started, because the insert operation is not specified or there was an explicit instruction for pre-filling. ... Pre-filling is finished. Number of records: 100000 Benchmark start. ... Benchmark stop. Results: Success operations: 1332979 Failed operations: 0 Request count: 1334004 Time (seconds): 10.000733 Requests per second: 133390 ``` Part of tarantool#645
1 parent 0a1d653 commit f2dd922

File tree

9 files changed

+312
-12
lines changed

9 files changed

+312
-12
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2323
rpm policy [[1](http://ftp.rpm.org/max-rpm/ch-rpm-file-format.html)]).
2424
For example, dashes in RPM version (like `1.2.3-0`) is no longer supported.
2525

26+
### Added
27+
28+
- Tarantool benchmark tool update (select and update operations)
29+
* Flag "insert" - percentage of insert operations to bench space.
30+
* Flag "select" - percentage of select operations from bench space.
31+
* Flag "update" - percentage of update operations in bench space.
32+
* Flag "fill" - number of records to pre-fill the space.
33+
2634
## [2.11.0] - 2022-01-26
2735

2836
### Changed

cli/bench/bench.go

+91-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"time"
99

1010
"github.com/FZambia/tarantool"
11-
"github.com/tarantool/cartridge-cli/cli/common"
1211
"github.com/tarantool/cartridge-cli/cli/context"
1312
)
1413

@@ -22,14 +21,25 @@ func printResults(results Results) {
2221
fmt.Printf("\tRequests per second: %d\n\n", results.requestsPerSecond)
2322
}
2423

24+
// verifyOperationsPercentage checks that the amount of operations percentage is 100.
25+
func verifyOperationsPercentage(ctx *context.BenchCtx) error {
26+
entire_percentage := ctx.InsertCount + ctx.SelectCount + ctx.UpdateCount
27+
if entire_percentage != 100 {
28+
return fmt.Errorf(
29+
"The number of operations as a percentage should be equal to 100, " +
30+
"note that by default the percentage of inserts is 100")
31+
}
32+
return nil
33+
}
34+
2535
// spacePreset prepares space for a benchmark.
2636
func spacePreset(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection) error {
2737
dropBenchmarkSpace(tarantoolConnection)
2838
return createBenchmarkSpace(tarantoolConnection)
2939
}
3040

3141
// incrementRequest increases the counter of successful/failed requests depending on the presence of an error.
32-
func incrementRequest(err error, results *Results) {
42+
func (results *Results) incrementRequestsCounters(err error) {
3343
if err == nil {
3444
results.successResultCount++
3545
} else {
@@ -39,39 +49,62 @@ func incrementRequest(err error, results *Results) {
3949
}
4050

4151
// requestsLoop continuously executes the insert query until the benchmark time runs out.
42-
func requestsLoop(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection, results *Results, backgroundCtx bctx.Context) {
52+
func requestsLoop(requestsSequence *RequestsSequence, backgroundCtx bctx.Context) {
4353
for {
4454
select {
4555
case <-backgroundCtx.Done():
4656
return
4757
default:
48-
_, err := tarantoolConnection.Exec(
49-
tarantool.Insert(
50-
benchSpaceName,
51-
[]interface{}{common.RandomString(ctx.KeySize), common.RandomString(ctx.DataSize)}))
52-
incrementRequest(err, results)
58+
request := requestsSequence.getNext()
59+
request.operation(request)
5360
}
5461
}
5562
}
5663

57-
// connectionLoop runs "ctx.SimultaneousRequests" requests execution threads through the same connection.
58-
func connectionLoop(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection, results *Results, backgroundCtx bctx.Context) {
64+
// connectionLoop runs "ctx.SimultaneousRequests" requests execution threads
65+
// through the same connection.
66+
func connectionLoop(
67+
ctx *context.BenchCtx,
68+
requestsSequence RequestsSequence,
69+
backgroundCtx bctx.Context,
70+
) {
5971
var connectionWait sync.WaitGroup
6072
for i := 0; i < ctx.SimultaneousRequests; i++ {
6173
connectionWait.Add(1)
6274
go func() {
6375
defer connectionWait.Done()
64-
requestsLoop(ctx, tarantoolConnection, results, backgroundCtx)
76+
requestsLoop(&requestsSequence, backgroundCtx)
6577
}()
6678
}
6779

6880
connectionWait.Wait()
6981
}
7082

83+
// preFillBenchmarkSpaceIfRequired fills benchmark space
84+
// if insert count = 0 or PreFillingCount flag is explicitly specified.
85+
func preFillBenchmarkSpaceIfRequired(ctx context.BenchCtx, connectionPool []*tarantool.Connection) error {
86+
if ctx.InsertCount == 0 || ctx.PreFillingCount != PreFillingCount {
87+
fmt.Println("\nThe pre-filling of the space has started,\n" +
88+
"because the insert operation is not specified\n" +
89+
"or there was an explicit instruction for pre-filling.")
90+
fmt.Println("...")
91+
filledCount, err := fillBenchmarkSpace(ctx, connectionPool)
92+
if err != nil {
93+
return err
94+
}
95+
fmt.Printf("Pre-filling is finished. Number of records: %d\n\n", filledCount)
96+
}
97+
return nil
98+
}
99+
71100
// Main benchmark function.
72101
func Run(ctx context.BenchCtx) error {
73102
rand.Seed(time.Now().UnixNano())
74103

104+
if err := verifyOperationsPercentage(&ctx); err != nil {
105+
return err
106+
}
107+
75108
// Connect to tarantool and preset space for benchmark.
76109
tarantoolConnection, err := tarantool.Connect(ctx.URL, tarantool.Opts{
77110
User: ctx.User,
@@ -103,13 +136,22 @@ func Run(ctx context.BenchCtx) error {
103136
defer connectionPool[i].Close()
104137
}
105138

139+
if err := preFillBenchmarkSpaceIfRequired(ctx, connectionPool); err != nil {
140+
return err
141+
}
142+
106143
fmt.Println("Benchmark start")
107144
fmt.Println("...")
108145

109146
// The "context" will be used to stop all "connectionLoop" when the time is out.
110147
backgroundCtx, cancel := bctx.WithCancel(bctx.Background())
111148
var waitGroup sync.WaitGroup
112149
results := Results{}
150+
getRandomTupleCommand := fmt.Sprintf(
151+
"box.space.%s.index.%s:random",
152+
benchSpaceName,
153+
benchSpacePrimaryIndexName,
154+
)
113155

114156
startTime := time.Now()
115157
timer := time.NewTimer(time.Duration(ctx.Duration * int(time.Second)))
@@ -119,7 +161,44 @@ func Run(ctx context.BenchCtx) error {
119161
waitGroup.Add(1)
120162
go func(connection *tarantool.Connection) {
121163
defer waitGroup.Done()
122-
connectionLoop(ctx, connection, &results, backgroundCtx)
164+
requestsSequence := RequestsSequence{
165+
[]RequestsGenerator{
166+
{
167+
Request{
168+
insertOperation,
169+
ctx,
170+
connection,
171+
&results,
172+
nil,
173+
},
174+
ctx.InsertCount,
175+
},
176+
{
177+
Request{
178+
selectOperation,
179+
ctx,
180+
connection,
181+
&results,
182+
&getRandomTupleCommand,
183+
},
184+
ctx.SelectCount,
185+
},
186+
{
187+
Request{
188+
updateOperation,
189+
ctx,
190+
connection,
191+
&results,
192+
&getRandomTupleCommand,
193+
},
194+
ctx.UpdateCount,
195+
},
196+
},
197+
0,
198+
ctx.InsertCount,
199+
sync.Mutex{},
200+
}
201+
connectionLoop(&ctx, requestsSequence, backgroundCtx)
123202
}(connectionPool[i])
124203
}
125204
// Sends "signal" to all "connectionLoop" and waits for them to complete.

cli/bench/config.go

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
var (
1313
benchSpaceName = "__benchmark_space__"
1414
benchSpacePrimaryIndexName = "__bench_primary_key__"
15+
PreFillingCount = 1000000
1516
)
1617

1718
// printConfig output formatted config parameters.
@@ -25,6 +26,10 @@ func printConfig(ctx context.BenchCtx, tarantoolConnection *tarantool.Connection
2526
fmt.Printf("\tduration: %d seconds\n", ctx.Duration)
2627
fmt.Printf("\tkey size: %d bytes\n", ctx.KeySize)
2728
fmt.Printf("\tdata size: %d bytes\n", ctx.DataSize)
29+
fmt.Printf("\tinsert: %d percentages\n", ctx.InsertCount)
30+
fmt.Printf("\tselect: %d percentages\n", ctx.SelectCount)
31+
fmt.Printf("\tupdate: %d percentages\n\n", ctx.UpdateCount)
32+
2833
fmt.Printf("Data schema\n")
2934
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
3035
fmt.Fprintf(w, "|\tkey\t|\tvalue\n")

cli/bench/requests.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package bench
2+
3+
import (
4+
"math/rand"
5+
"reflect"
6+
7+
"github.com/FZambia/tarantool"
8+
"github.com/tarantool/cartridge-cli/cli/common"
9+
)
10+
11+
// insertOperation execute insert operation.
12+
func insertOperation(request Request) {
13+
_, err := request.tarantoolConnection.Exec(
14+
tarantool.Insert(
15+
benchSpaceName,
16+
[]interface{}{
17+
common.RandomString(request.ctx.KeySize),
18+
common.RandomString(request.ctx.DataSize),
19+
}))
20+
request.results.incrementRequestsCounters(err)
21+
}
22+
23+
// selectOperation execute select operation.
24+
func selectOperation(request Request) {
25+
_, err := request.tarantoolConnection.Exec(tarantool.Call(
26+
*request.getRandomTupleCommand,
27+
[]interface{}{rand.Int()}))
28+
request.results.incrementRequestsCounters(err)
29+
}
30+
31+
// updateOperation execute update operation.
32+
func updateOperation(request Request) {
33+
getRandomTupleResponse, err := request.tarantoolConnection.Exec(
34+
tarantool.Call(*request.getRandomTupleCommand,
35+
[]interface{}{rand.Int()}))
36+
if err == nil {
37+
data := getRandomTupleResponse.Data
38+
if len(data) > 0 {
39+
key := reflect.ValueOf(data[0]).Index(0).Elem().String()
40+
_, err := request.tarantoolConnection.Exec(
41+
tarantool.Update(
42+
benchSpaceName,
43+
benchSpacePrimaryIndexName,
44+
[]interface{}{key},
45+
[]tarantool.Op{tarantool.Op(
46+
tarantool.OpAssign(
47+
2,
48+
common.RandomString(request.ctx.DataSize)))}))
49+
request.results.incrementRequestsCounters(err)
50+
}
51+
}
52+
}
53+
54+
// getNext return next operation in operations sequence.
55+
func (requestsSequence *RequestsSequence) getNext() Request {
56+
// If at the moment the number of remaining requests = 0,
57+
// then find a new generator, which requests count > 0.
58+
// If new generator has requests count = 0, then repeat.
59+
requestsSequence.findNewRequestsGeneratorMutex.Lock()
60+
for requestsSequence.currentCounter == 0 {
61+
// Increase the index, which means logical switching to a new generator.
62+
requestsSequence.currentRequestIndex++
63+
requestsSequence.currentRequestIndex %= len(requestsSequence.requests)
64+
// Get new generator by index.
65+
nextRequestsGenerator := requestsSequence.requests[requestsSequence.currentRequestIndex]
66+
// Get requests count for new operation.
67+
requestsSequence.currentCounter = nextRequestsGenerator.count
68+
}
69+
// Logical taking of a single request.
70+
requestsSequence.currentCounter--
71+
requestsSequence.findNewRequestsGeneratorMutex.Unlock()
72+
return requestsSequence.requests[requestsSequence.currentRequestIndex].request
73+
}

cli/bench/space.go

+83
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package bench
22

33
import (
4+
bctx "context"
45
"fmt"
56
"reflect"
7+
"sync"
68

79
"github.com/FZambia/tarantool"
10+
"github.com/tarantool/cartridge-cli/cli/common"
11+
"github.com/tarantool/cartridge-cli/cli/context"
812
)
913

1014
// createBenchmarkSpace creates benchmark space with formatting and primary index.
@@ -54,3 +58,82 @@ func dropBenchmarkSpace(tarantoolConnection *tarantool.Connection) error {
5458
}
5559
return nil
5660
}
61+
62+
// fillBenchmarkSpace fills benchmark space
63+
// with a PreFillingCount number of records
64+
// using connectionPool for fast filling.
65+
func fillBenchmarkSpace(ctx context.BenchCtx, connectionPool []*tarantool.Connection) (int, error) {
66+
var insertMutex sync.Mutex
67+
var waitGroup sync.WaitGroup
68+
filledCount := 0
69+
errorChan := make(chan error, ctx.Connections)
70+
backgroundCtx, cancel := bctx.WithCancel(bctx.Background())
71+
72+
for i := 0; i < ctx.Connections; i++ {
73+
waitGroup.Add(1)
74+
go func(tarantoolConnection *tarantool.Connection) {
75+
defer waitGroup.Done()
76+
for filledCount < ctx.PreFillingCount && len(errorChan) == 0 {
77+
select {
78+
case <-backgroundCtx.Done():
79+
return
80+
default:
81+
// Lock mutex for checking extra iteration and increment counter.
82+
insertMutex.Lock()
83+
if filledCount == ctx.PreFillingCount {
84+
insertMutex.Unlock()
85+
return
86+
}
87+
filledCount++
88+
insertMutex.Unlock()
89+
_, err := tarantoolConnection.Exec(tarantool.Insert(
90+
benchSpaceName,
91+
[]interface{}{
92+
common.RandomString(ctx.KeySize),
93+
common.RandomString(ctx.DataSize),
94+
},
95+
))
96+
if err != nil {
97+
fmt.Println(err)
98+
errorChan <- err
99+
return
100+
}
101+
}
102+
}
103+
}(connectionPool[i])
104+
}
105+
106+
// Thread for checking error in channel.
107+
go func() {
108+
for {
109+
select {
110+
case <-backgroundCtx.Done():
111+
return
112+
default:
113+
if len(errorChan) > 0 {
114+
// Stop "insert" threads
115+
cancel()
116+
return
117+
}
118+
}
119+
}
120+
}()
121+
122+
waitGroup.Wait()
123+
// Stop all threads
124+
// If "error" thread stopped others "insert" threads, "error" thread stops itself
125+
// If "insert" threads successfully completed, then need to stop "error" thread
126+
cancel()
127+
128+
// Check if we have an error
129+
if len(errorChan) > 0 {
130+
err := <-errorChan
131+
close(errorChan)
132+
return filledCount, fmt.Errorf(
133+
"Error during space pre-filling: %s.",
134+
err.Error())
135+
}
136+
close(errorChan)
137+
138+
return filledCount, nil
139+
}

0 commit comments

Comments
 (0)