solid_objects 0.5.1 → 0.5.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/benchmark/support.rb +2 -0
- data/docs/benchmarks.md +7 -0
- data/lib/solid_objects/database_adapter.rb +28 -5
- data/lib/solid_objects/database_adapters/sqlite.rb +5 -5
- data/lib/solid_objects/version.rb +1 -1
- data/sig/generated/lib/solid_objects/database_adapter.rbs +10 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ac17dc212792f2dfa71c01e701a9e4de27f54b6198ec6e64696b855ff82c4c8
|
|
4
|
+
data.tar.gz: 45bff1ddb2bbc0ff80ddfb55488fec0380d02c729e08a7f06b000b744dddb82d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffb590f2e07a0de4d4ba2ab5ac1cb7d76420b742d36d656b85ecdb0fc401dbabd92b75e47df73948f7e1d42fe4a289d2f16fa8d86086c8bff336ac4e33ae89dc
|
|
7
|
+
data.tar.gz: 5a43e24c8358e2352de0ca1428cdc10d15bdf679c62ca1983b420fa1e739d0c975004e0e33ffb64a96fe3d09cbbcd30181db6a95a678c071fd80f85b935811cd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.2 - 2026-08-09
|
|
4
|
+
|
|
5
|
+
- Read the database clock once per transaction instead of once per step, and
|
|
6
|
+
resolve the SQLite busy wait from configuration instead of querying the
|
|
7
|
+
connection for it. A synchronous call now issues 49 database queries instead
|
|
8
|
+
of 66, which matters most on PostgreSQL and MySQL where every query is a
|
|
9
|
+
network round trip.
|
|
10
|
+
- Apply every migration in the benchmark harness. It applied only the initial
|
|
11
|
+
migration, so the `state_revision` column added in 0.4.0 was missing, every
|
|
12
|
+
message failed at commit, and the synchronous benchmarks timed out.
|
|
13
|
+
|
|
3
14
|
## 0.5.1 - 2026-08-07
|
|
4
15
|
|
|
5
16
|
- Restore the SQLite busy wait that a synchronous invocation suspends for its
|
data/benchmark/support.rb
CHANGED
|
@@ -228,7 +228,9 @@ module SolidObjectsBenchmark
|
|
|
228
228
|
# @rbs () -> void
|
|
229
229
|
def migrate
|
|
230
230
|
require_relative "../db/migrate/20260805000000_create_solid_objects_tables"
|
|
231
|
+
require_relative "../db/migrate/20260806000000_add_state_revision_to_solid_objects_instances"
|
|
231
232
|
CreateSolidObjectsTables.new.migrate(:up)
|
|
233
|
+
AddStateRevisionToSolidObjectsInstances.new.migrate(:up)
|
|
232
234
|
end
|
|
233
235
|
|
|
234
236
|
# @rbs () -> void
|
data/docs/benchmarks.md
CHANGED
|
@@ -41,6 +41,13 @@ scenario used four worker threads.
|
|
|
41
41
|
| Synchronous latency | p50 1.8 ms, p95 25.6 ms, p99 156.2 ms |
|
|
42
42
|
| Activation reuse | 98.0%, four activations for 200 messages |
|
|
43
43
|
| Queries for one message turn | 29 |
|
|
44
|
+
| Queries for one synchronous call | 49 |
|
|
45
|
+
|
|
46
|
+
A synchronous call costs far more queries than a worker turn because the caller
|
|
47
|
+
also registers or heartbeats its caller process, claims the activation, and
|
|
48
|
+
observes the result. Query count, not query time, dominates synchronous latency
|
|
49
|
+
on a networked database: measured locally against SQLite, database time is
|
|
50
|
+
roughly 5% of a call and the remaining 95% is Ruby.
|
|
44
51
|
|
|
45
52
|
The difference between the SQLite development result and the MySQL adoption
|
|
46
53
|
result is why Solid Objects does not publish one latency promise. Network
|
|
@@ -4,6 +4,9 @@ require "time"
|
|
|
4
4
|
|
|
5
5
|
module SolidObjects
|
|
6
6
|
class DatabaseAdapter
|
|
7
|
+
TRANSACTION_CLOCK = :solid_objects_transaction_clock
|
|
8
|
+
TRANSACTION_CLOCK_SCOPE = :solid_objects_transaction_clock_scope
|
|
9
|
+
|
|
7
10
|
class << self
|
|
8
11
|
# @rbs (untyped) -> DatabaseAdapter
|
|
9
12
|
def for(connection)
|
|
@@ -46,10 +49,9 @@ module SolidObjects
|
|
|
46
49
|
|
|
47
50
|
# @rbs () -> Time
|
|
48
51
|
def database_now
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
value.is_a?(Time) ? value.utc : Time.parse("#{value} UTC").utc
|
|
52
|
+
return read_database_now unless ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE]
|
|
53
|
+
|
|
54
|
+
ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK] ||= read_database_now
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
# @rbs () { () -> untyped } -> untyped
|
|
@@ -70,7 +72,7 @@ module SolidObjects
|
|
|
70
72
|
with_transaction_deadline(connection) do
|
|
71
73
|
connection.transaction(requires_new: true) do
|
|
72
74
|
configure_transaction_deadline(connection)
|
|
73
|
-
block.call
|
|
75
|
+
with_transaction_clock { block.call }
|
|
74
76
|
end
|
|
75
77
|
end
|
|
76
78
|
end
|
|
@@ -91,6 +93,27 @@ module SolidObjects
|
|
|
91
93
|
|
|
92
94
|
attr_reader :connection_pool, :fixed_connection
|
|
93
95
|
|
|
96
|
+
# @rbs () { () -> untyped } -> untyped
|
|
97
|
+
def with_transaction_clock
|
|
98
|
+
return yield if ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE]
|
|
99
|
+
|
|
100
|
+
ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE] = true
|
|
101
|
+
begin
|
|
102
|
+
yield
|
|
103
|
+
ensure
|
|
104
|
+
ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK_SCOPE] = false
|
|
105
|
+
ActiveSupport::IsolatedExecutionState[TRANSACTION_CLOCK] = nil
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @rbs () -> Time
|
|
110
|
+
def read_database_now
|
|
111
|
+
value = with_connection do |connection|
|
|
112
|
+
connection.select_value("SELECT #{current_time_expression}")
|
|
113
|
+
end
|
|
114
|
+
value.is_a?(Time) ? value.utc : Time.parse("#{value} UTC").utc
|
|
115
|
+
end
|
|
116
|
+
|
|
94
117
|
# @rbs (untyped) { () -> untyped } -> untyped
|
|
95
118
|
def with_transaction_deadline(_connection)
|
|
96
119
|
yield
|
|
@@ -80,13 +80,13 @@ module SolidObjects
|
|
|
80
80
|
|
|
81
81
|
# @rbs (untyped) -> Hash[Symbol, untyped]?
|
|
82
82
|
def restorable_busy_wait(connection)
|
|
83
|
-
pragma_timeout = connection.select_value("PRAGMA busy_timeout").to_i
|
|
84
|
-
return { pragma_timeout: } if pragma_timeout.positive?
|
|
85
|
-
|
|
86
83
|
handler_timeout = configured_busy_handler_timeout(connection)
|
|
87
|
-
return
|
|
84
|
+
return { handler_timeout: } if handler_timeout
|
|
85
|
+
|
|
86
|
+
pragma_timeout = connection.select_value("PRAGMA busy_timeout").to_i
|
|
87
|
+
return nil unless pragma_timeout.positive?
|
|
88
88
|
|
|
89
|
-
{ pragma_timeout
|
|
89
|
+
{ pragma_timeout: }
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
# @rbs (untyped, Hash[Symbol, untyped]) -> void
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidObjects
|
|
4
4
|
class DatabaseAdapter
|
|
5
|
+
TRANSACTION_CLOCK: ::Symbol
|
|
6
|
+
|
|
7
|
+
TRANSACTION_CLOCK_SCOPE: ::Symbol
|
|
8
|
+
|
|
5
9
|
# @rbs (untyped) -> DatabaseAdapter
|
|
6
10
|
def self.for: (untyped) -> DatabaseAdapter
|
|
7
11
|
|
|
@@ -42,6 +46,12 @@ module SolidObjects
|
|
|
42
46
|
|
|
43
47
|
attr_reader fixed_connection: untyped
|
|
44
48
|
|
|
49
|
+
# @rbs () { () -> untyped } -> untyped
|
|
50
|
+
def with_transaction_clock: () { () -> untyped } -> untyped
|
|
51
|
+
|
|
52
|
+
# @rbs () -> Time
|
|
53
|
+
def read_database_now: () -> Time
|
|
54
|
+
|
|
45
55
|
# @rbs (untyped) { () -> untyped } -> untyped
|
|
46
56
|
def with_transaction_deadline: (untyped) { () -> untyped } -> untyped
|
|
47
57
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_objects
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lucas Carlson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actioncable
|