Skip to content

Commit 5dcc6de

Browse files
committed
Cache dashboard queue previews
1 parent b6e4176 commit 5dcc6de

7 files changed

Lines changed: 349 additions & 67 deletions

File tree

lib/reencodarr/dashboard/state.ex

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -352,25 +352,14 @@ defmodule Reencodarr.Dashboard.State do
352352
defp fetch_queue_items(current_items) do
353353
timeout = queue_query_timeout()
354354

355+
preview_items =
356+
fetch_queue_preview_items(timeout)
357+
|> Enum.group_by(& &1.queue_type)
358+
355359
%{
356-
analyzer:
357-
fetch_queue_preview(
358-
"analyzer",
359-
current_items.analyzer,
360-
fn -> VideoQueries.videos_needing_analysis_preview(5, timeout: timeout) end
361-
),
362-
crf_searcher:
363-
fetch_queue_preview(
364-
"crf_searcher",
365-
current_items.crf_searcher,
366-
fn -> VideoQueries.videos_for_crf_search_preview(5, timeout: timeout) end
367-
),
368-
encoder:
369-
fetch_queue_preview(
370-
"encoder",
371-
current_items.encoder,
372-
fn -> VideoQueries.videos_ready_for_encoding_preview(5, timeout: timeout) end
373-
)
360+
analyzer: queue_preview_rows(preview_items, :analyzer, current_items.analyzer),
361+
crf_searcher: queue_preview_rows(preview_items, :crf_searcher, current_items.crf_searcher),
362+
encoder: queue_preview_rows(preview_items, :encoder, current_items.encoder)
374363
}
375364
end
376365

@@ -402,18 +391,10 @@ defmodule Reencodarr.Dashboard.State do
402391
%{
403392
analyzer: stats.needs_analysis || current_queue_counts.analyzer || 0,
404393
crf_searcher: stats.analyzed || current_queue_counts.crf_searcher || 0,
405-
encoder: refresh_encoder_queue_count(current_queue_counts.encoder)
394+
encoder: stats.encoding_queue_count || current_queue_counts.encoder || 0
406395
}
407396
end
408397

409-
defp refresh_encoder_queue_count(previous_count) do
410-
VideoQueries.encoding_queue_count(timeout: queue_query_timeout()) || previous_count || 0
411-
rescue
412-
error ->
413-
Logger.warning("Dashboard.State encoder queue count refresh failed: #{inspect(error)}")
414-
previous_count || 0
415-
end
416-
417398
defp queue_refresh_enabled? do
418399
Application.get_env(:reencodarr, :dashboard_queue_refresh_enabled, true)
419400
end
@@ -427,19 +408,67 @@ defmodule Reencodarr.Dashboard.State do
427408
|> Map.put(:percent, percent)
428409
end
429410

430-
defp fetch_queue_preview(label, fallback, fun) do
431-
fun.()
411+
defp queue_preview_rows(preview_items_by_type, queue_type, fallback) do
412+
preview_items_by_type
413+
|> Map.get(queue_type, [])
414+
|> Enum.sort_by(&queue_preview_sort_key(queue_type, &1))
415+
|> Enum.map(&queue_preview_item/1)
416+
|> case do
417+
[] -> fallback
418+
rows -> rows
419+
end
420+
end
421+
422+
defp queue_preview_sort_key(:analyzer, row) do
423+
{
424+
-normalize_queue_int(row.priority),
425+
-normalize_queue_int(row.size),
426+
-timestamp(row.inserted_at),
427+
-timestamp(row.updated_at)
428+
}
429+
end
430+
431+
defp queue_preview_sort_key(:crf_searcher, row) do
432+
{
433+
-normalize_queue_int(row.priority),
434+
-normalize_queue_int(row.bitrate),
435+
-normalize_queue_int(row.size),
436+
timestamp(row.updated_at)
437+
}
438+
end
439+
440+
defp queue_preview_sort_key(:encoder, row) do
441+
{
442+
-normalize_queue_int(row.priority),
443+
-normalize_queue_int(row.savings),
444+
-timestamp(row.updated_at)
445+
}
446+
end
447+
448+
defp normalize_queue_int(nil), do: 0
449+
defp normalize_queue_int(value) when is_integer(value), do: value
450+
451+
defp timestamp(nil), do: 0
452+
defp timestamp(%DateTime{} = dt), do: DateTime.to_unix(dt, :microsecond)
453+
454+
defp queue_preview_item(row), do: %{id: row.video_id, path: row.path}
455+
456+
defp fetch_queue_preview_items(timeout) do
457+
VideoQueries.dashboard_queue_preview_items(timeout: timeout)
432458
rescue
433459
error in [DBConnection.ConnectionError, Exqlite.Error] ->
434460
Logger.debug(
435-
"Dashboard.State #{label} queue preview failed, keeping previous items: #{inspect(error)}"
461+
"Dashboard.State queue preview cache read failed, falling back to previous items: #{inspect(error)}"
436462
)
437463

438-
fallback
464+
[]
439465
catch
440466
:exit, {:timeout, _} ->
441-
Logger.debug("Dashboard.State #{label} queue preview timed out, keeping previous items")
442-
fallback
467+
Logger.debug(
468+
"Dashboard.State queue preview cache read timed out, falling back to previous items"
469+
)
470+
471+
[]
443472
end
444473

445474
defp stats_query_timeout do

lib/reencodarr/media.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,7 @@ defmodule Reencodarr.Media do
18281828
avg_duration_minutes: 0.0,
18291829
most_recent_video_update: nil,
18301830
most_recent_inserted_video: nil,
1831+
encoding_queue_count: 0,
18311832
total_savings_gb: 0.0
18321833
}
18331834
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
defmodule Reencodarr.Media.DashboardQueueCache do
2+
use Ecto.Schema
3+
4+
@primary_key {:id, :id, autogenerate: true}
5+
6+
schema "dashboard_queue_cache" do
7+
field :queue_type, Ecto.Enum, values: [:analyzer, :crf_searcher, :encoder]
8+
field :video_id, :integer
9+
field :path, :string
10+
field :priority, :integer
11+
field :bitrate, :integer
12+
field :size, :integer
13+
field :savings, :integer
14+
field :inserted_at, :utc_datetime
15+
field :updated_at, :utc_datetime
16+
end
17+
end

lib/reencodarr/media/dashboard_stats_cache.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule Reencodarr.Media.DashboardStatsCache do
1818
field :most_recent_inserted_video, :utc_datetime
1919
field :total_vmafs, :integer
2020
field :chosen_vmafs, :integer
21+
field :encoding_queue_count, :integer
2122
field :encoded_savings_bytes, :integer
2223
field :predicted_savings_bytes, :integer
2324
end

lib/reencodarr/media/shared_queries.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ defmodule Reencodarr.Media.SharedQueries do
107107
),
108108
most_recent_video_update: c.most_recent_video_update,
109109
most_recent_inserted_video: c.most_recent_inserted_video,
110+
encoding_queue_count: c.encoding_queue_count,
110111
total_savings_gb: 0.0
111112
}
112113
end

lib/reencodarr/media/video_queries.ex

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule Reencodarr.Media.VideoQueries do
77
"""
88

99
import Ecto.Query
10-
alias Reencodarr.{Media.Video, Media.Vmaf, Repo}
10+
alias Reencodarr.{Media.DashboardQueueCache, Media.Video, Media.Vmaf, Repo}
1111

1212
@doc """
1313
Gets videos ready for CRF search (state: analyzed).
@@ -76,22 +76,8 @@ defmodule Reencodarr.Media.VideoQueries do
7676
Gets a lightweight preview of videos needing analysis for dashboard display.
7777
"""
7878
@spec videos_needing_analysis_preview(integer(), keyword()) :: [map()]
79-
def videos_needing_analysis_preview(limit \\ 10, opts \\ []) do
80-
Repo.all(
81-
from(v in Video,
82-
where: v.state == :needs_analysis,
83-
order_by: [
84-
desc: v.priority,
85-
desc: v.size,
86-
desc: v.inserted_at,
87-
desc: v.updated_at
88-
],
89-
limit: ^limit,
90-
select: %{id: v.id, path: v.path}
91-
),
92-
opts
93-
)
94-
end
79+
def videos_needing_analysis_preview(limit \\ 10, opts \\ []),
80+
do: cached_dashboard_queue_preview(:analyzer, limit, opts)
9581

9682
@doc """
9783
Atomically claims videos for analysis by transitioning them from
@@ -146,17 +132,8 @@ defmodule Reencodarr.Media.VideoQueries do
146132
Gets a lightweight preview of videos ready for CRF search for dashboard display.
147133
"""
148134
@spec videos_for_crf_search_preview(integer(), keyword()) :: [map()]
149-
def videos_for_crf_search_preview(limit \\ 10, opts \\ []) do
150-
Repo.all(
151-
from(v in Video,
152-
where: v.state == :analyzed,
153-
order_by: [desc: v.priority, desc: v.bitrate, desc: v.size, asc: v.updated_at],
154-
limit: ^limit,
155-
select: %{id: v.id, path: v.path}
156-
),
157-
opts
158-
)
159-
end
135+
def videos_for_crf_search_preview(limit \\ 10, opts \\ []),
136+
do: cached_dashboard_queue_preview(:crf_searcher, limit, opts)
160137

161138
@doc """
162139
Gets videos ready for encoding with complex alternation logic between services and libraries.
@@ -184,18 +161,72 @@ defmodule Reencodarr.Media.VideoQueries do
184161
Gets a lightweight preview of videos ready for encoding for dashboard display.
185162
"""
186163
@spec videos_ready_for_encoding_preview(integer(), keyword()) :: [map()]
187-
def videos_ready_for_encoding_preview(limit, opts \\ []) do
164+
def videos_ready_for_encoding_preview(limit, opts \\ []),
165+
do: cached_dashboard_queue_preview(:encoder, limit, opts)
166+
167+
@doc """
168+
Returns all cached dashboard queue preview items.
169+
170+
The dashboard uses this single cache-backed query so queue previews no longer
171+
hit the live `videos` table during refresh.
172+
"""
173+
@spec dashboard_queue_preview_items(keyword()) :: [DashboardQueueCache.t()]
174+
def dashboard_queue_preview_items(opts \\ []) do
188175
Repo.all(
189-
from(v in Video,
190-
where: v.state == :crf_searched and not is_nil(v.chosen_vmaf_id),
191-
order_by: [desc: v.priority, desc: v.updated_at],
192-
limit: ^limit,
193-
select: %{id: v.id, path: v.path}
176+
from(q in DashboardQueueCache,
177+
where: q.queue_type in [:analyzer, :crf_searcher, :encoder],
178+
select: q
194179
),
195180
opts
196181
)
197182
end
198183

184+
defp queue_preview_items(queue_type, opts) do
185+
dashboard_queue_preview_items(opts)
186+
|> Enum.filter(&(&1.queue_type == queue_type))
187+
|> Enum.sort_by(&queue_preview_sort_key(queue_type, &1))
188+
end
189+
190+
defp cached_dashboard_queue_preview(queue_type, limit, opts) do
191+
queue_preview_items(queue_type, opts)
192+
|> Enum.take(limit)
193+
|> Enum.map(&queue_preview_item/1)
194+
end
195+
196+
defp queue_preview_sort_key(:analyzer, row) do
197+
{
198+
-normalize_int(row.priority),
199+
-normalize_int(row.size),
200+
-timestamp(row.inserted_at),
201+
-timestamp(row.updated_at)
202+
}
203+
end
204+
205+
defp queue_preview_sort_key(:crf_searcher, row) do
206+
{
207+
-normalize_int(row.priority),
208+
-normalize_int(row.bitrate),
209+
-normalize_int(row.size),
210+
timestamp(row.updated_at)
211+
}
212+
end
213+
214+
defp queue_preview_sort_key(:encoder, row) do
215+
{
216+
-normalize_int(row.priority),
217+
-normalize_int(row.savings),
218+
-timestamp(row.updated_at)
219+
}
220+
end
221+
222+
defp normalize_int(nil), do: 0
223+
defp normalize_int(value) when is_integer(value), do: value
224+
225+
defp timestamp(nil), do: 0
226+
defp timestamp(%DateTime{} = dt), do: DateTime.to_unix(dt, :microsecond)
227+
228+
defp queue_preview_item(row), do: %{id: row.video_id, path: row.path}
229+
199230
@doc """
200231
Counts total videos ready for encoding.
201232
"""

0 commit comments

Comments
 (0)