Skip to content

feat: add paired Ruby SDK performance benchmarks - #323

Open
dustinbyrne wants to merge 1 commit into
mainfrom
feat/ruby-performance-benchmarks
Open

dustinbyrne wants to merge 1 commit into
mainfrom
feat/ruby-performance-benchmarks

Conversation

@dustinbyrne

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Add every-PR performance reporting for the Ruby SDK using the same sdk-benchmarks/v1 workloads as Node, Python and .NET. Compares separately built and installed merge-base/head gems using the same head-side benchmark code.

  • All 20 flag/capture workloads, six alternating paired repetitions, four independent workload shards, and fresh worker processes for each measurement pass.
  • Native Ruby elapsed/CPU time, cumulative allocated-object counts, RSS and retained ObjectSpace bytes. Unsupported allocation-byte and enqueue-only allocation measurements remain explicit.
  • Controlled loopback delivery through public SDK APIs, independent correctness checks, exact sequence validation, and bounded worker execution.
  • Read-only PR execution plus the separately trusted shared publisher from ci: add trusted SDK benchmark report workflow .github#84. Human-readable comments flag ≥5% changes only when all six pairs agree; percentages do not gate CI.

No SDK runtime, public API or default behavior changes. No release changeset is needed. Automatic comments activate after the trusted caller lands on the default branch. Shared pins match the existing integrations; mainline repinning follows the shared PR's squash merge.

Related integrations: PostHog/posthog-js#5454, PostHog/posthog-python#1135, PostHog/posthog-dotnet#358.

💚 How did you test it?

  • 12 coordinator controls and native controls covering fixtures, counters, generation changes, incomplete delivery and actual worker timeouts.
  • Complete paired smoke: 80 successful workers. Full six-pair execution: 480 successful workers; exact workload/pass/count/checkpoint conformance checked independently.
  • Actual results accepted by the shared schema, shard collector and renderer; failed-source output suppresses numerical comparisons.
  • bundle exec rspec: 2019 examples, 0 failures, 40 pending (optional OpenTelemetry integration bundle).
  • bundle exec rubocop: 145 files, no offenses; bundle exec rake public_api:check unchanged.
  • Python Black/Ruff, Actionlint and whitespace checks passed. Fresh read-only review recorded separately before publication.

Local durations are correctness/execution evidence only; hosted CI establishes the final latency budget. Intentional native scope differences and unsupported metrics are documented in CONTRIBUTING.md.

📝 Checklist

  • All commits are signed.
  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs.
  • No breaking change or changelog entry.

🤖 Agent context

Autonomy: Human-driven (agent-assisted).

Implemented with Pi using file/shell tools and a fresh read-only reviewer. The benchmark preserves public SDK lifecycle and transport behavior; native Ruby counters are used where meaningful instead of equating allocation volume with retained memory. Human review is required.

@dustinbyrne
dustinbyrne requested a review from a team as a code owner October 30, 2026 15:05
@dustinbyrne dustinbyrne self-assigned this Oct 30, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not approved yet — waiting on the conditions below.

Re-add the stamphog label to request another review once you have addressed this.

stamphog can't auto-review this pull request because three gates refused it. The deny-list gate matched infra_cicd, since it adds .github/workflows/benchmarks.yml and .github/workflows/benchmark-report.yml. The size gate refused it because it has 1523 substantive lines against an 800-line ceiling. The tier gate classified it as T2-never (1897 lines across 13 files, two areas, a feature).

The refusal is final, so please ask a human reviewer to look at it. To make review easier, you could split the CI workflow changes from the benchmark scripts and fixtures so each part is smaller and more focused.

Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✗ matches: infra_cicd
size ✗ too large for auto-review (1523L substantive in global — ceiling is 800L; 1523L, 11F total, 1897L/13F incl. docs/generated/snapshots)
tier ✗ classified as T2-never: T2-never (1897L, 13F, two-areas, feat)
stamphog 2.6.1 .stamphog/policy.yml @ unknown · reviewed head b28bf78

@stamphog stamphog Bot removed the stamphog label Oct 30, 2026
@github-actions

Copy link
Copy Markdown

posthog-ruby-sync Compliance Report

Date: 2026-10-30T15:07:23.249799+00:00
Duration: 56087ms

⚠️ Some Tests Failed

39/47 tests passed, 8 failed


Capture Tests

⚠️ 23/30 tests passed, 7 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 10ms
Format Validation.Event Has Uuid ✅ 7ms
Format Validation.Event Has Lib Properties ✅ 10ms
Format Validation.Distinct Id Is String ✅ 7ms
Format Validation.Token Is Present ✅ 7ms
Format Validation.Custom Properties Preserved ✅ 7ms
Format Validation.Event Has Timestamp ✅ 10ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 8ms
Retry Behavior.Retries On 503 ✅ 5260ms
Retry Behavior.Does Not Retry On 400 ❌ 9ms
Retry Behavior.Does Not Retry On 401 ❌ 8ms
Retry Behavior.Respects Retry After Header ✅ 8017ms
Retry Behavior.Implements Backoff ❌ 305ms
Retry Behavior.Retries On 500 ✅ 5117ms
Retry Behavior.Retries On 502 ✅ 5154ms
Retry Behavior.Retries On 504 ✅ 5163ms
Retry Behavior.Max Retries Respected ❌ 530ms
Deduplication.Generates Unique Uuids ✅ 18ms
Deduplication.Preserves Uuid On Retry ✅ 5116ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10328ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5125ms
Deduplication.No Duplicate Events In Batch ✅ 20ms
Deduplication.Different Events Have Different Uuids ✅ 10ms
Compression.Sends Gzip When Enabled ✅ 6ms
Batch Format.Uses Proper Batch Structure ✅ 7ms
Batch Format.Flush With No Events Sends Nothing ✅ 4ms
Batch Format.Multiple Events Batched Together ❌ 18ms
Error Handling.Does Not Retry On 403 ❌ 6ms
Error Handling.Does Not Retry On 413 ❌ 5ms
Error Handling.Retries On 408 ✅ 5112ms

Failures

retry_behavior.does_not_retry_on_400

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

retry_behavior.does_not_retry_on_401

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

retry_behavior.implements_backoff

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

retry_behavior.max_retries_respected

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

batch_format.multiple_events_batched_together

Expected 1 requests, got 5

error_handling.does_not_retry_on_403

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

error_handling.does_not_retry_on_413

500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'

Feature_Flags Tests

⚠️ 16/17 tests passed, 1 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 10ms
Request Payload.Flags Request Uses V2 Query Param ✅ 8ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 9ms
Request Payload.Flags Request Omits Authorization Header ✅ 9ms
Request Payload.Token In Flags Body Matches Init ✅ 10ms
Request Payload.Groups Round Trip ✅ 7ms
Request Payload.Groups Default To Empty Object ✅ 7ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 7ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 7ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 7ms
Request Lifecycle.No Flags Request On Init Alone ✅ 2ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 7ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 10ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 7ms
Retry Behavior.Retries Flags On 502 ✅ 111ms
Retry Behavior.Retries Flags On 504 ✅ 139ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 8ms

Failures

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']

@github-actions

Copy link
Copy Markdown

posthog-ruby-async Compliance Report

Date: 2026-10-30T15:08:04.462100+00:00
Duration: 98142ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 107ms
Format Validation.Event Has Uuid ✅ 104ms
Format Validation.Event Has Lib Properties ✅ 107ms
Format Validation.Distinct Id Is String ✅ 105ms
Format Validation.Token Is Present ✅ 105ms
Format Validation.Custom Properties Preserved ✅ 106ms
Format Validation.Event Has Timestamp ✅ 106ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 6ms
Retry Behavior.Retries On 503 ✅ 5310ms
Retry Behavior.Does Not Retry On 400 ✅ 2108ms
Retry Behavior.Does Not Retry On 401 ✅ 2107ms
Retry Behavior.Respects Retry After Header ✅ 8112ms
Retry Behavior.Implements Backoff ✅ 15516ms
Retry Behavior.Retries On 500 ✅ 5207ms
Retry Behavior.Retries On 502 ✅ 5210ms
Retry Behavior.Retries On 504 ✅ 5210ms
Retry Behavior.Max Retries Respected ✅ 15620ms
Deduplication.Generates Unique Uuids ✅ 108ms
Deduplication.Preserves Uuid On Retry ✅ 5209ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10407ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5213ms
Deduplication.No Duplicate Events In Batch ✅ 110ms
Deduplication.Different Events Have Different Uuids ✅ 105ms
Compression.Sends Gzip When Enabled ✅ 105ms
Batch Format.Uses Proper Batch Structure ✅ 104ms
Batch Format.Flush With No Events Sends Nothing ✅ 3ms
Batch Format.Multiple Events Batched Together ✅ 107ms
Error Handling.Does Not Retry On 403 ✅ 2105ms
Error Handling.Does Not Retry On 413 ✅ 2107ms
Error Handling.Retries On 408 ✅ 5210ms

Feature_Flags Tests

⚠️ 16/17 tests passed, 1 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 106ms
Request Payload.Flags Request Uses V2 Query Param ✅ 106ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 106ms
Request Payload.Flags Request Omits Authorization Header ✅ 106ms
Request Payload.Token In Flags Body Matches Init ✅ 108ms
Request Payload.Groups Round Trip ✅ 107ms
Request Payload.Groups Default To Empty Object ✅ 105ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 106ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 105ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 106ms
Request Lifecycle.No Flags Request On Init Alone ✅ 2ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 104ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 108ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 105ms
Retry Behavior.Retries Flags On 502 ✅ 208ms
Retry Behavior.Retries Flags On 504 ✅ 244ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 107ms

Failures

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']

@posthog posthog 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.

PostHog Review · Standard · reviewed b28bf78: no findings. This pull request is large, so the review ran in 4 parts with less depth than usual.

This branch has not been deployed

No deployments
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