solid_objects 0.14.3 → 0.14.4
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 +20 -0
- data/docs/benchmarks.md +16 -0
- data/lib/solid_objects/executor.rb +15 -10
- data/lib/solid_objects/serialization.rb +8 -1
- data/lib/solid_objects/state.rb +5 -0
- data/lib/solid_objects/version.rb +1 -1
- data/sig/generated/lib/solid_objects/executor.rbs +5 -2
- data/sig/generated/lib/solid_objects/serialization.rbs +3 -0
- data/sig/generated/lib/solid_objects/state.rbs +3 -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: 32207ee5017c24fa87258061048c532532bcf599d46357ce8da4deef8cb0c93e
|
|
4
|
+
data.tar.gz: d5594c50173f64f96303e91b0bc0b0659335d42b0d16b4df1e6f618ad98041fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6578fe32fed59a2c8fc6f4209244de1d33793561af20b258d96c8bd7c5cd78e7203fa08ebaea4a2c8315c89980bf4033852d00c3f4cadad3a6986c766dea7dad
|
|
7
|
+
data.tar.gz: ec2db7a0481f6075ef9e1565a47f972bc41f6d464b65a7700139b4209636e079a254454007857ce7988fb1d01b745277a71a737d8f3e49f340e768106b511c38
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.4 - 2026-08-30
|
|
4
|
+
|
|
5
|
+
- Reuse the encoding the after image already built. `State#to_h` copies the
|
|
6
|
+
state by encoding it and parsing the result, then threw the encoded string
|
|
7
|
+
away, and `complete` normalized and encoded the same hash a second time to
|
|
8
|
+
measure it. `to_h_with_byte_size` returns the copy with the size of the
|
|
9
|
+
encoding that produced it, so a committed turn now traverses the state twice
|
|
10
|
+
rather than three times and encodes it twice rather than three times.
|
|
11
|
+
Measured on SQLite against 0.14.3, committed throughput rises 5.3% at 13 KB
|
|
12
|
+
of state, 16.1% at 116 KB, and 12.0% at 1 MB. `docs/benchmarks.md` holds the
|
|
13
|
+
numbers. `solid-objects-js` already measured the string it commits; this
|
|
14
|
+
brings the gem to the same shape.
|
|
15
|
+
- Add `Serialization.deep_copy_with_byte_size`, which returns a deep copy
|
|
16
|
+
beside the size of its encoded form. `deep_copy` now calls it, so both
|
|
17
|
+
encode once.
|
|
18
|
+
- Enforce `max_state_bytes` against the size the after image reports rather
|
|
19
|
+
than by encoding the state again. The turn still fails with
|
|
20
|
+
`PayloadTooLarge` before it opens its commit transaction, which a test now
|
|
21
|
+
covers end to end.
|
|
22
|
+
|
|
3
23
|
## 0.14.3 - 2026-08-29
|
|
4
24
|
|
|
5
25
|
- Cut one of the three full state copies a committed turn made. The executor
|
data/docs/benchmarks.md
CHANGED
|
@@ -142,6 +142,22 @@ used to do, so part of the saving pays for that check. The gain grows with the
|
|
|
142
142
|
state, because the database write dominates a small turn. The empty-state row
|
|
143
143
|
sits inside run-to-run variance.
|
|
144
144
|
|
|
145
|
+
`0.14.4` removed the traversal that remained. Copying the state encodes it and
|
|
146
|
+
parses the result, and the commit then normalized and encoded the same hash
|
|
147
|
+
again to measure it. The copy now reports the size of the encoding that
|
|
148
|
+
produced it. Measured the same way, against the released `0.14.3` tree:
|
|
149
|
+
|
|
150
|
+
| Committed state | 0.14.3 | 0.14.4 | Change |
|
|
151
|
+
| ---: | ---: | ---: | ---: |
|
|
152
|
+
| 23 bytes | 1,251.2 messages/s | 1,242.8 messages/s | -0.7% |
|
|
153
|
+
| 13,662 bytes | 612.0 messages/s | 644.7 messages/s | +5.3% |
|
|
154
|
+
| 118,786 bytes | 171.9 messages/s | 199.6 messages/s | +16.1% |
|
|
155
|
+
| 1,026,356 bytes | 23.4 messages/s | 26.2 messages/s | +12.0% |
|
|
156
|
+
|
|
157
|
+
A committed turn now traverses the state twice and encodes it twice, against
|
|
158
|
+
three of each before. The empty-state row again sits inside run-to-run
|
|
159
|
+
variance.
|
|
160
|
+
|
|
145
161
|
The curve matters more than the change. Throughput falls about 28 times between
|
|
146
162
|
13 KB and 1 MB of state, and about 53 times between an empty state and 1 MB.
|
|
147
163
|
The `max_state_bytes` default of 5 MB is therefore a limit rather than an
|
|
@@ -26,13 +26,13 @@ module SolidObjects
|
|
|
26
26
|
SolidObjects.instrument(:"message.started", **instrumentation_payload)
|
|
27
27
|
result = invoke_actor(message_context)
|
|
28
28
|
observable_changes = changed_observables(observables_before, actor.observable_values)
|
|
29
|
-
state_after = actor.state.
|
|
30
|
-
ensure_query_did_not_mutate_state!(state_before, state_after)
|
|
29
|
+
state_after = actor.state.to_h_with_byte_size
|
|
30
|
+
ensure_query_did_not_mutate_state!(state_before, state_after.value)
|
|
31
31
|
complete(
|
|
32
32
|
result,
|
|
33
33
|
observable_changes,
|
|
34
34
|
state_after:,
|
|
35
|
-
state_changed: state_after != state_before
|
|
35
|
+
state_changed: state_after.value != state_before
|
|
36
36
|
)
|
|
37
37
|
true
|
|
38
38
|
rescue LostActivation
|
|
@@ -81,12 +81,9 @@ module SolidObjects
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
# @rbs (untyped, Hash[String, untyped], state_after:
|
|
84
|
+
# @rbs (untyped, Hash[String, untyped], state_after: Serialization::Dumped, state_changed: bool) -> void
|
|
85
85
|
def complete(result, observable_changes, state_after:, state_changed:)
|
|
86
|
-
|
|
87
|
-
state_after,
|
|
88
|
-
max_bytes: SolidObjects.configuration.max_state_bytes
|
|
89
|
-
)
|
|
86
|
+
ensure_state_fits!(state_after.byte_size)
|
|
90
87
|
serialized_result = Serialization.dump(
|
|
91
88
|
(message.delivery_mode == "sync") ? result : nil,
|
|
92
89
|
max_bytes: SolidObjects.configuration.max_result_bytes
|
|
@@ -108,7 +105,7 @@ module SolidObjects
|
|
|
108
105
|
locked_message = Message.lock.find(message.id)
|
|
109
106
|
execute_commit_actions(commit_action_intents)
|
|
110
107
|
instance.update!(
|
|
111
|
-
state:
|
|
108
|
+
state: state_after.value,
|
|
112
109
|
state_version: actor.class.state_version,
|
|
113
110
|
state_revision: locked_message.sequence,
|
|
114
111
|
last_used_at: SolidObjects.database_adapter.database_now
|
|
@@ -154,11 +151,19 @@ module SolidObjects
|
|
|
154
151
|
actor_id: message.actor_id
|
|
155
152
|
)
|
|
156
153
|
end
|
|
157
|
-
report_large_state(
|
|
154
|
+
report_large_state(state_after.byte_size)
|
|
158
155
|
SolidObjects.instrument_after_commit(:"message.completed", **instrumentation_payload)
|
|
159
156
|
SolidObjects.wake_up.signal
|
|
160
157
|
end
|
|
161
158
|
|
|
159
|
+
# @rbs (Integer) -> void
|
|
160
|
+
def ensure_state_fits!(byte_size)
|
|
161
|
+
max_bytes = SolidObjects.configuration.max_state_bytes
|
|
162
|
+
return if byte_size <= max_bytes
|
|
163
|
+
|
|
164
|
+
raise PayloadTooLarge, "serialized value exceeds #{max_bytes} bytes"
|
|
165
|
+
end
|
|
166
|
+
|
|
162
167
|
# @rbs (Integer) -> void
|
|
163
168
|
def report_large_state(byte_count)
|
|
164
169
|
threshold = SolidObjects.configuration.warn_state_bytes
|
|
@@ -37,7 +37,14 @@ module SolidObjects
|
|
|
37
37
|
|
|
38
38
|
# @rbs (untyped) -> untyped
|
|
39
39
|
def deep_copy(value)
|
|
40
|
-
|
|
40
|
+
deep_copy_with_byte_size(value).value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @rbs (untyped) -> Dumped
|
|
44
|
+
def deep_copy_with_byte_size(value)
|
|
45
|
+
encoded = JSON.generate(normalize(value), max_nesting: MAX_NESTING)
|
|
46
|
+
|
|
47
|
+
Dumped.new(value: JSON.parse(encoded), byte_size: encoded.bytesize)
|
|
41
48
|
rescue JSON::GeneratorError, JSON::ParserError, EncodingError => error
|
|
42
49
|
raise InvalidPayload, error.message
|
|
43
50
|
end
|
data/lib/solid_objects/state.rb
CHANGED
|
@@ -52,6 +52,11 @@ module SolidObjects
|
|
|
52
52
|
Serialization.deep_copy(data)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# @rbs () -> Serialization::Dumped
|
|
56
|
+
def to_h_with_byte_size
|
|
57
|
+
Serialization.deep_copy_with_byte_size(data)
|
|
58
|
+
end
|
|
59
|
+
|
|
55
60
|
# @rbs (Symbol | String) -> untyped
|
|
56
61
|
def fetch(name)
|
|
57
62
|
key = name.to_s
|
|
@@ -30,8 +30,11 @@ module SolidObjects
|
|
|
30
30
|
# @rbs (Hash[String, untyped], Hash[String, untyped]) -> Hash[String, untyped]
|
|
31
31
|
def changed_observables: (Hash[String, untyped], Hash[String, untyped]) -> Hash[String, untyped]
|
|
32
32
|
|
|
33
|
-
# @rbs (untyped, Hash[String, untyped], state_after:
|
|
34
|
-
def complete: (untyped, Hash[String, untyped], state_after:
|
|
33
|
+
# @rbs (untyped, Hash[String, untyped], state_after: Serialization::Dumped, state_changed: bool) -> void
|
|
34
|
+
def complete: (untyped, Hash[String, untyped], state_after: Serialization::Dumped, state_changed: bool) -> void
|
|
35
|
+
|
|
36
|
+
# @rbs (Integer) -> void
|
|
37
|
+
def ensure_state_fits!: (Integer) -> void
|
|
35
38
|
|
|
36
39
|
# @rbs (Integer) -> void
|
|
37
40
|
def report_large_state: (Integer) -> void
|
|
@@ -29,6 +29,9 @@ module SolidObjects
|
|
|
29
29
|
# @rbs (untyped) -> untyped
|
|
30
30
|
def self.deep_copy: (untyped) -> untyped
|
|
31
31
|
|
|
32
|
+
# @rbs (untyped) -> Dumped
|
|
33
|
+
def self.deep_copy_with_byte_size: (untyped) -> Dumped
|
|
34
|
+
|
|
32
35
|
# @rbs (untyped) -> untyped
|
|
33
36
|
def self.readonly_copy: (untyped) -> untyped
|
|
34
37
|
|
|
@@ -45,6 +45,9 @@ module SolidObjects
|
|
|
45
45
|
# @rbs () -> Hash[String, untyped]
|
|
46
46
|
def to_h: () -> Hash[String, untyped]
|
|
47
47
|
|
|
48
|
+
# @rbs () -> Serialization::Dumped
|
|
49
|
+
def to_h_with_byte_size: () -> Serialization::Dumped
|
|
50
|
+
|
|
48
51
|
# @rbs (Symbol | String) -> untyped
|
|
49
52
|
def fetch: (Symbol | String) -> untyped
|
|
50
53
|
|
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.14.
|
|
4
|
+
version: 0.14.4
|
|
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-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actioncable
|