Skip to content

test(bigquery): add query benchmark - #6794

Draft
alvarowolfx wants to merge 11 commits into
googleapis:mainfrom
alvarowolfx:impl-bq-benchmark
Draft

alvarowolfx wants to merge 11 commits into
googleapis:mainfrom
alvarowolfx:impl-bq-benchmark

Conversation

@alvarowolfx

Copy link
Copy Markdown
Contributor

Benchmark for the query aspect of the bigquery crate to check resilience to retryable errors. Has different scenario with synthetic and hitting real tables, publishes metrics via Otel, writes errors to a dedicated error file (so we can better act on them) and also writes sample data to a .csv file to be queried later.

Disclaimer: this is a tool. We are not going to run it in the CI. It is not production code. We really just want to make sure it measures what we think it is measuring.

@product-auto-label product-auto-label Bot added the api: bigquery Issues related to the BigQuery API. label Sep 14, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new bigquery-benchmark-queries crate, which provides a comprehensive benchmark and endurance test suite for the Rust BigQuery client library. It supports multiple query scenarios, OpenTelemetry tracing and metrics export, and under-the-hood job retry detection. Feedback on the changes highlights two key issues: first, holding a non-Send tracing span guard across .await points in runner.rs which can cause future safety issues; second, performing blocking synchronous file I/O operations within an async context in reporter.rs, violating the repository's async guidelines.

Comment thread src/bigquery/benchmarks/queries/src/runner.rs
Comment thread src/bigquery/benchmarks/queries/src/reporter.rs Outdated
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.94%. Comparing base (5abcf11) to head (a881948).
⚠️ Report is 43 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6794      +/-   ##
==========================================
- Coverage   96.95%   96.94%   -0.01%     
==========================================
  Files         316      316              
  Lines      105153   105153              
==========================================
- Hits       101949   101943       -6     
- Misses       3204     3210       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alvarowolfx

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive BigQuery SDK Benchmark & Endurance Test Suite (bigquery-benchmark-queries) to measure latency, throughput, and connection stability of the Rust BigQuery client library. The suite supports preset and custom SQL scenarios, OpenTelemetry trace and metric exports, and real-time CSV/JSON reporting. Feedback on the implementation highlights a critical violation of the async style guide in reporter.rs due to blocking synchronous file I/O inside async functions, which should be replaced with asynchronous operations. Additionally, reviewers pointed out a potential CPU starvation issue from periodic vector sorting in build_report during long-running tests, and a validation gap in args.rs where --iterations 0 is not properly rejected when a duration is specified.

Comment on lines +274 to +280
tokio::task::block_in_place(|| {
let _ = csv_writer.flush();
let _ = errors_writer.flush();
if let Ok(json_file) = File::create(&json_path_clone) {
let _ = serde_json::to_writer_pretty(json_file, &current_report);
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using tokio::task::block_in_place to perform synchronous file writes and flushes can cause a panic if the benchmark is run within a single-threaded (current-thread) Tokio runtime. Additionally, performing synchronous blocking I/O operations (like std::fs::File::create and flush) inside an async loop blocks the executor thread, which can introduce artificial latency spikes and skew the benchmark results.

Consider using tokio::fs and tokio::io::AsyncWriteExt to perform all file operations asynchronously, eliminating the need for block_in_place entirely.

Suggested change
tokio::task::block_in_place(|| {
let _ = csv_writer.flush();
let _ = errors_writer.flush();
if let Ok(json_file) = File::create(&json_path_clone) {
let _ = serde_json::to_writer_pretty(json_file, &current_report);
}
});
let current_report = build_report(&stats);
let json_path_clone = json_path.clone();
let _ = tokio::io::AsyncWriteExt::flush(csv_writer).await;
let _ = tokio::io::AsyncWriteExt::flush(errors_writer).await;
if let Ok(json_str) = serde_json::to_string_pretty(&current_report) {
let _ = tokio::fs::write(&json_path_clone, json_str).await;
}
References
  1. No blocking I/O (e.g., std::fs, std::net) in async functions. (link)

Comment on lines +255 to +256
if last_json_write.elapsed() >= Duration::from_secs(5) {
last_json_write = Instant::now();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In long-running endurance tests, the number of successful query samples can grow into the hundreds of thousands or millions. Calling build_report every 5 seconds clones and sorts four potentially massive vectors (success_total_durations, success_send_durations, etc.) to compute percentile latencies. This $O(N \log N)$ sorting overhead will eventually consume significant CPU, leading to CPU starvation and skewing the benchmark's latency measurements.

To prevent this, consider only computing the percentile latencies (and writing the full JSON summary) at the end of the benchmark run, or excluding the percentile calculations from the periodic 5-second updates.

Comment on lines +139 to +144
if let Some(iterations) = self.iterations
&& iterations == 0
&& self.duration.is_none()
{
anyhow::bail!("--iterations must be greater than 0");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If --iterations 0 is passed along with a --duration (e.g., --iterations 0 --duration 1h), the validation succeeds, but the benchmark runner loop will exit immediately on the first iteration because iteration >= max_iter (0 >= 0) evaluates to true.

To prevent this, --iterations should always be validated to be greater than 0 if it is specified, regardless of whether --duration is set.

        if let Some(iterations) = self.iterations
            && iterations == 0
        {
            anyhow::bail!("--iterations must be greater than 0");
        }
References
  1. Prefer validating input ranges (such as preventing zero values that could cause division by zero) at the configuration or input validation boundary rather than using inline filtering at the point of calculation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: bigquery Issues related to the BigQuery API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant