Skip to content

Commit 2084e35

Browse files
committed
fix: solve req and telemetry issues
1 parent 64b9b63 commit 2084e35

5 files changed

Lines changed: 100 additions & 70 deletions

File tree

app/controllers/status_controller.rb

Lines changed: 52 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,47 @@ class StatusController < ActionController::API
66
skip_before_action :verify_authenticity_token, raise: false
77

88
COMPONENT_META = {
9-
'api' => { name: 'API', description: 'Core REST API services' },
10-
'database' => { name: 'Database', description: 'PostgreSQL primary database' },
11-
'redis' => { name: 'Cache & Background Jobs', description: 'Redis cache and Sidekiq queue processor' },
12-
'websocket' => { name: 'Real-time (WebSocket)', description: 'ActionCable WebSocket connections' },
13-
'sidekiq' => { name: 'Background Jobs (Sidekiq)', description: 'Async job processing' },
14-
'riot_api' => { name: 'Riot API Integration', description: 'Riot Games data synchronization' }
9+
'api' => { name: 'API', description: 'Core REST API services' },
10+
'database' => { name: 'Database', description: 'PostgreSQL primary database' },
11+
'redis' => { name: 'Cache & Background Jobs', description: 'Redis cache and Sidekiq queue processor' },
12+
'websocket' => { name: 'Real-time (WebSocket)', description: 'ActionCable WebSocket connections' },
13+
'sidekiq' => { name: 'Background Jobs (Sidekiq)', description: 'Async job processing' },
14+
'riot_api' => { name: 'Riot API Integration', description: 'Riot Games data synchronization' }
1515
}.freeze
1616

1717
def index
18-
components = build_component_statuses
19-
incidents = build_incidents
20-
uptime = build_uptime_history
21-
indicator, description = overall_status(components)
18+
cached = Rails.cache.fetch('status_page/v2', expires_in: 30.seconds) do
19+
components = build_component_statuses
20+
incidents = build_incidents
21+
uptime = build_uptime_history
22+
indicator, description = overall_status(components)
23+
24+
{
25+
status: { indicator: indicator, description: description },
26+
components: components,
27+
incidents: incidents,
28+
uptime_history: uptime
29+
}
30+
end
2231

23-
render json: {
32+
render json: cached.merge(
2433
page: {
25-
id: 'prostaff',
26-
name: 'ProStaff',
27-
url: 'https://status.prostaff.gg',
28-
time_zone: 'UTC',
34+
id: 'prostaff',
35+
name: 'ProStaff',
36+
url: 'https://status.prostaff.gg',
37+
time_zone: 'UTC',
2938
updated_at: Time.current.iso8601
30-
},
31-
status: {
32-
indicator: indicator,
33-
description: description
34-
},
35-
components: components,
36-
incidents: incidents,
37-
uptime_history: uptime
38-
}, status: :ok
39+
}
40+
), status: :ok
3941
end
4042

4143
private
4244

4345
def build_component_statuses
44-
StatusIncident::COMPONENTS.map do |component|
45-
snapshot = StatusSnapshot.for_component(component).order(checked_at: :desc).first
46+
latest = StatusSnapshot.latest_per_component
4647

47-
if snapshot
48+
StatusIncident::COMPONENTS.map do |component|
49+
if (snapshot = latest[component])
4850
build_component_from_snapshot(component, snapshot)
4951
else
5052
build_component_live(component)
@@ -55,13 +57,13 @@ def build_component_statuses
5557
def build_component_from_snapshot(component, snapshot)
5658
meta = COMPONENT_META[component]
5759
{
58-
id: component,
59-
name: meta[:name],
60-
status: snapshot.status,
61-
description: meta[:description],
60+
id: component,
61+
name: meta[:name],
62+
status: snapshot.status,
63+
description: meta[:description],
6264
response_time_ms: snapshot.response_time_ms,
63-
last_checked_at: snapshot.checked_at.iso8601,
64-
updated_at: snapshot.updated_at.iso8601
65+
last_checked_at: snapshot.checked_at.iso8601,
66+
updated_at: snapshot.updated_at.iso8601
6567
}
6668
end
6769

@@ -70,13 +72,13 @@ def build_component_live(component)
7072
result = live_check(component)
7173

7274
{
73-
id: component,
74-
name: meta[:name],
75-
status: result[:status],
76-
description: meta[:description],
75+
id: component,
76+
name: meta[:name],
77+
status: result[:status],
78+
description: meta[:description],
7779
response_time_ms: result[:response_time_ms],
78-
last_checked_at: Time.current.iso8601,
79-
updated_at: Time.current.iso8601
80+
last_checked_at: Time.current.iso8601,
81+
updated_at: Time.current.iso8601
8082
}
8183
end
8284

@@ -120,24 +122,25 @@ def build_incidents
120122

121123
def serialize_incident(incident)
122124
{
123-
id: incident.id,
124-
title: incident.title,
125-
body: incident.body,
126-
severity: incident.severity,
127-
status: incident.status,
125+
id: incident.id,
126+
title: incident.title,
127+
body: incident.body,
128+
severity: incident.severity,
129+
status: incident.status,
128130
affected_components: incident.affected_components,
129-
started_at: incident.started_at.iso8601,
130-
resolved_at: incident.resolved_at&.iso8601,
131-
postmortem: incident.postmortem,
132-
updates: incident.updates.order(created_at: :desc).map do |u|
131+
started_at: incident.started_at.iso8601,
132+
resolved_at: incident.resolved_at&.iso8601,
133+
postmortem: incident.postmortem,
134+
updates: incident.updates.order(created_at: :desc).map do |u|
133135
{ id: u.id, status: u.status, body: u.body, created_at: u.created_at.iso8601 }
134136
end
135137
}
136138
end
137139

138140
def build_uptime_history
141+
bulk = StatusSnapshot.bulk_uptime_by_day(days: 90)
139142
StatusIncident::COMPONENTS.each_with_object({}) do |component, hash|
140-
hash[component] = StatusSnapshot.uptime_by_day(component: component, days: 90)
143+
hash[component] = bulk[component] || []
141144
end
142145
rescue StandardError => e
143146
Rails.logger.error("[STATUS] Failed to build uptime history: #{e.message}")

app/models/status_snapshot.rb

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,41 @@ class StatusSnapshot < ApplicationRecord
1515
scope :for_component, ->(component) { where(component: component) }
1616

1717
# Returns daily uptime percentage for a component over the last N days.
18-
#
19-
# @param component [String] one of COMPONENTS
20-
# @param days [Integer] number of days to look back (default 90)
21-
# @return [Array<Hash>] array of { date: Date, uptime_pct: Float, status: String }
22-
# Days without snapshots are omitted from the result.
2318
def self.uptime_by_day(component:, days: 90)
24-
rows = fetch_rows(component, days)
25-
grouped = rows.group_by { |checked_at, _| checked_at.to_date }
26-
grouped.map { |date, entries| aggregate_day(date, entries) }
19+
rows = for_component(component).recent(days).order(checked_at: :asc).pluck(:checked_at, :status)
20+
rows.group_by { |checked_at, _| checked_at.to_date }
21+
.map { |date, entries| aggregate_day(date, entries) }
2722
end
2823

29-
private_class_method def self.fetch_rows(component, days)
30-
for_component(component)
31-
.recent(days)
32-
.order(checked_at: :asc)
33-
.pluck(:checked_at, :status)
24+
# Single-query bulk version: returns { component => [{ date:, uptime_pct:, status: }] }
25+
def self.bulk_uptime_by_day(days: 90)
26+
rows = where(checked_at: days.days.ago..Time.current)
27+
.order(checked_at: :asc)
28+
.pluck(:component, :checked_at, :status)
29+
30+
rows
31+
.group_by(&:first)
32+
.transform_values do |component_rows|
33+
component_rows
34+
.map { |_, checked_at, status| [checked_at, status] }
35+
.group_by { |checked_at, _| checked_at.to_date }
36+
.map { |date, entries| aggregate_day(date, entries) }
37+
end
38+
end
39+
40+
# Single-query bulk version: returns { component => snapshot } for the latest per component
41+
def self.latest_per_component
42+
select('DISTINCT ON (component) *')
43+
.order('component, checked_at DESC')
44+
.index_by(&:component)
3445
end
3546

36-
private_class_method def self.aggregate_day(date, entries)
47+
def self.aggregate_day(date, entries)
3748
total = entries.size
3849
ok = entries.count { |_, s| s == 'operational' }
3950
uptime_pct = (ok.to_f / total * 100).round(2)
4051
dominant = entries.map { |_, s| s }.tally.max_by { |_, c| c }&.first
4152
{ date: date, uptime_pct: uptime_pct, status: dominant }
4253
end
54+
private_class_method :aggregate_day
4355
end

app/modules/inhouses/controllers/internal/inhouse_queues_controller.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ module Internal
99
class InhouseQueuesController < ApplicationController
1010
before_action :verify_internal_token
1111

12-
# GET /internal/api/inhouse_queues/active
1312
def active
1413
queues = InhouseQueue.check_in
1514
.where('check_in_deadline > ?', Time.current)
@@ -28,10 +27,14 @@ def verify_internal_token
2827

2928
render json: { error: 'unauthorized' }, status: :unauthorized and return unless token.present?
3029

31-
payload = JwtService.decode(token)
30+
secret = ENV.fetch('INTERNAL_JWT_SECRET', nil)
31+
render json: { error: 'unauthorized' }, status: :unauthorized and return unless secret.present?
32+
33+
decoded = JWT.decode(token, secret, true, { algorithm: 'HS256' })
34+
payload = HashWithIndifferentAccess.new(decoded[0])
3235

3336
render json: { error: 'forbidden' }, status: :forbidden and return unless payload[:type] == 'internal'
34-
rescue JwtService::AuthenticationError
37+
rescue JWT::DecodeError, JWT::ExpiredSignature
3538
render json: { error: 'unauthorized' }, status: :unauthorized
3639
end
3740

app/modules/players/controllers/players_controller.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ class PlayersController < Api::V1::BaseController
1414

1515
# GET /api/v1/players
1616
def index
17-
# Optimized query to prevent timeout during bulk sync operations
18-
# PostgreSQL will allow concurrent reads even during updates (MVCC)
19-
# Set a reasonable timeout to prevent 504s
20-
ActiveRecord::Base.connection.execute("SET LOCAL statement_timeout = '5000'") # 5 seconds
17+
ActiveRecord::Base.connection.execute("SET statement_timeout = '5000'")
2118

2219
players = organization_scoped(Player).includes(:organization)
2320

@@ -47,6 +44,8 @@ def index
4744
code: 'QUERY_TIMEOUT',
4845
status: :request_timeout
4946
)
47+
ensure
48+
ActiveRecord::Base.connection.execute("RESET statement_timeout") rescue nil
5049
end
5150

5251
# GET /api/v1/players/:id
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
class AddIndexToStatusSnapshots < ActiveRecord::Migration[7.2]
4+
disable_ddl_transaction!
5+
6+
def change
7+
add_index :status_snapshots, %i[component checked_at],
8+
order: { checked_at: :desc },
9+
algorithm: :concurrently,
10+
if_not_exists: true,
11+
name: 'idx_status_snapshots_component_checked_at'
12+
end
13+
end

0 commit comments

Comments
 (0)