Skip to content

perf: benchmarks, allocation budgets, and instrumentation#10

Merged
mhenrixon merged 2 commits intomainfrom
perf/benchmarks-and-allocation-budgets
Mar 31, 2026
Merged

perf: benchmarks, allocation budgets, and instrumentation#10
mhenrixon merged 2 commits intomainfrom
perf/benchmarks-and-allocation-budgets

Conversation

@mhenrixon
Copy link
Copy Markdown
Owner

@mhenrixon mhenrixon commented Mar 31, 2026

Summary

  • Instrumentation: ActiveSupport::Notifications events on Client (send_message, send_batch, read_message, read_batch), Executor (execute), and Serializer (serialize, deserialize) for runtime observability
  • Benchmark suite: benchmark-ips + memory_profiler scripts measuring throughput and memory for serialization, client ops, and executor cycles — PGMQ mocked to isolate gem overhead
  • Allocation budget specs: CI guardrails that fail if object allocations spike above thresholds or if any objects are retained (leak detection)
  • Rake tasks: rake bench, rake bench:serialization, rake bench:client, rake bench:executor, rake bench:memory

Test plan

  • All 275 existing specs pass (no regressions from instrumentation changes)
  • 5 new allocation budget specs pass
  • Rubocop clean on all changed files
  • Run bundle exec rake bench to verify benchmark scripts execute end-to-end
  • Run bundle exec rake bench:memory to baseline current allocation profile

Summary by CodeRabbit

  • New Features

    • Added runtime instrumentation to key client, executor, and serializer operations for performance monitoring.
  • Tests

    • Added memory allocation budget tests and leak-detection specs.
  • Chores

    • Added benchmarking scripts and Rake tasks to run performance/memory benchmarks.
    • Added profiling dependencies for benchmarking and memory analysis.

Add performance infrastructure to measure and guard against regressions:

- ActiveSupport::Notifications instrumentation on Client, Executor, and
  Serializer hot paths for runtime observability
- benchmark-ips suite (serialization, client ops, executor cycle) with
  mocked PGMQ to isolate gem overhead from database I/O
- memory_profiler-based detailed allocation analysis and leak detection
- Allocation budget specs that fail CI if object counts spike above
  thresholds (send_message <50, send_batch <25/item, read_batch <30,
  JSON round-trip <20) or if any objects are retained (leak detection)
- Rake tasks: bench, bench:serialization, bench:client, bench:executor,
  bench:memory
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ab8eea1-386c-400a-9ba4-5a09495274c3

📥 Commits

Reviewing files that changed from the base of the PR and between e90434c and 6a709be.

📒 Files selected for processing (1)
  • benchmarks/memory_profile.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • benchmarks/memory_profile.rb

📝 Walkthrough

Walkthrough

This PR adds benchmarking scripts and Rake tasks, introduces a conditional Instrumentation API, wraps serializer/client/executor hot paths with instrumentation, and adds memory-allocation tests. It also adds test-only gems benchmark-ips and memory_profiler.

Changes

Cohort / File(s) Summary
Dependency Management
Gemfile
Added benchmark-ips (> 2.13) and memory_profiler (> 1.1) to the :test group.
Build Tasks
Rakefile
Added bench namespace tasks (serialization, client, executor, memory) plus bench:all and top-level bench.
Benchmarking Helpers
benchmarks/bench_helper.rb
New helper module BenchStubs, MockPgmq class, and payload constants for benchmark scripts.
Benchmark Scripts
benchmarks/serialization_bench.rb, benchmarks/client_bench.rb, benchmarks/executor_bench.rb, benchmarks/memory_profile.rb
New Benchmark.ips and MemoryProfiler scripts for serialization, client ops, executor execution, and memory profiling.
Instrumentation Layer
lib/pgbus/instrumentation.rb
New Pgbus::Instrumentation.instrument(event, payload = {}, &block) that delegates to ActiveSupport::Notifications when available, else yields to the block.
Instrumented Client Operations
lib/pgbus/client.rb
Wrapped send_message, send_batch, read_message, and read_batch calls with Instrumentation.instrument emitting pgbus.client.* events and contextual metadata.
Instrumented Executor
lib/pgbus/active_job/executor.rb
Extracted job_class from payload and wrapped core execute flow in Instrumentation.instrument("pgbus.executor.execute", queue:, job_class:) block; adjusted subsequent job-completed instrumentation to use local job_class.
Instrumented Serializer
lib/pgbus/serializer.rb
Wrapped serialize_job and deserialize_job in Instrumentation.instrument blocks emitting pgbus.serializer.* events with kind: :job.
Allocation Budget Tests
spec/pgbus/allocation_budget_spec.rb
New RSpec file using MemoryProfiler to assert allocation budgets and absence of retained objects for client/serialization/read operations.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant PGMQ as PGMQ
  participant Instrument as Instrumentation
  participant Serializer as Serializer
  participant Executor as Executor

  Client->>Instrument: instrument("pgbus.client.send_message", queue:)
  Instrument-->>Client: yield
  Client->>Serializer: serialize_job(job)
  Serializer->>Instrument: instrument("pgbus.serializer.serialize", kind: :job)
  Instrument-->>Serializer: yield
  Serializer-->>Client: serialized_payload
  Client->>PGMQ: produce(serialized_payload)
  Client-->>Instrument: instrumentation complete

  Executor->>Instrument: instrument("pgbus.executor.execute", queue:, job_class:)
  Instrument-->>Executor: yield
  Executor->>PGMQ: read_message
  PGMQ-->>Executor: message
  Executor->>Serializer: deserialize_job(payload)
  Serializer->>Instrument: instrument("pgbus.serializer.deserialize", kind: :job)
  Instrument-->>Serializer: yield
  Serializer-->>Executor: job_instance
  Executor->>Executor: execute_job(job_instance)
  Executor->>PGMQ: archive_message
  Executor-->>Instrument: instrumentation complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped through code with tiny feet,
Measured bytes and beats per beat,
JSON twirled and jobs took flight,
Memory danced in morning light.
Benchmarks hum — a carrot treat! 🥕

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: adding instrumentation, benchmarks, and allocation budgets to improve performance observability and testing.

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


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

@mhenrixon mhenrixon self-assigned this Mar 31, 2026
@coderabbitai coderabbitai Bot added the enhancement New feature or request label Mar 31, 2026
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: 1

🤖 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/memory_profile.rb`:
- Around line 69-76: Fix the incorrect hash-style access to MemoryProfiler Stat
objects in memory_profile.rb: replace stat[:count] and stat[:memory] with the
Stat method accessors (stat.count and stat.memsize) and stop using stat[:data];
instead pull the gem or location label from the grouping returned by
report.retained_memory_by_gem / report.retained_memory_by_location (e.g.,
iterate with |gem_name, stats| or |location, stats| and then use stat.count and
stat.memsize for each Stat). Ensure you update the puts lines to use the
gem_name/location label plus stat.count and stat.memsize.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ff370d0b-2d84-43f9-a266-0a80a7a23f56

📥 Commits

Reviewing files that changed from the base of the PR and between 31fd277 and e90434c.

📒 Files selected for processing (12)
  • Gemfile
  • Rakefile
  • benchmarks/bench_helper.rb
  • benchmarks/client_bench.rb
  • benchmarks/executor_bench.rb
  • benchmarks/memory_profile.rb
  • benchmarks/serialization_bench.rb
  • lib/pgbus/active_job/executor.rb
  • lib/pgbus/client.rb
  • lib/pgbus/instrumentation.rb
  • lib/pgbus/serializer.rb
  • spec/pgbus/allocation_budget_spec.rb

Comment thread benchmarks/memory_profile.rb
memory_profiler returns Hashes with :data and :count keys, not objects
with method accessors. The :count key in retained_memory_by_* methods
represents bytes, while retained_objects_by_* methods give object counts.

Split the output to show both object counts and memory bytes correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant