rubydb 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +296 -227
  6. data/Rakefile +6 -1
  7. data/accelerator/bin/SHA256SUMS +6 -0
  8. data/accelerator/bin/rubydb-accelerator-darwin-amd64 +0 -0
  9. data/accelerator/bin/rubydb-accelerator-darwin-arm64 +0 -0
  10. data/accelerator/bin/rubydb-accelerator-linux-amd64 +0 -0
  11. data/accelerator/bin/rubydb-accelerator-linux-arm64 +0 -0
  12. data/accelerator/bin/rubydb-accelerator-windows-amd64.exe +0 -0
  13. data/accelerator/bin/rubydb-accelerator-windows-arm64.exe +0 -0
  14. data/accelerator/cmd/rubydb-accelerator/main.go +11 -0
  15. data/accelerator/go.mod +3 -0
  16. data/accelerator/internal/execution/aggregate.go +94 -0
  17. data/accelerator/internal/execution/distinct.go +22 -0
  18. data/accelerator/internal/execution/filter.go +73 -0
  19. data/accelerator/internal/execution/join.go +79 -0
  20. data/accelerator/internal/execution/operators.go +167 -0
  21. data/accelerator/internal/execution/scan.go +20 -0
  22. data/accelerator/internal/execution/sort.go +62 -0
  23. data/accelerator/internal/execution/types.go +136 -0
  24. data/accelerator/internal/execution/value.go +67 -0
  25. data/accelerator/internal/memory/arena.go +47 -0
  26. data/accelerator/internal/memory/reuse.go +22 -0
  27. data/accelerator/internal/metrics/registry.go +67 -0
  28. data/accelerator/internal/parallel/bounded_queue.go +56 -0
  29. data/accelerator/internal/parallel/scheduler.go +47 -0
  30. data/accelerator/internal/parallel/worker_pool.go +53 -0
  31. data/accelerator/internal/protocol/cancellation.go +48 -0
  32. data/accelerator/internal/protocol/columnar.go +263 -0
  33. data/accelerator/internal/protocol/frame.go +187 -0
  34. data/accelerator/internal/runtime/worker.go +521 -0
  35. data/accelerator/internal/storage/page_reader.go +81 -0
  36. data/accelerator/internal/storage/snapshot_scan.go +539 -0
  37. data/accelerator/internal/wal/checksum.go +13 -0
  38. data/accelerator/internal/wal/compression.go +41 -0
  39. data/accelerator/internal/wal/group_commit.go +24 -0
  40. data/accelerator/internal/wal/record_encoder.go +40 -0
  41. data/adapters/activerecord/README.md +8 -3
  42. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +50 -42
  43. data/adapters/activerecord/rubydb-activerecord.gemspec +1 -1
  44. data/docs/README.md +3 -1
  45. data/docs/architecture/go-accelerator.md +179 -0
  46. data/docs/cli.md +24 -0
  47. data/docs/contributing/benchmarking.md +16 -0
  48. data/docs/developer/local-development.md +32 -0
  49. data/docs/release.md +2 -2
  50. data/lessons/02-local-development.md +2 -2
  51. data/lessons/04-rails-complex-apps.md +2 -2
  52. data/lessons/05-rubydb-production-server.md +2 -2
  53. data/lessons/07-hybrid-microservices.md +175 -90
  54. data/lessons/10-release-readiness.md +184 -117
  55. data/lessons/11-community-adapter.md +323 -0
  56. data/lessons/12-rails-ecommerce-pressure.md +263 -0
  57. data/lib/rubydb/accelerator/client.rb +451 -0
  58. data/lib/rubydb/accelerator/error.rb +22 -0
  59. data/lib/rubydb/accelerator/manager.rb +606 -0
  60. data/lib/rubydb/accelerator.rb +13 -0
  61. data/lib/rubydb/cli/application.rb +6 -1
  62. data/lib/rubydb/cli/commands/accelerator.rb +72 -0
  63. data/lib/rubydb/cli/commands/doctor.rb +3 -0
  64. data/lib/rubydb/client/client.rb +7 -0
  65. data/lib/rubydb/client/connection.rb +15 -0
  66. data/lib/rubydb/client/result.rb +5 -1
  67. data/lib/rubydb/configuration/defaults.rb +12 -0
  68. data/lib/rubydb/configuration/validation.rb +8 -1
  69. data/lib/rubydb/execution/accelerator_dispatch.rb +30 -0
  70. data/lib/rubydb/execution/cost_model.rb +72 -0
  71. data/lib/rubydb/execution/executor.rb +373 -11
  72. data/lib/rubydb/execution/operator_selection.rb +57 -0
  73. data/lib/rubydb/execution/physical_plan.rb +47 -0
  74. data/lib/rubydb/execution/planner.rb +12 -46
  75. data/lib/rubydb/execution/sort_executor.rb +22 -8
  76. data/lib/rubydb/indexes/btree.rb +31 -2
  77. data/lib/rubydb/rubydb.rb +7 -1
  78. data/lib/rubydb/server/session.rb +45 -0
  79. data/lib/rubydb/storage/engine.rb +74 -12
  80. data/lib/rubydb/storage/snapshot_reader.rb +167 -0
  81. data/lib/rubydb/version.rb +1 -1
  82. data/lib/rubydb/wal/archive.rb +17 -0
  83. data/lib/rubydb/wal/wal.rb +1 -0
  84. data/rubydb.gemspec +12 -2
  85. data/scripts/build_accelerator +49 -0
  86. data/scripts/release +34 -4
  87. data/scripts/replication_failover_drill +2 -2
  88. metadata +49 -1
@@ -0,0 +1,323 @@
1
+ # Lesson 11 — Build a RubyDB adapter for your community
2
+
3
+ This lesson is for a developer who wants to make RubyDB available to another
4
+ language or framework community. The goal is a real, supportable adapter that
5
+ can be released independently—not a thin helper that concatenates SQL.
6
+
7
+ RubyDB adapters connect to a running RubyDB server. They do not open `.rdb`
8
+ files directly. One Ruby process owns an embedded database path; a Python,
9
+ Node.js, Go, Java, Rust, or framework application should connect to RubyDB in
10
+ server mode.
11
+
12
+ ## 1. Choose the community and the support boundary
13
+
14
+ Write this down before coding:
15
+
16
+ ```text
17
+ Adapter: rubydb-example
18
+ Language: Example 1.0+
19
+ Framework: Example Framework 4.x+
20
+ RubyDB: 0.1.x
21
+ Transport: rubydb:// for development, rubydbs:// for production
22
+ API: query, parameters, transactions, prepared statements, pooling
23
+ ```
24
+
25
+ Start with a narrow, honest support matrix. A language driver, an ORM adapter,
26
+ and a migration tool have different responsibilities:
27
+
28
+ - A language driver owns connections, parameters, results, errors, deadlines,
29
+ cancellation, transactions, TLS, and resource cleanup.
30
+ - An ORM adapter maps the ORM's query, type, transaction, schema, and pooling
31
+ APIs to the driver. It must not claim that RubyDB supports another database's
32
+ dialect just because the ORM has a familiar adapter name.
33
+ - A framework integration owns configuration, lifecycle hooks, health checks,
34
+ logging, and deployment conventions for that framework.
35
+
36
+ Do not promise PostgreSQL, MySQL, or SQLite compatibility unless the exact
37
+ syntax and behavior has been implemented and tested. Link users to RubyDB's
38
+ [SQL compatibility guide](../docs/sql/compatibility-guide.md) and state which
39
+ features your adapter supports.
40
+
41
+ ## 2. Study the reference implementations
42
+
43
+ Use the repository's adapters as working references:
44
+
45
+ - [`adapters/python`](../adapters/python) demonstrates a DB-API 2.0 client.
46
+ - [`adapters/rubydb`](../adapters/rubydb) demonstrates a TypeScript client,
47
+ promises, TLS, pooling, prepared statements, and timeout cancellation.
48
+ - [`adapters/activerecord`](../adapters/activerecord) demonstrates a Ruby ORM
49
+ integration and Rails schema behavior.
50
+
51
+ Read the [wire protocol guide](../docs/server/protocol.md),
52
+ [`spec/wire/protocol.md`](../spec/wire/protocol.md), and
53
+ [`spec/protocol/protocol.md`](../spec/protocol/protocol.md) together. The
54
+ implementation and executable tests are authoritative when a draft document
55
+ does not describe a detail.
56
+
57
+ ## 3. Create a maintainable package
58
+
59
+ Keep the adapter in its own repository or in `adapters/<community>` while it is
60
+ being developed. A useful layout is:
61
+
62
+ ```text
63
+ rubydb-example/
64
+ ├── README.md
65
+ ├── LICENSE
66
+ ├── CHANGELOG.md
67
+ ├── CONTRIBUTING.md
68
+ ├── SECURITY.md
69
+ ├── package-or-project-manifest
70
+ ├── src/
71
+ │ ├── connection
72
+ │ ├── protocol
73
+ │ ├── errors
74
+ │ ├── types
75
+ │ └── pool
76
+ ├── tests/
77
+ │ ├── unit/
78
+ │ ├── protocol/
79
+ │ ├── integration/
80
+ │ └── security/
81
+ └── examples/
82
+ └── basic_app/
83
+ ```
84
+
85
+ Keep public API types separate from socket and JSON code. This makes it
86
+ possible to replace the transport or add a framework integration without
87
+ making application code depend on internal protocol objects.
88
+
89
+ Use a package name that is valid for the target registry and clearly belongs
90
+ to your maintainers. For npm, new package names must be lowercase; a scoped
91
+ package therefore looks like `@your-scope/rubydb`, not `@YourScope/rubydb`.
92
+ See npm's [package naming guidance](https://docs.npmjs.com/creating-a-package-json-file)
93
+ before reserving a name. Never use `rubydb` alone for an unofficial package.
94
+
95
+ ## 4. Implement the protocol boundary
96
+
97
+ RubyDB uses bounded, newline-delimited JSON messages for its client/server
98
+ transport. Each message has an envelope with a `type`, an identifier, a
99
+ creation timestamp, and a payload. Keep the following invariants in the driver:
100
+
101
+ 1. Parse the RubyDB URL and reject unknown schemes. Support `rubydb://` for a
102
+ trusted private network and `rubydbs://` for TLS.
103
+ 2. Open one TCP or TLS connection and enable peer verification by default for
104
+ production TLS connections. Allow CA, client certificate, and key settings
105
+ through configuration or a secret manager, never through committed files.
106
+ 3. Apply a maximum frame size before allocating unbounded memory. Reject an
107
+ oversized request or response and close the connection safely.
108
+ 4. Send a handshake with the protocol version, client name, client version,
109
+ username, and database. Follow it with authentication and synchronization.
110
+ Fail closed when the server rejects any step.
111
+ 5. Give every request a unique ID and correlate responses by ID. Do not assume
112
+ that response order will remain safe if multiplexing or asynchronous
113
+ notifications are added later.
114
+ 6. Implement the supported operations: `query`, `prepare`, `execute`, `close`,
115
+ `begin`, `commit`, `rollback`, `ping`, and `terminate`.
116
+ 7. Keep parameter values separate from SQL. Encode supported scalar, array,
117
+ object, date/time, and binary values according to the adapter's documented
118
+ mapping. Never interpolate user input into a query string.
119
+ 8. Normalize result fields into the community's idioms while preserving column
120
+ metadata, rows, row count, affected rows, and insert identifiers.
121
+ 9. Map server error codes into stable public exception types. Include a safe
122
+ message and code, but do not expose passwords, TLS keys, or raw secrets in
123
+ logs.
124
+
125
+ A driver request should conceptually look like this; use the target language's
126
+ JSON and socket APIs rather than copying this pseudocode literally:
127
+
128
+ ```text
129
+ request_id = new_unique_id()
130
+ send {
131
+ type: "query",
132
+ id: request_id,
133
+ created_at: now_as_iso8601,
134
+ payload: {
135
+ sql: "SELECT id, name FROM users WHERE active = ?",
136
+ params: [true],
137
+ deadline_at: deadline_as_iso8601
138
+ }
139
+ }
140
+ response = read_and_match_id(request_id)
141
+ return normalize_result(response.payload.result || response.payload.data)
142
+ ```
143
+
144
+ The exact wire behavior belongs in protocol tests, not in assumptions hidden in
145
+ the adapter. If you need a protocol capability that RubyDB does not advertise,
146
+ open a design issue before inventing a private message type.
147
+
148
+ ## 5. Make transaction behavior explicit
149
+
150
+ Expose explicit `begin`, `commit`, and `rollback` operations. If your
151
+ community API has implicit transactions, document exactly when they begin and
152
+ how a connection returns to an idle state.
153
+
154
+ ```text
155
+ connection.begin()
156
+ try:
157
+ connection.execute(
158
+ "INSERT INTO events (name) VALUES (?)",
159
+ ["community-adapter.started"]
160
+ )
161
+ connection.commit()
162
+ except:
163
+ connection.rollback()
164
+ raise
165
+ ```
166
+
167
+ A connection must not be returned to a pool while it has an open transaction,
168
+ an active cursor, or an unclosed prepared statement. On network loss, mark the
169
+ connection unusable and roll back locally; do not silently reuse it.
170
+
171
+ ## 6. Implement deadlines and cancellation safely
172
+
173
+ Every potentially long operation needs a bounded timeout. On timeout, send a
174
+ wire `cancel` request containing the timed-out request's ID, then drain or
175
+ close the connection according to the response. Cancellation is cooperative;
176
+ it is not permission to kill a thread while it owns database state.
177
+
178
+ Never automatically retry a write just because the client timed out. The write
179
+ may have committed before the response was lost. Tell users to use an
180
+ idempotency key or application-level deduplication for retryable writes.
181
+
182
+ Test all of these cases:
183
+
184
+ - timeout before the server starts execution;
185
+ - cancellation during a long-running query;
186
+ - a cancellation response for an unknown request;
187
+ - connection loss while cancellation is being sent; and
188
+ - a late response arriving after the caller has timed out.
189
+
190
+ ## 7. Add pooling without hiding failures
191
+
192
+ Provide a bounded pool only if the target community expects one. The pool must
193
+ have a maximum size, acquisition timeout, idle cleanup, connection validation,
194
+ and deterministic shutdown. A checkout/return API should make ownership clear:
195
+
196
+ ```text
197
+ pool = Pool(url, min_size=1, max_size=8)
198
+ try:
199
+ rows = pool.use(lambda db:
200
+ db.query("SELECT id FROM jobs WHERE state = ?", ["ready"]).rows
201
+ )
202
+ finally:
203
+ pool.close()
204
+ ```
205
+
206
+ Do not create one unbounded connection per request. Do not share one connection
207
+ between concurrent operations unless the API and protocol explicitly support
208
+ that behavior. Pool limits should be lower than the server's connection and
209
+ resource limits, with headroom for health checks and migrations.
210
+
211
+ ## 8. Test the adapter against a real server
212
+
213
+ A protocol fixture is useful for fast unit tests, but it cannot prove that the
214
+ adapter works. Add a live integration job that starts a pinned RubyDB server
215
+ and runs the adapter against a temporary database.
216
+
217
+ Minimum test groups:
218
+
219
+ - URL parsing, defaults, TLS options, type conversion, and public errors;
220
+ - fragmented frames, multiple frames, blank lines, malformed JSON, and
221
+ oversized frames;
222
+ - handshake, authentication failure, authorization failure, ping, and close;
223
+ - parameterized CRUD, `NULL`, booleans, numbers, dates, text, arrays, and JSON;
224
+ - prepared statements and statement cleanup;
225
+ - commit, rollback, transaction isolation expectations, and pool reuse;
226
+ - deadline, wire cancellation, late responses, and connection loss;
227
+ - concurrent operations up to the documented pool limit;
228
+ - server restart, backup/restore validation, and version mismatch behavior; and
229
+ - secrets absent from exceptions, logs, test output, and published artifacts.
230
+
231
+ Run the RubyDB repository checks first:
232
+
233
+ ```powershell
234
+ bundle install
235
+ bundle exec rspec
236
+ ```
237
+
238
+ Then run the adapter's fast and live suites. The exact commands depend on the
239
+ language, but the live suite should receive a URL from the environment rather
240
+ than hard-code credentials:
241
+
242
+ ```powershell
243
+ $env:RUBYDB_URL = "rubydb://rubydb@127.0.0.1:7432/rubydb"
244
+ your-package-test-command
245
+ your-package-live-integration-command
246
+ ```
247
+
248
+ Repeat the live suite over `rubydbs://` with a test CA. Add property or fuzz
249
+ tests for the frame decoder and parameter encoder. A driver that passes only a
250
+ mock server test is not ready for a community release.
251
+
252
+ ## 9. Document production usage
253
+
254
+ Your README should include copy-and-paste examples for:
255
+
256
+ 1. local development with an isolated database;
257
+ 2. starting RubyDB server mode;
258
+ 3. setting `RUBYDB_URL` through the platform's secret store;
259
+ 4. TLS with peer verification and certificate rotation;
260
+ 5. pool sizing and request timeouts;
261
+ 6. migrations and backup/restore procedures;
262
+ 7. health checks and metrics; and
263
+ 8. unsupported SQL, RubyDB versions, operating systems, and framework versions.
264
+
265
+ Show users the production shape:
266
+
267
+ ```text
268
+ Application processes ──TLS/private network──> RubyDB server
269
+ secrets from manager durable storage + backups
270
+ ```
271
+
272
+ The adapter is not the database server, a backup system, or a failover
273
+ controller. Link to RubyDB's [production operations guide](../docs/operations/production-guide.md)
274
+ and require users to validate their own workload before making availability or
275
+ durability claims.
276
+
277
+ ## 10. Release and maintain the community package
278
+
279
+ Before the first release:
280
+
281
+ - choose a license and add a changelog;
282
+ - publish a support matrix and compatibility policy;
283
+ - enable CI on every supported language/runtime version;
284
+ - run unit, live, security, fuzz, and package-content checks;
285
+ - verify the package contains no `.env`, credentials, private keys, databases,
286
+ build caches, or test secrets;
287
+ - use trusted publishing or a short-lived registry token in CI;
288
+ - sign releases when the target registry supports signing;
289
+ - tag the source commit and record the RubyDB protocol/server version; and
290
+ - provide a security contact and a responsible disclosure policy.
291
+
292
+ For an npm package, inspect the tarball before publishing:
293
+
294
+ ```sh
295
+ npm test
296
+ npm pack --dry-run
297
+ npm publish --access public
298
+ ```
299
+
300
+ For Python, Ruby, Rust, or another registry, use that ecosystem's equivalent
301
+ build, metadata, signature, and upload checks. Release the adapter separately
302
+ from RubyDB and pin compatible versions in the package metadata. After release,
303
+ install the package in a clean environment and rerun the live smoke test.
304
+
305
+ ## Community contribution checklist
306
+
307
+ Open a pull request or design issue with:
308
+
309
+ ```text
310
+ [ ] Adapter name, owner, license, and supported versions are listed.
311
+ [ ] Server-mode boundary and embedded-mode limitation are documented.
312
+ [ ] Parameter binding is used for every user value.
313
+ [ ] Frame limits, malformed input, TLS verification, and secret handling exist.
314
+ [ ] Transactions, timeouts, cancellation, and connection cleanup are tested.
315
+ [ ] Live tests pass against a pinned RubyDB server.
316
+ [ ] Package contents and release provenance are checked.
317
+ [ ] README includes installation, examples, operations, and limitations.
318
+ ```
319
+
320
+ The adapter becomes part of the wider RubyDB ecosystem when users can install
321
+ it, understand its limits, run a real query, observe failures, and upgrade it
322
+ without guessing. That standard protects both RubyDB users and the community
323
+ maintainer.
@@ -0,0 +1,263 @@
1
+ # Lesson 12: Build and pressure-test a Rails shop with RubyDB
2
+
3
+ This lesson uses the runnable application in
4
+ [`examples/rails_ecommerce`](../examples/rails_ecommerce/). It is intentionally
5
+ small enough to understand, but it exercises the database paths that usually
6
+ matter in a commerce service:
7
+
8
+ - catalog filters, ordering, limits, and compound indexes;
9
+ - grouped order summaries;
10
+ - customer/order/item associations and eager loading;
11
+ - a transaction that creates an order and updates inventory;
12
+ - direct ActiveRecord pressure and HTTP request pressure.
13
+
14
+ The example is a validation tool. A successful local run proves that this
15
+ specific application and workload work together; it is not a capacity promise
16
+ for every Rails application or deployment.
17
+
18
+ ## 1. Copy the example and install it
19
+
20
+ From a checkout of RubyDB:
21
+
22
+ ```sh
23
+ cd examples/rails_ecommerce
24
+ bundle install
25
+ ```
26
+
27
+ The example uses local paths to the RubyDB engine and ActiveRecord adapter, so
28
+ you can test the code currently checked out without publishing a new gem.
29
+ When using released gems in your own application, use pinned versions instead:
30
+
31
+ ```ruby
32
+ gem "rubydb", "0.1.6"
33
+ gem "rubydb-activerecord", "0.1.3"
34
+ ```
35
+
36
+ ## 2. Create and seed the local database
37
+
38
+ Embedded mode is the easiest local development setup. RubyDB creates the file
39
+ under `examples/rails_ecommerce/tmp/` and Rails talks to it through the real
40
+ ActiveRecord adapter. The example pins embedded Puma and the ActiveRecord pool
41
+ to one thread/connection because one embedded RubyDB path has one process-local
42
+ owner:
43
+
44
+ ```sh
45
+ bundle exec rails db:prepare
46
+ RUBYDB_PRODUCTS=500 RUBYDB_CUSTOMERS=100 RUBYDB_ORDERS=1000 bundle exec rails db:seed
47
+ bundle exec ruby script/smoke.rb
48
+ ```
49
+
50
+ PowerShell:
51
+
52
+ ```powershell
53
+ $env:RUBYDB_PRODUCTS = "500"
54
+ $env:RUBYDB_CUSTOMERS = "100"
55
+ $env:RUBYDB_ORDERS = "1000"
56
+ bundle exec rails db:prepare
57
+ bundle exec rails db:seed
58
+ bundle exec ruby script/smoke.rb
59
+ ```
60
+
61
+ The seed is deterministic. Change the three environment variables to create a
62
+ larger fixture without changing the application:
63
+
64
+ ```sh
65
+ RUBYDB_PRODUCTS=10000 RUBYDB_CUSTOMERS=2000 RUBYDB_ORDERS=25000 bundle exec rails db:seed
66
+ ```
67
+
68
+ ## 3. Understand the Rails queries
69
+
70
+ The catalog action uses a filtered and ordered relation:
71
+
72
+ ```ruby
73
+ Product.active
74
+ .in_category(params[:category])
75
+ .order(price_cents: :asc)
76
+ .limit(50)
77
+ ```
78
+
79
+ It also executes a grouped aggregate for the category navigation:
80
+
81
+ ```ruby
82
+ Product.active.group(:category).count
83
+ ```
84
+
85
+ The smoke test exercises eager loading and a grouped order query:
86
+
87
+ ```ruby
88
+ Order.completed.group(:status).count
89
+ Order.includes(:customer, :order_items).order(id: :desc).first
90
+ ```
91
+
92
+ The order action keeps the business write atomic:
93
+
94
+ ```ruby
95
+ Order.transaction do
96
+ order = customer.orders.create!(status: "paid", total_cents: total)
97
+ order.order_items.create!(product: product, quantity: quantity, unit_price_cents: price)
98
+ product.update!(stock: product.stock - quantity)
99
+ end
100
+ ```
101
+
102
+ In a real store, add an explicit inventory reservation strategy, idempotency
103
+ keys, payment authorization boundaries, audit records, and a concurrency test
104
+ for overselling. The small example keeps the business flow visible for
105
+ learning.
106
+
107
+ ## 4. Run direct database pressure
108
+
109
+ Run the database workload without HTTP overhead:
110
+
111
+ ```sh
112
+ RUBYDB_PRESSURE_OPERATIONS=1000 bundle exec ruby script/pressure.rb
113
+ ```
114
+
115
+ The output is JSON containing completed operations, errors, throughput, and
116
+ p50/p95/p99 latency. The workload randomly exercises catalog reads, a `LIKE`
117
+ filter, grouped order counts, and eager-loaded order history.
118
+
119
+ To add transaction writes:
120
+
121
+ ```sh
122
+ RUBYDB_PRESSURE_MODE=mixed \
123
+ RUBYDB_PRESSURE_WRITE_RATIO=0.10 \
124
+ RUBYDB_PRESSURE_OPERATIONS=1000 \
125
+ bundle exec ruby script/pressure.rb
126
+ ```
127
+
128
+ The script exits non-zero when any operation fails. Treat the first error as a
129
+ correctness issue to investigate, not as an acceptable benchmark result.
130
+
131
+ ## 5. Run the Rails app and HTTP pressure
132
+
133
+ Start the app:
134
+
135
+ ```sh
136
+ bundle exec rails server -b 127.0.0.1 -p 3002
137
+ ```
138
+
139
+ In a second terminal, send concurrent requests to the JSON catalog endpoint:
140
+
141
+ ```sh
142
+ RUBYDB_HTTP_THREADS=8 \
143
+ RUBYDB_HTTP_REQUESTS=500 \
144
+ bundle exec ruby script/http_pressure.rb
145
+ ```
146
+
147
+ PowerShell:
148
+
149
+ ```powershell
150
+ $env:RUBYDB_HTTP_THREADS = "8"
151
+ $env:RUBYDB_HTTP_REQUESTS = "500"
152
+ bundle exec ruby script/http_pressure.rb
153
+ ```
154
+
155
+ This measures Rails routing, controller work, serialization, and database
156
+ reads together. It does not replace a load test that runs from another host,
157
+ but it gives a reproducible local baseline.
158
+
159
+ ## 6. Test the multi-process topology
160
+
161
+ Embedded mode is for one process owning one database path. For concurrent web
162
+ traffic, web workers,
163
+ job workers, or more than one host, run a RubyDB server and connect over the
164
+ protocol. From the example directory, start the local server using the
165
+ repository executable:
166
+
167
+ ```sh
168
+ mkdir -p tmp/rubydb-commerce-server
169
+ bundle exec ruby ../../exe/rubydb start \
170
+ --host 127.0.0.1 \
171
+ --port 7432 \
172
+ --data-dir tmp/rubydb-commerce-server \
173
+ --log-dir tmp/rubydb-commerce-server/log
174
+ ```
175
+
176
+ In another terminal, migrate and seed through the server:
177
+
178
+ ```sh
179
+ RUBYDB_EMBEDDED=false \
180
+ RUBYDB_URL='rubydb://rubydb@127.0.0.1:7432/rubydb' \
181
+ bundle exec rails db:prepare
182
+
183
+ RUBYDB_EMBEDDED=false \
184
+ RUBYDB_URL='rubydb://rubydb@127.0.0.1:7432/rubydb' \
185
+ RUBYDB_PRODUCTS=1000 RUBYDB_CUSTOMERS=200 RUBYDB_ORDERS=5000 \
186
+ bundle exec rails db:seed
187
+ ```
188
+
189
+ Then run several Rails database workers against the server:
190
+
191
+ ```sh
192
+ RUBYDB_EMBEDDED=false \
193
+ RUBYDB_URL='rubydb://rubydb@127.0.0.1:7432/rubydb' \
194
+ RAILS_MAX_THREADS=8 \
195
+ RUBYDB_PRESSURE_THREADS=8 \
196
+ RUBYDB_PRESSURE_OPERATIONS=2000 \
197
+ bundle exec ruby script/pressure.rb
198
+ ```
199
+
200
+ For TLS, use a `rubydbs://` URL and configure certificate verification. In a
201
+ real deployment, inject the URL through a secret manager; do not commit a
202
+ password into `database.yml` or a benchmark script.
203
+
204
+ ## 7. Read the results correctly
205
+
206
+ Record the JSON output with the Git commit and fixture size. Compare:
207
+
208
+ 1. p50 for normal user requests;
209
+ 2. p95 and p99 for tail latency under pressure;
210
+ 3. throughput and error count;
211
+ 4. server CPU, memory, WAL growth, disk space, and rejected connections;
212
+ 5. results before and after enabling an accelerator policy.
213
+
214
+ Do not compare one tiny in-memory run with a production claim. Go acceleration
215
+ is adaptive: small queries can stay in Ruby when process/protocol overhead is
216
+ larger than the work, while eligible large immutable-snapshot operations can be
217
+ accelerated. Correct results, durable commits, and bounded resource use come
218
+ before a lower benchmark number.
219
+
220
+ ## 8. Run the accelerator A/B check
221
+
222
+ From the repository root, run the same 10,000-row workload with a Ruby-only
223
+ baseline and the required Go worker:
224
+
225
+ ```powershell
226
+ $env:RUBYDB_ACCELERATOR_ROWS = "10000"
227
+ $env:RUBYDB_ACCELERATOR_ITERATIONS = "5"
228
+ $env:RUBYDB_ACCELERATOR_THREADS = "4"
229
+ $env:RUBYDB_ACCELERATOR_REQUESTS = "5"
230
+ bundle exec ruby benchmarks/go_accelerator.rb
231
+ ```
232
+
233
+ The benchmark verifies the row result before and during measurement, performs
234
+ an explicit worker restart, and reports p50/p95/p99 latency, throughput, CPU,
235
+ RSS, concurrent-request errors, worker starts/restarts/failures, and a speed
236
+ gate. `performance_gate_passed` must be `true` for this exact workload and
237
+ machine before selecting `RUBYDB_ACCELERATOR=required`; otherwise leave the
238
+ policy at `auto` or `off`. A Go worker can be correct but slower when the
239
+ workload is dominated by copying Ruby objects through the process boundary.
240
+
241
+ For larger Rails pressure, use at least 10,000 products and 10,000 orders,
242
+ then run the direct and server-mode pressure commands above. Include the
243
+ result JSON, commit, Ruby/Rails versions, and host metrics in the performance
244
+ record.
245
+
246
+ ## 9. Production checklist for this app
247
+
248
+ Before using the pattern for a real service:
249
+
250
+ - pin Ruby, Rails, RubyDB, and adapter versions;
251
+ - use server mode for multiple processes and hosts;
252
+ - run migrations against a backup or restored staging copy first;
253
+ - configure TLS, authentication, connection limits, timeouts, and monitoring;
254
+ - test idempotent order creation and payment retries;
255
+ - test inventory contention and deadlock/timeout behavior;
256
+ - run backup, restore, restart, and disk-space drills;
257
+ - establish an application-specific p95/p99 SLO with representative data;
258
+ - keep PostgreSQL as the comparison target when the application needs its
259
+ broader SQL dialect, ecosystem, or large-scale operational guarantees.
260
+
261
+ This lesson makes RubyDB easy to try locally while keeping the boundary clear:
262
+ the benchmark measures the features the example actually uses, and production
263
+ readiness still requires validation of the exact application and topology.