Project

General

Profile

Feature #22128

Updated by byroot (Jean Boussier) 3 months ago

### Context 

 I'm trying to experiment with adapting Active Record for a Ractor architecture. 

 Since database connections can't possibly be Ractor shareable, the idea is to warp each connection inside its own ractor, and then send SQL queries and responses through a port. 

 But for this to perform well, I'd like to directly build the query response as a fully shareable object, so that it can be pushed into the port for free, instead of having Ruby need to recursively walk the potentially large response to mark objects as shareable. 

 Here's an example of how it would work in trilogy: https://github.com/trilogy-libraries/trilogy/pull/299 https://github.com/byroot/trilogy/commit/5f58200b398995d2fc0d4673a5a6bbccd0dd235d 

 ### Problem 

 Unfortunately, the necessary API isn't currently exposed in the C API: 

   - `RB_OBJ_SET_FROZEN_SHAREABLE` 
   - `RB_OBJ_SET_SHAREABLE` / `rb_obj_set_shareable` 

 I understand that this API could potentially be misused, but given it's a C API, I believe it's acceptable to require care from the caller. 

 ### Benchmark 

 ```ruby 
 # frozen_string_literal: true 

 require 'trilogy' 
 require 'benchmark/ips' 

 baseline = Trilogy.new(database: "test") 
 shareable = Trilogy.new(database: "test", shareable: true) 


 values = { 
   null_test: "test", 
   bit_test: "test", 
   single_bit_test: 1, 
   tiny_int_test: 2, 
   bool_cast_test: true, 
   small_int_test: 4, 
   medium_int_test: 23434, 
   int_test: 324234, 
   big_int_test: 234234, 
   unsigned_big_int_test: 23423423, 
   float_test: 234234, 
   float_zero_test: 213.23, 
   double_test: 23123.12323, 
   decimal_test: 123213.12312, 
   decimal_zero_test: 213213.21323, 
   date_test: "2026-01-30", 
   date_time_test: "2026-01-30 14:03:56", 
   date_time_with_precision_test: "2026-01-30 14:03:56.12", 
   time_with_precision_test: "2026-01-30 14:03:56.12", 
   timestamp_test: "2026-01-30 14:03:56.12", 
   varchar_test: "VARCHAR" 
 } 

 baseline.query("DELETE FROM trilogy_test") 
 insert = "INSERT INTO trilogy_test(#{values.keys.join(", ")}) VALUES (#{values.values.map(&:inspect).join(", ")})" 
 1000.times do 
   baseline.query(insert) 
 end 

 p shareable.query("SELECT * FROM test.trilogy_test").to_a.size 
 Benchmark.ips do |x| 
   x.report("baseline") { Ractor.make_shareable(baseline.query("SELECT * FROM trilogy_test")) } 
   x.report("shareable") { Ractor.make_shareable(shareable.query("SELECT * FROM trilogy_test")) } 
   x.compare!(order: :baseline) 
 end 
 ``` 

 ``` 
 ruby 4.1.0dev (2026-06-24T13:17:28Z expose-ractor-set-.. f9d7dd50cd) +PRISM [arm64-darwin25] 
 Warming up -------------------------------------- 
             baseline      42.000 i/100ms 
            shareable      66.000 i/100ms 
 Calculating ------------------------------------- 
             baseline      220.024 (±43.2%) i/s      (4.54 ms/i) -        1.134k in     5.153976s 
            shareable      677.487 (± 2.4%) i/s      (1.48 ms/i) -        3.432k in     5.065782s 

 Comparison: 
  baseline:        220.0 i/s 
 shareable:        677.5 i/s - 3.08x    faster 
 ```

Back