From 5182d5d67aa3c6f7906d4603efcec4b48f048991 Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Sat, 28 Dec 2024 15:23:02 +0000 Subject: [PATCH] fix: add downloads_count to episodes table, computed every hour This removes computing latency when retrieving episodes list with download count in admin. The more analytics records, the more it took to calculate the sum of hits to get the downloads count for each episode. --- app/Commands/EpisodesComputeDownloads.php | 50 +++++++++++++++++++ app/Config/Tasks.php | 4 ++ ...5-29-100000_add_episode_download_count.php | 29 +++++++++++ app/Entities/Episode.php | 5 +- app/Models/EpisodeModel.php | 1 + .../Admin/Controllers/EpisodeController.php | 17 ++----- themes/cp_admin/episode/list.php | 2 +- 7 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 app/Commands/EpisodesComputeDownloads.php create mode 100644 app/Database/Migrations/2024-05-29-100000_add_episode_download_count.php diff --git a/app/Commands/EpisodesComputeDownloads.php b/app/Commands/EpisodesComputeDownloads.php new file mode 100644 index 00000000..1bdc7b2d --- /dev/null +++ b/app/Commands/EpisodesComputeDownloads.php @@ -0,0 +1,50 @@ +builder() + ->select('episodes.id as id, IFNULL(SUM(ape.hits),0) as downloads_count') + ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left') + ->groupBy('episodes.id'); + + $episodeModel2 = new EpisodeModel(); + $episodeModel2->builder() + ->setQueryAsData($query) + ->onConstraint('id') + ->updateBatch(); + } +} diff --git a/app/Config/Tasks.php b/app/Config/Tasks.php index 06dd531c..e78ebfc3 100644 --- a/app/Config/Tasks.php +++ b/app/Config/Tasks.php @@ -51,5 +51,9 @@ class Tasks extends BaseConfig $schedule->command('podcast:import') ->everyMinute() ->named('podcast-import'); + + $schedule->command('episodes:compute-downloads') + ->everyHour() + ->named('episodes:compute-downloads'); } } diff --git a/app/Database/Migrations/2024-05-29-100000_add_episode_download_count.php b/app/Database/Migrations/2024-05-29-100000_add_episode_download_count.php new file mode 100644 index 00000000..ec6a9023 --- /dev/null +++ b/app/Database/Migrations/2024-05-29-100000_add_episode_download_count.php @@ -0,0 +1,29 @@ + [ + 'type' => 'INT', + 'unsigned' => true, + 'default' => 0, + 'after' => 'is_published_on_hubs', + ], + ]; + + $this->forge->addColumn('episodes', $fields); + } + + public function down(): void + { + $this->forge->dropColumn('episodes', 'downloads_count'); + } +} diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 44651d07..7fbdc1fb 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -76,9 +76,9 @@ use SimpleXMLElement; * @property array|null $custom_rss * @property string $custom_rss_string * @property bool $is_published_on_hubs + * @property int $downloads_count * @property int $posts_count * @property int $comments_count - * @property int $downloads * @property EpisodeComment[]|null $comments * @property bool $is_premium * @property int $created_by @@ -116,8 +116,6 @@ class Episode extends Entity protected ?Chapters $chapters = null; - protected int $downloads = 0; - /** * @var Person[]|null */ @@ -178,6 +176,7 @@ class Episode extends Entity 'location_osm' => '?string', 'custom_rss' => '?json-array', 'is_published_on_hubs' => 'boolean', + 'downloads_count' => 'integer', 'posts_count' => 'integer', 'comments_count' => 'integer', 'is_premium' => 'boolean', diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index 1757161c..bf32f119 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -89,6 +89,7 @@ class EpisodeModel extends UuidModel 'location_osm', 'custom_rss', 'is_published_on_hubs', + 'downloads_count', 'posts_count', 'comments_count', 'is_premium', diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index d83bae5c..f3b3bbc6 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -75,34 +75,25 @@ class EpisodeController extends BaseController // Use LIKE operator as a fallback. if (strlen($query) < 4) { $episodes = $episodeModel - ->select('episodes.*, IFNULL(SUM(ape.hits),0) as downloads') - ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left') - ->where('episodes.podcast_id', $this->podcast->id) + ->where('podcast_id', $this->podcast->id) ->like('title', $episodeModel->db->escapeLikeString($query)) ->orLike('description_markdown', $episodeModel->db->escapeLikeString($query)) ->orLike('slug', $episodeModel->db->escapeLikeString($query)) ->orLike('location_name', $episodeModel->db->escapeLikeString($query)) - ->groupBy('episodes.id') ->orderBy('-`published_at`', '', false) ->orderBy('created_at', 'desc'); } else { $episodes = $episodeModel - ->select('episodes.*, IFNULL(SUM(ape.hits),0) as downloads') - ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left') - ->where('episodes.podcast_id', $this->podcast->id) + ->where('podcast_id', $this->podcast->id) ->where( "MATCH (title, description_markdown, slug, location_name) AGAINST ('{$episodeModel->db->escapeString( $query )}')" - ) - ->groupBy('episodes.id'); + ); } } else { $episodes = $episodeModel - ->select('episodes.*, IFNULL(SUM(ape.hits),0) as downloads') - ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left') - ->where('episodes.podcast_id', $this->podcast->id) - ->groupBy('episodes.id') + ->where('podcast_id', $this->podcast->id) ->orderBy('-`published_at`', '', false) ->orderBy('created_at', 'desc'); } diff --git a/themes/cp_admin/episode/list.php b/themes/cp_admin/episode/list.php index a3d3fde0..261bdcc1 100644 --- a/themes/cp_admin/episode/list.php +++ b/themes/cp_admin/episode/list.php @@ -96,7 +96,7 @@ data_table( [ 'header' => lang('Episode.list.downloads'), 'cell' => function ($episode): string { - return downloads_abbr($episode->downloads); + return downloads_abbr($episode->downloads_count); }, ], [