solid_objects 0.16.0 → 0.16.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d267d861c25f682b3e45208131b9c115b49710ae43fb499223c6d63f48519a0
4
- data.tar.gz: 1acc3bb415c57e9072f231fc5e37ed37ce8bab4a9ba5fc894a7dbe5453839d8e
3
+ metadata.gz: b060638b79ca2236a6530f8c478fe28ac0453fc514a0daeeb5f0f3a400f446ee
4
+ data.tar.gz: 31424c65ecc34caab85a2296b3e2f7055f731b07a5db16022fc48d1fcc860ed6
5
5
  SHA512:
6
- metadata.gz: 1f3d71f114877579419a680b8ff46b56036060d8575c7c6732c4c9d530ed3020fbb58eb61ac3e348810d44ecf09e8553b4f07390bd1c22460b5604005578e06f
7
- data.tar.gz: 07e811b54283aa3f3638ae90b8fbc449f5e151dbcae69e01487a8231a1b8cc5556bd1cc035b67aa347f080329a64f9c0394965d468860dd989f0ba40afdc7552
6
+ metadata.gz: 6fee1b678cfb18e2a0b59c1e275a079631570b7a7b44cdd9edc2e0d880ec7efc688f9ee59bc9e3afb2742f355f793939f9a263f07f9e3a7bc466c07367dcbb23
7
+ data.tar.gz: 5d92fe6f884c53b757c5dde1ab372afe0200a3f509ddef737f75bd8810f42e224def36ce363abf57d1e41326fe2dc5ff0985b111deed3656db960a3889b26d9c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.1 - 2026-10-02
4
+
5
+ - Fix the SQLite join order of the claimed-message scan. The query in
6
+ `ActivationManager#claimed_instance_ids` has no condition on a
7
+ claimed-message column. The claimed-messages table is a short queue, so it is
8
+ often empty when `ANALYZE` or `PRAGMA optimize` runs, and `sqlite_stat1` then
9
+ has no row for it. Without statistics, SQLite assumed that the table was
10
+ large, and it scanned every instance on each worker poll. The scan now uses
11
+ `CROSS JOIN`, which SQLite keeps as a fixed join order, so it starts from the
12
+ claimed messages. PostgreSQL and MySQL treat `CROSS JOIN` with an equality as
13
+ an inner join and keep their plans. The ids and their order do not change.
14
+ - Find SQLite effect recovery candidates through the processing effects.
15
+ `sqlite_stat1` records only the average row count for each effect status.
16
+ When most effects are complete, SQLite estimated that `status = 'processing'`
17
+ matched most of the effects table. It then read every recovery row in key
18
+ order to skip a sort, on each effect poll. An index on the recovery filter
19
+ does not help, because a recovery row keeps `retired_at` empty after a normal
20
+ completion. On SQLite the status test now carries
21
+ `likelihood(..., 0.000001)`, so the plan starts from `idx_so_effects_poll`.
22
+ The PostgreSQL and MySQL queries do not change.
23
+
3
24
  ## 0.16.0 - 2026-09-23
4
25
 
5
26
  - Find a message whose reference a caller lost.
@@ -86,7 +86,8 @@ module SolidObjects
86
86
  # @rbs (Time) -> Array[Integer]
87
87
  def claimed_instance_ids(now)
88
88
  ClaimedMessage
89
- .joins(:instance)
89
+ .joins("CROSS JOIN #{Instance.table_name}")
90
+ .where("#{Instance.table_name}.id = #{ClaimedMessage.table_name}.instance_id")
90
91
  .where("#{Instance.table_name}.paused_at IS NULL")
91
92
  .where(available_lease_sql, now)
92
93
  .group(:instance_id)
@@ -63,17 +63,22 @@ module SolidObjects
63
63
  effects = Effect.table_name
64
64
  owners = Process.table_name
65
65
  bindings = EffectRecovery.table_name
66
- heartbeat = case DatabaseAdapter.family(Record.connection)
66
+ family = DatabaseAdapter.family(Record.connection)
67
+ heartbeat = case family
67
68
  when :postgresql then "EXTRACT(EPOCH FROM #{owners}.last_heartbeat_at)"
68
69
  when :mysql then "UNIX_TIMESTAMP(#{owners}.last_heartbeat_at)"
69
70
  else "CAST(STRFTIME('%s', #{owners}.last_heartbeat_at) AS REAL)"
70
71
  end
72
+ processing = case family
73
+ when :postgresql, :mysql then "#{effects}.status = ?"
74
+ else "likelihood(#{effects}.status = ?, 0.000001)"
75
+ end
71
76
  now = SolidObjects.database_adapter.database_clock_now.to_f
72
77
  threshold = SolidObjects.configuration.process_alive_threshold
73
78
  EffectRecovery.joins("INNER JOIN #{effects} ON #{effects}.effect_id = #{bindings}.effect_id")
74
79
  .joins("LEFT JOIN #{owners} ON #{owners}.id = #{effects}.claimed_by")
75
80
  .where(retired_at: nil).where.not(recovery_operation: nil)
76
- .where("#{effects}.status = ?", "processing")
81
+ .where(processing, "processing")
77
82
  .where("#{owners}.id IS NULL OR #{heartbeat} <= ? - CASE WHEN #{bindings}.recovery_timeout > ? THEN #{bindings}.recovery_timeout ELSE ? END", now, threshold, threshold)
78
83
  .order(:effect_id).limit(SolidObjects.configuration.claim_scan_limit)
79
84
  end
@@ -1,5 +1,5 @@
1
1
  # rbs_inline: enabled
2
2
 
3
3
  module SolidObjects
4
- VERSION = "0.16.0"
4
+ VERSION = "0.16.1"
5
5
  end
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.16.0
4
+ version: 0.16.1
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-09-23 00:00:00.000000000 Z
11
+ date: 2026-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actioncable