railwatch 0.8.2 → 0.8.3

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: d3e41b1890c573cc71fe35d7a9ac134dc3964e4250126bd7c898132800ba47d9
4
- data.tar.gz: 84f59bb0bc68c014618c564533b0f3a5266062b1fac93a252a5a41c3632f0136
3
+ metadata.gz: 6426edd02fd2bb306da28aa55723598581751003cce2b10371fdabb5d5a726c7
4
+ data.tar.gz: 44f256ceff92b14dd0534e0f260f6d8d8b9fb4294e357091c25ec3e0eb3b3ef3
5
5
  SHA512:
6
- metadata.gz: 4472da6ecdbb054cd17cae2a31cf3d1f0a5a6237e4964af2c9da04fa2b754a95a7aa91c268dd43ef9a29791762b82c2edffb446557b0f2b6b5a3a47f6b504a8c
7
- data.tar.gz: 276639c1d1f4c9632892a419f83443407a41af5e33c062a426e50bf9a3f5fa8daf039b267157c14079e2c826dbd5b025e0a2e955d43450d28b59be31637fb6b4
6
+ metadata.gz: bdb8ba9db502d460a3a653a52238728993807fe5b374823d08f8418b865a1a0d6c0a39f18809a9a00fe95c96952f6a3db4d0337bb3b544e85e63c835830b5e62
7
+ data.tar.gz: 0ef6f54e5da09ae2b3fc7048ae8b9b4d50fbda878dbb42220c98236345054db8b6b24134e41c7eedf8d13d13f843f4405b0f037d3dd8e6e6caa3cb5d2a6f6d0e
data/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@
6
6
  filled in by the release commit, which is also the only commit that
7
7
  touches lib/railwatch/version.rb and Gemfile.lock. See CONTRIBUTING.md. -->
8
8
 
9
+ ## 0.8.3 (2026-09-23)
10
+
11
+ - Filtering jobs by `class:` seeks the class's group (a job's group is
12
+ the digest of its class name) instead of walking every job in the
13
+ window: 715 ms over a week on the platform's own tenant, now 9 ms, and
14
+ one MCP search took 49 s.
15
+ - The Scheduled tasks page and the missed-task check read each task's
16
+ newest schedule with one index seek per key. The single grouped query
17
+ they used read every scheduled run kept (10 s once, 185 ms warm).
18
+
9
19
  ## 0.8.2 (2026-09-23)
10
20
 
11
21
  - The Profiles page's profiled share counts only the executions inside
@@ -125,7 +125,19 @@ module Railwatch
125
125
  outcome = fields["outcome"].presence || fields["status"].presence
126
126
  scope = scope.where(queue: fields["queue"]) if fields["queue"].present?
127
127
  scope = scope.where(outcome: outcome) if outcome
128
- scope = scope.where(name: fields["class"]) if fields["class"].present?
128
+ # A job's group is the digest of its class name, and group_hash is
129
+ # indexed with occurred_at; name is not, so class: alone walked every
130
+ # job in the window (49 s for a week through MCP). Not every group
131
+ # follows that rule -- Solid Queue's pruned attempts are named
132
+ # "(pruned)" under the SolidQueue::Pruned group -- so the window's
133
+ # rollups supply any other group recorded under the name, and the last
134
+ # FRESH_NAMES, which rollups have not caught up with, match by name.
135
+ if fields["class"].present?
136
+ name = fields["class"]
137
+ groups = [ Record.group_hash(name) ] |
138
+ Telemetry::Rollup.for_type("job_attempt").between(range.begin, range.end).where(name: name).distinct.pluck(:group_hash)
139
+ scope = scope.merge(Telemetry::Execution.in_groups_or_fresh("job_attempt", groups, range)).where(name: name)
140
+ end
129
141
  scope = scope.where(job_id: fields["job_id"]) if fields["job_id"].present?
130
142
  return scope unless text.present?
131
143
  scope.merge(Telemetry::Execution.named_like("job_attempt", text, range, previews: true))
@@ -61,6 +61,19 @@ module Railwatch
61
61
  .where(ids.map { |branch| "#{quoted_table_name}.id IN (#{branch.select(:id).to_sql})" }.join(" OR "))
62
62
  end
63
63
 
64
+ # Executions of `kind` in `range` that are in one of `groups`, or recent
65
+ # enough (FRESH_NAMES) that their rollup may not exist yet. Each branch
66
+ # names its index and the outer lookup is by rowid, as in named_like:
67
+ # an OR over the two conditions made SQLite walk the whole window.
68
+ def self.in_groups_or_fresh(kind, groups, range)
69
+ by_group = indexed_by("index_executions_on_group_hash_and_occurred_at").where(group_hash: groups, occurred_at: range, kind: kind)
70
+ fresh = where(kind: kind, occurred_at: [ range.begin, range.end - FRESH_NAMES ].max..range.end)
71
+ return where(kind: kind, occurred_at: range).where(group_hash: groups).or(fresh) unless TelemetryRecord.sqlite?
72
+
73
+ from("#{quoted_table_name} NOT INDEXED").where(kind: kind, occurred_at: range)
74
+ .where([ by_group, fresh ].map { |branch| "#{quoted_table_name}.id IN (#{branch.select(:id).to_sql})" }.join(" OR "))
75
+ end
76
+
64
77
  def self.indexed_by(index) = unscoped.from("#{quoted_table_name} INDEXED BY #{index}")
65
78
  private_class_method :indexed_by
66
79
 
@@ -74,14 +87,17 @@ module Railwatch
74
87
  KINDS.flat_map { |kind| where(kind: kind, occurred_at: time..).distinct.pluck(:server) }.uniq
75
88
  end
76
89
 
77
- # The schedule string each task key last ran with, in one query: the
78
- # newest scheduled_task row per key carries it in detail. The scheduled
79
- # tasks page and CheckScheduledTasksJob both used to look it up per key.
90
+ # The schedule string each task key last ran with: the newest
91
+ # scheduled_task row per key carries it in detail. A task's runs share
92
+ # its group (the digest of its key), so that row is the last entry of
93
+ # its group_hash index -- one seek per key, a few dozen keys. A single
94
+ # MAX(id) GROUP BY task_key read every scheduled run ever kept instead:
95
+ # 10 s on the platform's own tenant.
80
96
  def self.latest_schedules(keys)
81
- return {} if keys.empty?
82
- latest = scheduled.where(task_key: keys).group(:task_key).select(:task_key, Arel.sql("MAX(id) AS id"))
83
- scheduled.where(id: latest.map(&:id))
84
- .pluck(:task_key, Arel.sql("json_extract(detail, '$.schedule')")).to_h
97
+ keys.index_with do |key|
98
+ scheduled.where(group_hash: Record.group_hash(key), task_key: key).order(occurred_at: :desc)
99
+ .pick(Arel.sql("json_extract(detail, '$.schedule')"))
100
+ end.compact
85
101
  end
86
102
 
87
103
  CHILDREN = {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Railwatch
4
- VERSION = "0.8.2"
4
+ VERSION = "0.8.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railwatch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cole Robertson