From d9b3b6ed62f739b2bfb7960ccc903e3646dfad45 Mon Sep 17 00:00:00 2001 From: Aymeric Date: Tue, 17 Jan 2023 10:47:49 +0000 Subject: [PATCH] feat: add performance test (FIR-17551) (#43) --- .github/workflows/performance-tests.yml | 61 ++++++++++++++ performance_test.go | 103 ++++++++++++++++++++++++ scripts/performance_test_init.go | 37 +++++++++ 3 files changed, 201 insertions(+) create mode 100644 .github/workflows/performance-tests.yml create mode 100644 performance_test.go create mode 100644 scripts/performance_test_init.go diff --git a/.github/workflows/performance-tests.yml b/.github/workflows/performance-tests.yml new file mode 100644 index 0000000..bfcdf07 --- /dev/null +++ b/.github/workflows/performance-tests.yml @@ -0,0 +1,61 @@ +name: Performance tests + +on: workflow_dispatch + +jobs: + performance-tests: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: '1.18.0' + + - name: Setup database and engine + id: setup + uses: firebolt-db/integration-testing-setup@master + with: + firebolt-username: ${{ secrets.FIREBOLT_USERNAME_STAGING }} + firebolt-password: ${{ secrets.FIREBOLT_PASSWORD_STAGING }} + api-endpoint: "api.staging.firebolt.io" + region: "us-east-1" + instance-type: "C2" + engine-scale: 2 + + - name: Add data to database + env: + USER_NAME: ${{ secrets.FIREBOLT_USERNAME_STAGING }} + PASSWORD: ${{ secrets.FIREBOLT_PASSWORD_STAGING }} + DATABASE_NAME: ${{ steps.setup.outputs.database_name }} + ENGINE_NAME: ${{ steps.setup.outputs.engine_name }} + ENGINE_URL: ${{ steps.setup.outputs.engine_url }} + STOPPED_ENGINE_NAME: ${{ steps.setup.outputs.stopped_engine_name }} + STOPPED_ENGINE_URL: ${{ steps.setup.outputs.stopped_engine_url }} + FIREBOLT_ENDPOINT: "api.staging.firebolt.io" + ACCOUNT_NAME: "firebolt" + run: | + go run scripts/performance_test_init.go + + - name: gobenchdata publish + uses: bobheadxi/gobenchdata@v1 + with: + PRUNE_COUNT: 20 + GO_TEST_FLAGS: -test.benchtime=50x + PUBLISH: true + PUBLISH_BRANCH: gh-pages + GO_BENCHMARKS: BenchmarkSelect + BENCHMARKS_OUT: benchmark/benchmarks.json + env: + USER_NAME: ${{ secrets.FIREBOLT_USERNAME_STAGING }} + PASSWORD: ${{ secrets.FIREBOLT_PASSWORD_STAGING }} + DATABASE_NAME: ${{ steps.setup.outputs.database_name }} + ENGINE_NAME: ${{ steps.setup.outputs.engine_name }} + ENGINE_URL: ${{ steps.setup.outputs.engine_url }} + STOPPED_ENGINE_NAME: ${{ steps.setup.outputs.stopped_engine_name }} + STOPPED_ENGINE_URL: ${{ steps.setup.outputs.stopped_engine_url }} + FIREBOLT_ENDPOINT: "api.staging.firebolt.io" + ACCOUNT_NAME: "firebolt" + TEST_THREAD_COUNT: 5 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/performance_test.go b/performance_test.go new file mode 100644 index 0000000..53c1add --- /dev/null +++ b/performance_test.go @@ -0,0 +1,103 @@ +package fireboltgosdk + +import ( + "database/sql" + "fmt" + "log" + "os" + "strconv" + "sync" + "testing" +) + +var pool *sql.DB +var threadCount int +var selectLineItemQuery = "select * from lineitem ORDER BY l_orderkey LIMIT 1000;" +var select1Query = "select 1;" + +func TestMain(m *testing.M) { + username := os.Getenv("USER_NAME") + password := os.Getenv("PASSWORD") + databaseName := os.Getenv("DATABASE_NAME") + threadCount, _ = strconv.Atoi(os.Getenv("TEST_THREAD_COUNT")) + dsn := fmt.Sprintf("firebolt://%s:%s@%s", username, password, databaseName) + var err error + pool, err = sql.Open("firebolt", dsn) + + if err != nil { + log.Fatal("error during opening a driver", err) + } + code := m.Run() + err = pool.Close() + if err != nil { + return + } + os.Exit(code) +} + +func BenchmarkSelectLineItemWithThreads(b *testing.B) { + benchmarkSelectWithThreads(b, selectLineItemQuery) +} + +func BenchmarkSelectLineItemWithoutThreads(b *testing.B) { + benchmarkSelectWithoutThreads(b, selectLineItemQuery) +} + +func BenchmarkSelect1WithThreads(b *testing.B) { + benchmarkSelectWithThreads(b, select1Query) +} + +func BenchmarkSelect1WithoutThreads(b *testing.B) { + benchmarkSelectWithoutThreads(b, select1Query) +} + +func benchmarkSelectWithThreads(b *testing.B, query string) { + var loops = b.N + var wg sync.WaitGroup + wg.Add(threadCount) + for j := 0; j < threadCount; j++ { + go func(i int) { + executeQuery(loops, query, b) + defer wg.Done() + }(j) + } + wg.Wait() +} + +func benchmarkSelectWithoutThreads(b *testing.B, query string) { + executeQuery(b.N, query, b) +} + +func executeQuery(loops int, query string, b *testing.B) { + var columns []string + for i := 0; i < loops; i++ { + rows, err := pool.Query(query) + if err != nil { + b.Errorf("error during select query %v", err) + } + + // because the function is used for different queries, we only know the number of columns at runtime. + columns, err = rows.Columns() + if err != nil { + b.Errorf("error while getting columns %v", err) + } + columnCount := len(columns) + values := make([]interface{}, columnCount) + valuePointers := make([]interface{}, columnCount) + for i := range columns { + valuePointers[i] = &values[i] + } + + // iterating over the resulting rows + for rows.Next() { + if err := rows.Scan(valuePointers...); err != nil { + b.Errorf("error during scan: %v", err) + } + } + err = rows.Close() + if err != nil { + b.Errorf("error while closing the row %v", err) + } + } + +} diff --git a/scripts/performance_test_init.go b/scripts/performance_test_init.go new file mode 100644 index 0000000..bc909f1 --- /dev/null +++ b/scripts/performance_test_init.go @@ -0,0 +1,37 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "os" +) + +func main() { + username := os.Getenv("USER_NAME") + password := os.Getenv("PASSWORD") + databaseName := os.Getenv("DATABASE_NAME") + dsn := fmt.Sprintf("firebolt://%s:%s@%s", username, password, databaseName) + + db, err := sql.Open("firebolt", dsn) + + if err != nil { + log.Fatal("error while opening a database", err) + } + queries := []string{ + "CREATE EXTERNAL TABLE IF NOT EXISTS ex_lineitem ( l_orderkey LONG, l_partkey LONG, l_suppkey LONG, l_linenumber INT, l_quantity LONG, l_extendedprice LONG, l_discount LONG, l_tax LONG, l_returnflag TEXT, l_linestatus TEXT, l_shipdate TEXT, l_commitdate TEXT, l_receiptdate TEXT, l_shipinstruct TEXT, l_shipmode TEXT, l_comment TEXT)URL = 's3://firebolt-publishing-public/samples/tpc-h/parquet/lineitem/'OBJECT_PATTERN = '*.parquet'TYPE = (PARQUET)", + "CREATE FACT TABLE IF NOT EXISTS lineitem ( l_orderkey LONG, l_partkey LONG, l_suppkey LONG, l_linenumber INT, l_quantity LONG, l_extendedprice LONG, l_discount LONG, l_tax LONG, l_returnflag TEXT, l_linestatus TEXT, l_shipdate TEXT, l_commitdate TEXT, l_receiptdate TEXT, l_shipinstruct TEXT, l_shipmode TEXT, l_comment TEXT ) PRIMARY INDEX l_orderkey, l_linenumber", + "INSERT INTO lineitem SELECT * FROM ex_lineitem"} + + for _, query := range queries { + _, err := db.Query(query) + if err != nil { + log.Fatalf("the query %s returned an error: %v", query, err) + } + } + err = db.Close() + if err != nil { + log.Fatal("error while closing the database", err) + } + +}