[AGENT] Improve performance, fix bugs, and restructure documentation#39
Merged
Conversation
Executors previously rewrote instruction[:kind] to :step to delegate to the Step executor via super, which forced a Marshal.dump deep copy of the entire instruction tree on every operation call. Extract execute_step as a shared primitive on the base Executor class that any executor can call directly to invoke a step method, instrument it, and record the execution -- without mutating the instruction hash. This eliminates the need for any copying and yields ~40% throughput improvement on operation calls.
…nstant The class method referenced DEFAULT_MODE which does not exist, causing a NameError if ever called. Corrected to DEVELOPMENT_MODE to match the constant defined at the top of the class.
Config previously reached into Rails.application.config.x.reporter at initialization time, silently overriding any explicitly configured reporter. The reporter should be set by the consumer via the config block, not auto-detected from the host framework. BREAKING: Apps relying on config.x.reporter must now set the reporter explicitly in their Opera initializer.
Slim the README from 1,175 to 221 lines, focusing on what developers need to get started: installation, one complete example, configuration, DSL reference table, and Result API table. All existing examples are preserved verbatim in docs/examples/ as separate files organized by topic: basic operations, validations, transactions, success blocks, finish_if, inner operations, within, and context/params/dependencies.
Exercises the full execution path across four operation types: ComplexOperation (nested ops + transaction + within + validate + success + finish_if), BatchOperation (operations plural), ValidationOperation, and LeafOperation (minimal). Runs 1000 iterations of each, spawning ~7000 total operation instances for the complex scenario.
kikorb
commented
Apr 14, 2026
| @@ -0,0 +1,240 @@ | |||
| # frozen_string_literal: true | |||
Member
Author
There was a problem hiding this comment.
This file is here temporarily for the reviewers to understand the benchmark data, we can remove it later
Contributor
There was a problem hiding this comment.
keep it for future. It is branch agnostic. We can run it for sanity check before and after every major refactor
Exercises within nested inside a transaction with multiple steps and an inner operation, giving a dedicated measurement for the within executor path which benefits most from the Marshal.dump removal due to its deeper instruction tree nesting.
Replace super delegation with an explicit evaluate_instructions call, consistent with how all other executors now work after the execute_step refactor. No behavioral change -- just removes the last indirect super call from the executor hierarchy.
msx2
approved these changes
Apr 14, 2026
petergebala
reviewed
Apr 14, 2026
petergebala
left a comment
Contributor
There was a problem hiding this comment.
Lets update changelog and bump version too :)
cintrzyk
approved these changes
Apr 15, 2026
1df3e94 to
70af2bc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A focused set of improvements to Opera's internals, correctness, and developer experience.
Marshal.dump— ~40-55% throughput improvementsuperindirection orinstruction[:kind]rewritesConfig.development_mode?bug — referenced non-existent constantdocs/Changes
1. Stop mutating instruction hashes + remove
Marshal.dump(c034edb)Five executors (
Success,Validate,FinishIf,Operation,Operations) previously rewroteinstruction[:kind] = :stepand calledsuperto delegate to theStepexecutor. This mutation forced aMarshal.load(Marshal.dump(instructions))deep copy of the entire instruction tree on every operation call.Fix: Extracted
execute_stepas a shared primitive on the baseExecutorclass. All executors now call it directly to invoke a step method, instrument it, and record execution — without touching the instruction hash. TheMarshal.dumpandwhile/shiftloop are replaced with a simpleeach/break.2. Make Within executor explicit (
12a44d2)Withinwas the last executor still usingsuperto delegate toExecutor#call. While it didn't mutate instruction hashes, it was inconsistent with the rest of the codebase after the refactor. Now callsevaluate_instructions(nested_instructions)directly — same behaviour, no indirection.After this change, no executor uses
super— each one calls exactly the primitive it needs (execute_steporevaluate_instructions).3. Fix
Config.development_mode?(d62a7af)The class method referenced
DEFAULT_MODEwhich does not exist — calling it would raiseNameError. Fixed to referenceDEVELOPMENT_MODE.4. Remove
custom_reporterRails coupling (dd48ecb)Config#custom_reporterimplicitly readRails.application.config.x.reporter.presenceat initialization, silently overriding any explicitly configured reporter. Removed — the reporter is now set exclusively via the config block.BREAKING: Apps relying on
config.x.reporterauto-detection (e.g., HAL'stest.rb) must addconfig.reporter = ...to their Opera initializer when upgrading.5. Restructure README (
113f20b)#inspectoutputdocs/examples/(7 files, 962 lines)All existing examples preserved verbatim — nothing lost, just reorganized.
6. Add benchmark script (
e6363b1,faa02cd)benchmarks/operation_benchmark.rbexercises the full execution path:withininsidetransactionoperations(plural) with multiple inner opsEach runs 1,000 iterations. ComplexOperation spawns ~7,000 total operation instances per run.
Performance Report
Environment: Ruby 3.3.4 (arm64-darwin24), Apple Silicon
Method: 1,000 iterations × 3 runs per branch, median real time
Mode:
production(no execution traces, matching real deployments)Times are wall-clock for 1,000 calls.
Why
WithinOperationshows the largest improvement (54.4%)withinproduces the deepest instruction tree nesting — awithinblock inside atransactionblock creates three levels of nested instruction arrays. On master,Marshal.dumpwas called at every level ofevaluate_instructions, so deeper nesting meant more serialization overhead. With the refactor, there is no copying at all — instructions are iterated directly. The deeper the nesting, the bigger the win.This is particularly relevant because
withinis commonly used insidetransactionblocks in production (e.g., wrapping steps withActiveRecord::Base.connected_tofor replica reads inside a write transaction).How the benchmark was run
Raw results
master — 3 runs
feature/opera-improvements — 3 runs
The benchmark script is included in this PR at
benchmarks/operation_benchmark.rbso these numbers can be reproduced and tracked over time.Test Results
All 100 existing specs pass on every commit — no test changes required.