Repository navigation
Add a blog benchmark that writes - #575
Draft
thomasklemm wants to merge 1 commit into
Draft
thomasklemm wants to merge 1 commit into
thomasklemm wants to merge 1 commit into
Conversation
The Rails benchmarks here mostly read. In their timed iterations none of them submits a form, fails a validation, or runs after-commit callbacks and broadcasts; lobsters' only writes are its read-ribbon bookkeeping. Real apps spend much of their time saving records: validations, callbacks, transactions, INSERT/UPDATE/DELETE and enqueuing jobs. The app is Sam Ruby's create-blog from roundhouse-rb/roundhouse (MIT): rails new --css tailwind on Rails 8.1.4.1, with Article and Comment scaffolds, validations and Turbo broadcasts. It keeps the production defaults of rails new: SQLite, Solid Cache, Solid Queue, Solid Cable, Propshaft and importmap, no external services. The built Tailwind CSS is checked in, so no CSS build runs. benchmarks/blog/README.md lists the few changes to the generated tree. Each iteration runs 200 seeded visits in-process, like railsbench: reads (index, show, JSON) and writes (create, update and delete articles, add and delete comments, a create that fails validation with 422). Writes submit the CSRF token from the page's form, as a browser does, and every response's status is checked, along with a body marker or the redirect target. The iteration runs in one transaction per database that is rolled back at the end, as in Rails tests, so every iteration sees the same rows and IDs. Rails 8.1 opens that transaction in pin_connection!; Rails main no longer does (rails/rails 9e8fe3955b), so benchmark.rb opens it itself when none is open, and works on both. Time.now starts at a fixed date and advances one second per visit, so the work doesn't depend on the date and updates still write updated_at. Bootsnap is disabled, so Ruby builds that share a RUBY_DESCRIPTION don't load each other's cached bytecode. Saves run as savepoints inside that transaction, and their after-commit callbacks run when the savepoint is released, as in Rails tests. Solid Cable inserts each broadcast in the request rather than on its batching writer thread, so it lands in the transaction; the later broadcasts are enqueued into Solid Queue. Since nothing commits, a real COMMIT is not measured.
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.
The Rails benchmarks here mostly read. In their timed iterations none of them submits a form, fails a validation, or runs after-commit callbacks, while real apps spend much of their time saving records.
This adds
blog, Sam Ruby's create-blog (MIT): arails new --css tailwindapp on Rails 8.1.4.1 with Article and Comment scaffolds, validations and Turbo broadcasts, on the default SQLite, Solid Cache, Queue and Cable stack with no external services. Each iteration makes about 420 in-process requests: reads, creates, updates, deletes, comments and a create that fails with 422, with real CSRF tokens and every response checked. Each iteration runs in one transaction per database that is rolled back at the end, as in Rails tests, with a fixed clock.What it finds and what it doesn't is below. Would a write workload like this be welcome? Happy to reshape it.
What it finds, and what it doesn't
It catches changes the other Rails benchmarks never run. A small busy loop injected into
saveanddestroyadds 2.3% to blog's instructions per iteration, and that code doesn't run at all in railsbench's or lobsters' timed iterations. Its per-request split also shows where a Rails change lands: from Rails 8.1.4.1 to main, reads take 9–14% fewer instructions while creates stay flat. It overlaps with railsbench and lobsters on per-query and database-driver overhead, and in our A/B runs it didn't separate those changes better. Its total alone says little, since about two thirds of it is reads much like railsbench's. The measurements are in this record.Details
benchmark.rbpins each database's connection and opens a non-joinable transaction. Rails 8.1 opens it inpin_connection!; Rails main no longer does, so the benchmark opens it itself when none is open. Saves run as savepoints, and commit callbacks run when each savepoint is released, as in Rails tests. Each iteration checks the Solid Cable messages and Solid Queue jobs it wrote.Time.nowstarts at a fixed date and advances one second per visit, so the work doesn't depend on the date and updates still writeupdated_at.Allocations per iteration varied by less than 0.1% after two warmups, with no growth over 28 iterations. Times are Ruby master with
./run_benchmarks.rb --interleaveon one 4-vCPU VM, so they are rough.