Skip to content

perf: add benchmarking infrastructure and performance baseline report#16

Merged
poyrazK merged 2 commits intomainfrom
feature/benchmarking
Apr 4, 2026
Merged

perf: add benchmarking infrastructure and performance baseline report#16
poyrazK merged 2 commits intomainfrom
feature/benchmarking

Conversation

@poyrazK
Copy link
Copy Markdown
Owner

@poyrazK poyrazK commented Apr 4, 2026

This PR introduces the performance benchmarking infrastructure for cloudSQL.

Changes:

  • Integrated Google Benchmark via CMake FetchContent.
  • Created a benchmarks/ directory with suites for Storage, Execution, and Network.
  • Added a BUILD_BENCHMARKS CMake option.
  • Included the initial Performance Baseline Report V1.0 and profiling analysis identifying memory allocation bottlenecks.

Summary by CodeRabbit

  • New Features

    • Added performance benchmarking infrastructure with a configurable build option to measure key storage, execution, and network operations.
  • Documentation

    • Added initial performance baseline report documenting profiling findings and optimization recommendations for write-heavy workloads.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Warning

Rate limit exceeded

@poyrazK has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 58 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 58 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 096c47df-8fbc-4041-ab4f-0dd685b4724b

📥 Commits

Reviewing files that changed from the base of the PR and between ba5a230 and a9d4e6d.

📒 Files selected for processing (5)
  • CMakeLists.txt
  • benchmarks/execution_bench.cpp
  • benchmarks/network_bench.cpp
  • benchmarks/storage_bench.cpp
  • docs/performance/REPORT_V1.md
📝 Walkthrough

Walkthrough

This change adds comprehensive benchmarking infrastructure to the project, integrating Google Benchmark v1.8.3 via CMake, introducing three benchmark suites measuring storage, execution, and network subsystems, and providing an initial performance baseline report with profiling analysis.

Changes

Cohort / File(s) Summary
CMake Build Infrastructure
CMakeLists.txt
Added FetchContent integration to download Google Benchmark v1.8.3; introduced add_cloudsql_benchmark helper macro for creating benchmark executables linked against sqlEngineCore and benchmark library; added BUILD_BENCHMARKS option enabling three benchmark targets (storage_bench, execution_bench, network_bench).
Storage & Execution Benchmarks
benchmarks/storage_bench.cpp, benchmarks/execution_bench.cpp
New benchmark suites measuring buffer pool page fetch/unpin cycles, heap table insertions, sequential table scans, and hash join operations across configurable input sizes using temporary on-disk storage backed by StorageManager and BufferPoolManager.
Network Benchmarks
benchmarks/network_bench.cpp
New benchmark fixture exercising RPC round-trip performance with RpcServer/RpcClient pair, testing payload throughput across three message sizes (64B, 1KB, 16KB) via repeated synchronous AppendEntries calls.
Performance Documentation
docs/performance/REPORT_V1.md
Initial performance baseline report documenting release-build metrics for storage, execution, and network layers; includes profiling findings highlighting allocation-related bottlenecks in HeapTable::insert with call graph analysis and three recommended optimization strategies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Benchmarks hop through every lane,
Storage, nets, and execution's domain,
Measuring paths both swift and free,
Performance secrets now we see!
Allocation bounds, optimizations bright,
SQL engines take their flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and accurately summarizes the main changes: adding benchmarking infrastructure and a performance baseline report, which aligns perfectly with all the file additions (CMakeLists.txt integration, three benchmark files, and REPORT_V1.md).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/benchmarking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (3)
benchmarks/network_bench.cpp (1)

24-31: Unchecked send() return values in handler.

The handler ignores return values from send() calls. If either send fails (e.g., client disconnects), the benchmark continues with corrupt state. Consider at minimum logging failures, or returning early.

♻️ Proposed fix
         server->set_handler(RpcType::AppendEntries, [](const RpcHeader& header, const std::vector<uint8_t>& payload, int client_fd) {
             RpcHeader resp_header = header;
             resp_header.payload_len = static_cast<uint16_t>(payload.size());
             char header_buf[RpcHeader::HEADER_SIZE];
             resp_header.encode(header_buf);
-            send(client_fd, header_buf, RpcHeader::HEADER_SIZE, 0);
-            send(client_fd, payload.data(), payload.size(), 0);
+            if (send(client_fd, header_buf, RpcHeader::HEADER_SIZE, 0) <= 0) {
+                return;
+            }
+            if (!payload.empty() && send(client_fd, payload.data(), payload.size(), 0) <= 0) {
+                return;
+            }
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/network_bench.cpp` around lines 24 - 31, The handler registered
via server->set_handler for RpcType::AppendEntries ignores the return values
from the two send() calls, which can hide socket errors and leave the benchmark
in an invalid state; update the lambda (the AppendEntries handler) to check each
send() return value, log failures using the project logger or stderr including
client_fd and errno, and return early (or close the client_fd) on send errors to
avoid continuing with corrupt state — ensure both the header send (after
RpcHeader::encode) and the payload send are validated and handled.
CMakeLists.txt (1)

94-98: Consider linking benchmark::benchmark_main for consistency.

The macro links only benchmark::benchmark, requiring each benchmark source to include BENCHMARK_MAIN(). This works but linking benchmark::benchmark_main instead would eliminate that boilerplate.

♻️ Optional: Link benchmark_main to avoid BENCHMARK_MAIN() in sources
 macro(add_cloudsql_benchmark NAME SOURCE)
     add_executable(${NAME} ${SOURCE})
-    target_link_libraries(${NAME} sqlEngineCore benchmark::benchmark)
+    target_link_libraries(${NAME} sqlEngineCore benchmark::benchmark_main)
 endmacro()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 94 - 98, The add_cloudsql_benchmark macro
currently links targets to benchmark::benchmark requiring each benchmark source
to declare BENCHMARK_MAIN(); update the macro (macro name
add_cloudsql_benchmark) to also link benchmark::benchmark_main in
target_link_libraries so executables automatically get the main entry for Google
Benchmark (i.e., add benchmark::benchmark_main alongside benchmark::benchmark
when calling target_link_libraries for the created target).
docs/performance/REPORT_V1.md (1)

27-35: Add language specifier to fenced code block.

The static analysis tool flagged a missing language specifier. Since this is a call graph/ASCII representation, use text or plaintext.

📝 Proposed fix
-```
+```text
 BM_HeapTableInsert
   -> HeapTable::insert (1,859 samples)
   -> BufferPoolManager::unpin_page (789 samples)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/performance/REPORT_V1.md` around lines 27 - 35, The fenced code block
showing the call graph (starting with BM_HeapTableInsert and lines including
HeapTable::insert and BufferPoolManager::unpin_page) lacks a language specifier;
update the opening triple-backtick to include a plaintext language (e.g.,
```text or ```plaintext) so the static analysis tool recognizes it as plain text
and preserves formatting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@benchmarks/execution_bench.cpp`:
- Around line 45-55: The benchmark currently moves `table` into
`SeqScanOperator` each iteration (via std::move), destroying it and then
recreating an empty `HeapTable` without calling `SetupBenchTable()`, so only the
first iteration scans rows; fix by avoiding ownership transfer in the hot loop:
construct `table` and call `SetupBenchTable()` once outside the loop, and change
the `SeqScanOperator` invocation to accept a non-owning reference/pointer (e.g.,
pass `table.get()` or a `HeapTable&`) or use a `shared_ptr` so you can create
`scan_op` without std::move; alternatively, if you really want to re-populate
each iteration, call `SetupBenchTable()` after recreating
`HeapTable("scan_table", bpm, schema)` so the table has rows before scanning.
Ensure references to `SeqScanOperator`, `HeapTable`, `SetupBenchTable`, and the
`table` variable are updated accordingly.
- Around line 80-100: The benchmark currently std::move()s
left_table/right_table into SeqScanOperator inside the loop (see SeqScanOperator
and HashJoinOperator construction), which transfers ownership and leaves the
tables empty for subsequent iterations; fix by preserving table ownership across
iterations: either create the HeapTable instances once outside the for loop so
they are reused, or change the SeqScanOperator construction to take non-owning
pointers/references (e.g., pass left_table.get()/right_table.get() or use
shared_ptr instead of std::unique_ptr) so you don't std::move() the tables; if
you must recreate tables each iteration, repopulate them with data after
construction before creating SeqScanOperator/HashJoinOperator.

In `@benchmarks/network_bench.cpp`:
- Around line 42-46: TearDown currently calls server->stop() without checking if
server was initialized (SetUp may have failed); update TearDown in the test
class to guard the stop call by checking the server pointer (e.g., if (server)
server->stop()) before calling server.reset(), leaving client.reset() as-is;
ensure the check uses the same server symbol used in TearDown so you don't call
methods on a null pointer.
- Around line 33-39: The current code returns early when server->start() fails
which leaves client (the std::unique_ptr<RpcClient>) null and leaves the
benchmark in an inconsistent state; update the failure path in the block
containing server->start() so it aborts the benchmark cleanly (e.g., call
state.SkipWithError("server failed to start: <brief msg>") or throw) and ensure
any resources are reset/cleaned before returning so subsequent iterations won't
operate on a null client; locate the logic around server->start(), the client
unique_ptr and RpcClient construction/connection to apply this change.

In `@benchmarks/storage_bench.cpp`:
- Around line 49-76: BM_HeapTableInsert currently removes the test directory
while BufferPoolManager and HeapTable are still alive which can cause flush
failures, and uses a hardcoded "./bench_data_table" so different benchmark args
or concurrent runs could clash; fix by scoping or destroying bpm and table
before calling std::filesystem::remove_all (e.g., wrap StorageManager,
BufferPoolManager, HeapTable, and the tuple creation in an inner scope or
explicitly reset/destroy bpm/table) and make test_dir unique per run (use
benchmark state to incorporate state.thread_index(), state.range(0) or
state.iterations() into the directory name) so directories do not collide.
- Around line 43-46: The directory cleanup runs before bpm and disk_manager are
destroyed, causing flush_all_pages() to fail when destructors attempt I/O; move
the std::filesystem::remove_all(test_dir) so it runs after bpm and disk_manager
are out of scope (e.g., wrap bpm/disk_manager in an inner scope or explicitly
destroy/reset them before calling remove_all), ensuring the destructor of
BufferPoolManager (bpm) and DiskManager complete flushes to test_dir before
deletion.

---

Nitpick comments:
In `@benchmarks/network_bench.cpp`:
- Around line 24-31: The handler registered via server->set_handler for
RpcType::AppendEntries ignores the return values from the two send() calls,
which can hide socket errors and leave the benchmark in an invalid state; update
the lambda (the AppendEntries handler) to check each send() return value, log
failures using the project logger or stderr including client_fd and errno, and
return early (or close the client_fd) on send errors to avoid continuing with
corrupt state — ensure both the header send (after RpcHeader::encode) and the
payload send are validated and handled.

In `@CMakeLists.txt`:
- Around line 94-98: The add_cloudsql_benchmark macro currently links targets to
benchmark::benchmark requiring each benchmark source to declare
BENCHMARK_MAIN(); update the macro (macro name add_cloudsql_benchmark) to also
link benchmark::benchmark_main in target_link_libraries so executables
automatically get the main entry for Google Benchmark (i.e., add
benchmark::benchmark_main alongside benchmark::benchmark when calling
target_link_libraries for the created target).

In `@docs/performance/REPORT_V1.md`:
- Around line 27-35: The fenced code block showing the call graph (starting with
BM_HeapTableInsert and lines including HeapTable::insert and
BufferPoolManager::unpin_page) lacks a language specifier; update the opening
triple-backtick to include a plaintext language (e.g., ```text or ```plaintext)
so the static analysis tool recognizes it as plain text and preserves
formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a31cd230-bf36-42f0-a775-8d13ef320a57

📥 Commits

Reviewing files that changed from the base of the PR and between d023edd and ba5a230.

📒 Files selected for processing (5)
  • CMakeLists.txt
  • benchmarks/execution_bench.cpp
  • benchmarks/network_bench.cpp
  • benchmarks/storage_bench.cpp
  • docs/performance/REPORT_V1.md

@poyrazK poyrazK merged commit 8bc4289 into main Apr 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant