Skip to content

Commit d60ed9f

Browse files
committed
release 0.110.0
1 parent b82090b commit d60ed9f

24 files changed

+1569
-446
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ _testmain.go
3232
*.debug
3333
*.DS_Store
3434
.vscode
35-
metrics.prom
35+
metrics.prom
36+
bulkbench

RELEASENOTES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
Release Notes
22
=============
33

4+
## Release 0.110
5+
6+
#### Release Notes
7+
8+
- Bulk operation (incompatible change)
9+
10+
Due to 'historical' reasons go-hdb does support the following alternatives executing 'bulk' operations:
11+
- via query ("bulk insert ...")
12+
- via named arguments (Flush / NoFlush)
13+
- via 'many' supporting one and two dimensional slices, arrays
14+
- via extended parameter list with (len(args)%'#of paramerters' == 0
15+
- via function argument (func(args []any) error)
16+
17+
As to the restrictions and redundancy comming with some of the options the first three are going to be set to deprecated
18+
and the latter two (extended arguments and function argument) are kept going forward. Until go-hdb release V1.0 would
19+
be available the deprecated options can be used further by switching on 'legacy mode' on connector level.
20+
421
## Release 0.109
522

623
Release 0.109.2 (upgrade urgency: high)

cmd/bulkbench/README.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# bulkbench
2+
3+
bulkbench was created in the context of a performance / throughput analysis of different hdb client implementations.
4+
5+
## Test object
6+
7+
Test object is a column table consisting of 2 columns, one of type integer and one of type double:
8+
9+
```
10+
create column table <TableName> (id integer, field double)
11+
```
12+
13+
## Test variants
14+
15+
The basic idea is to insert data in chunks (batchCount) of a fixed amount of records (batchSize) whether sequentially or 'in parallel'.
16+
The actual 'grade of parallelization' is heavily depending on the test environment (CPU cores, TCP/IP stack). bulkbench 'enables'
17+
potential parallelism 'idiomatically' via Goroutines. Each Goroutine is using an own dedicated database connection for the tests
18+
being independent of the Go sql.DB connection pool handling and configuration.
19+
As the test performance results are heavily 'I/O bound' the implementation mainly tries to reduce client server round-trips. Therefore
20+
the go-hdb driver bulk insert capabilities are used (please refer to the [go-hdb driver documentation and examples](https://github.com/SAP/go-hdb)
21+
for details).
22+
23+
## In a real world example...
24+
25+
... one might consider
26+
27+
* to implement a worker pool with the number of concurrent workers set in relation to GOMAXPROCS
28+
* optimizing the number of records per chunk (batchSize)
29+
* optimizing the go-hdb driver TCP/IP buffer size.
30+
* all writes to the TCP/IP connection are buffered by the go-hdb client
31+
* the buffer size can be configured via the driver.Connector object (BufferSize)
32+
* when reaching the buffer size, the go-hdb driver writes the buffered data to the TCP/IP connection
33+
34+
## Execute tests
35+
36+
**Caution: please do NOT use a productive HANA instance for testing as bulkbench does create schemas and database tables.**
37+
38+
Executing bulkbench starts a HTTP server on 'localhost:8080'.
39+
40+
After starting a browser pointing to the server address the following HTML page should be visible in the browser window:
41+
42+
![cannot display bulkbench.png](./bulkbench.png)
43+
44+
* the first section displays some runtime information like GOMAXPROCS and the driver and database version
45+
* the second section lists all test relevant parameters which can be set as environment variables or commandline parameters
46+
* the third sections allows to execute tests with predefined BatchCount and BatchSize parameters (see parameters command-line flag)
47+
* the last section provides some database operations for the selected test database schema and table
48+
49+
Clicking on one of the predefined test will execute it and display the result consisting of test parameters and the duration in seconds.
50+
The result is a JSON payload, which provides an easy way to be interpreted by a program.
51+
52+
## URL format
53+
54+
Running bulkbench as HTTP server a test can be executed via a HTTP GET using the following URL format:
55+
56+
```
57+
http://<host>:<port>/test/<TestType>?batchcount=<number>&batchsize=<number>
58+
```
59+
with
60+
```
61+
<TestType> =:= Seq | Par
62+
```
63+
64+
## Benchmark
65+
66+
Parallel to the single execution using the browser or any other HTTP client (like wget, curl, ...), the tests can be executed automatically
67+
as Go benchmark. The benchmark can be executed whether by
68+
```
69+
go test -bench .
70+
```
71+
or compiling the benchmark with
72+
```
73+
go test -c
74+
```
75+
and executing it via
76+
```
77+
./bulkbench.test -test.bench .
78+
```
79+
80+
The benchmark is 'self-contained', meaning it includes its own http server (for details please see [httptest](https://golang.org/pkg/net/http/httptest/).
81+
82+
In addition to the standard Go benchmarks four additional metrics are reported:
83+
* avgsec/op: the average time (*)
84+
* maxsec/op: the maximum time (*)
85+
* medsec/op: the median time (*)
86+
* minsec/op: the minimal time (*)
87+
88+
(*) inserting BatchCount x BatchSize records into the database table when executing one test several times.
89+
90+
For details about Go benchmarks please see the [Golang testing documentation](https://golang.org/pkg/testing).
91+
92+
### Benchmark examples
93+
94+
Finally let's see some examples executing the benchmark.
95+
96+
```
97+
export GOHDBDSN="hdb://MyUser:MyPassword@host:port"
98+
go test -c
99+
```
100+
101+
* set the data source name (dsn) via environment variable
102+
* and compile the benchmark
103+
104+
105+
```
106+
./bulkbench.test -test.bench . -test.benchtime 10x
107+
```
108+
109+
* -test.bench . (run all benchmarks)
110+
* -test.benchtime 10x (run each benchmark ten times)
111+
* run benchmarks for all BatchCount / BatchSize combinations defined as parameters
112+
* the test database table is dropped and re-created before each benchmark execution (command-line parameter drop defaults to true)
113+
114+
```
115+
./bulkbench.test -test.bench . -test.benchtime 10x -parameters "10x10000"
116+
```
117+
* same like before but
118+
* execute benchmarks only for 10x10000 as BatchCount / BatchSize combination
119+
120+
```
121+
./bulkbench.test -test.bench . -test.benchtime 10x -wait 5
122+
```
123+
124+
* same like first example and
125+
* -wait 5 (wait 5 seconds before starting a benchmark run to reduce database pressure)
126+
127+
### Benchmark example output
128+
129+
```
130+
./bulkbench.test -test.bench . -test.benchtime 10x -wait 5
131+
132+
GOMAXPROCS: 8
133+
NumCPU: 8
134+
Driver Version: 0.110.0
135+
HANA Version: 2.00.045.00.1575639312
136+
goos: darwin
137+
goarch: arm64
138+
pkg: github.com/SAP/go-hdb/cmd/bulkbench
139+
Benchmark/seq-1x100000-8 10 5633601117 ns/op 0.2156 avgsec/op 0.2486 maxsec/op 0.2155 medsec/op 0.1947 minsec/op
140+
Benchmark/seq-10x10000-8 10 5699490917 ns/op 0.2786 avgsec/op 0.3108 maxsec/op 0.2803 medsec/op 0.2559 minsec/op
141+
Benchmark/seq-100x1000-8 10 6165608862 ns/op 0.7506 avgsec/op 0.8077 maxsec/op 0.7440 medsec/op 0.6990 minsec/op
142+
Benchmark/seq-1x1000000-8 10 6938632171 ns/op 1.514 avgsec/op 1.629 maxsec/op 1.515 medsec/op 1.407 minsec/op
143+
Benchmark/seq-10x100000-8 10 7054997538 ns/op 1.636 avgsec/op 1.725 maxsec/op 1.622 medsec/op 1.536 minsec/op
144+
Benchmark/seq-100x10000-8 10 7810094800 ns/op 2.397 avgsec/op 2.557 maxsec/op 2.411 medsec/op 2.262 minsec/op
145+
Benchmark/seq-1000x1000-8 10 12483512412 ns/op 7.064 avgsec/op 7.356 maxsec/op 7.038 medsec/op 6.812 minsec/op
146+
Benchmark/par-1x100000-8 10 5599063146 ns/op 0.2224 avgsec/op 0.2483 maxsec/op 0.2207 medsec/op 0.2002 minsec/op
147+
Benchmark/par-10x10000-8 10 5897479183 ns/op 0.1346 avgsec/op 0.1571 maxsec/op 0.1305 medsec/op 0.1186 minsec/op
148+
Benchmark/par-100x1000-8 10 9334510842 ns/op 0.2375 avgsec/op 0.2488 maxsec/op 0.2389 medsec/op 0.2175 minsec/op
149+
Benchmark/par-1x1000000-8 10 6892064029 ns/op 1.451 avgsec/op 1.581 maxsec/op 1.434 medsec/op 1.361 minsec/op
150+
Benchmark/par-10x100000-8 10 6699703246 ns/op 0.9531 avgsec/op 1.022 maxsec/op 0.9434 medsec/op 0.9004 minsec/op
151+
Benchmark/par-100x10000-8 10 10699403688 ns/op 0.9221 avgsec/op 0.9838 maxsec/op 0.9181 medsec/op 0.8722 minsec/op
152+
Benchmark/par-1000x1000-8 10 50726314071 ns/op 2.204 avgsec/op 2.301 maxsec/op 2.216 medsec/op 2.096 minsec/op
153+
PASS
154+
```

cmd/bulkbench/README.md.license

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// SPDX-FileCopyrightText: 2020-2022 Stefan Miller
2+
//
3+
// SPDX-License-Identifier: Apache-2.0

cmd/bulkbench/benchmark_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// SPDX-FileCopyrightText: 2014-2022 SAP SE
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package main
6+
7+
import (
8+
"fmt"
9+
"log"
10+
"net/http"
11+
"net/http/httptest"
12+
"os"
13+
"runtime"
14+
"sort"
15+
"testing"
16+
"time"
17+
)
18+
19+
func Benchmark(b *testing.B) {
20+
21+
checkErr := func(err error) {
22+
if err != nil {
23+
b.Fatal(err)
24+
}
25+
}
26+
27+
// Create handler.
28+
dbHandler, err := newDBHandler(b.Logf)
29+
checkErr(err)
30+
testHandler, err := newTestHandler(b.Logf)
31+
checkErr(err)
32+
33+
// Register handlers.
34+
mux := http.NewServeMux()
35+
mux.Handle("/test/", testHandler)
36+
mux.Handle("/db/", dbHandler)
37+
38+
// Start http test server.
39+
ts := httptest.NewServer(mux)
40+
client := ts.Client()
41+
42+
execTest := func(test string, batchCount, batchSize int) (*testResult, error) {
43+
r, err := client.Get(fmt.Sprintf("%s%s?batchcount=%d&batchsize=%d", ts.URL, test, batchCount, batchSize))
44+
if err != nil {
45+
return nil, err
46+
}
47+
defer r.Body.Close()
48+
return newTestResult(r)
49+
}
50+
51+
execDropSchema := func() (*testResult, error) {
52+
r, err := client.Get(fmt.Sprintf("%s/db/dropSchema", ts.URL))
53+
if err != nil {
54+
return nil, err
55+
}
56+
defer r.Body.Close()
57+
return newTestResult(r)
58+
}
59+
60+
const maxDuration time.Duration = 1<<63 - 1
61+
62+
f := func(test string, batchCount, batchSize int, b *testing.B) {
63+
ds := make([]time.Duration, b.N)
64+
var avg, max time.Duration
65+
min := maxDuration
66+
67+
for i := 0; i < b.N; i++ {
68+
r, err := execTest(test, batchCount, batchSize)
69+
if err != nil {
70+
b.Fatal(err)
71+
}
72+
if r.Error != "" {
73+
b.Fatal(r.Error)
74+
}
75+
76+
avg += r.Duration
77+
if r.Duration < min {
78+
min = r.Duration
79+
}
80+
if r.Duration > max {
81+
max = r.Duration
82+
}
83+
ds[i] = r.Duration
84+
}
85+
86+
// Median.
87+
var med time.Duration
88+
sort.Slice(ds, func(i, j int) bool { return ds[i] < ds[j] })
89+
l := len(ds)
90+
switch {
91+
case l == 0: // keep med == 0
92+
case l%2 != 0: // odd number
93+
med = ds[l/2] // mid value
94+
default:
95+
med = (ds[l/2] + ds[l/2-1]) / 2 // even number - return avg of the two mid numbers
96+
}
97+
98+
// Add metrics.
99+
b.ReportMetric((avg / time.Duration(b.N)).Seconds(), "avgsec/op")
100+
b.ReportMetric(min.Seconds(), "minsec/op")
101+
b.ReportMetric(max.Seconds(), "maxsec/op")
102+
b.ReportMetric(med.Seconds(), "medsec/op")
103+
}
104+
105+
// Additional info.
106+
log.SetOutput(os.Stdout)
107+
108+
format := `
109+
GOMAXPROCS: %d
110+
NumCPU: %d
111+
Driver Version: %s
112+
HANA Version: %s
113+
`
114+
log.Printf(format, runtime.GOMAXPROCS(0), runtime.NumCPU(), dbHandler.driverVersion(), dbHandler.hdbVersion())
115+
116+
b.Cleanup(func() {
117+
// close test server
118+
defer ts.Close()
119+
120+
r, err := execDropSchema()
121+
if err != nil {
122+
b.Fatal(err)
123+
}
124+
if r.Error != "" {
125+
b.Fatal(r.Error)
126+
}
127+
})
128+
129+
// Start benchmarks.
130+
names := []string{"seq", "par"}
131+
tests := []string{testSeq, testPar}
132+
133+
for i, test := range tests {
134+
for _, prm := range parameters.prms {
135+
// Use batchCount and batchCount flags.
136+
b.Run(fmt.Sprintf("%s-%dx%d", names[i], prm.BatchCount, prm.BatchSize), func(b *testing.B) {
137+
f(test, prm.BatchCount, prm.BatchSize, b)
138+
})
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)