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.
This commit is contained in:
Yassine Doghri 2024-12-28 15:23:02 +00:00
parent f288a750f5
commit f981937645
7 changed files with 91 additions and 17 deletions

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Commands;
use App\Models\EpisodeModel;
use CodeIgniter\CLI\BaseCommand;
class EpisodesComputeDownloads extends BaseCommand
{
/**
* The Command's Group
*
* @var string
*/
protected $group = 'Episodes';
/**
* The Command's Name
*
* @var string
*/
protected $name = 'episodes:compute-downloads';
/**
* The Command's Description
*
* @var string
*/
protected $description = "Calculates all episodes downloads and stores results in episodes' downloads_count field.";
/**
* Actually execute a command.
*/
public function run(array $params): void
{
$episodesModel = new EpisodeModel();
$query = $episodesModel->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();
}
}

View File

@ -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');
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddEpisodeDownloadsCount extends Migration
{
public function up(): void
{
$fields = [
'downloads_count' => [
'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');
}
}

View File

@ -72,9 +72,9 @@ use RuntimeException;
* @property string|null $location_geo
* @property string|null $location_osm
* @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
@ -112,8 +112,6 @@ class Episode extends Entity
protected ?Chapters $chapters = null;
protected int $downloads = 0;
/**
* @var Person[]|null
*/
@ -171,6 +169,7 @@ class Episode extends Entity
'location_geo' => '?string',
'location_osm' => '?string',
'is_published_on_hubs' => 'boolean',
'downloads_count' => 'integer',
'posts_count' => 'integer',
'comments_count' => 'integer',
'is_premium' => 'boolean',

View File

@ -88,6 +88,7 @@ class EpisodeModel extends UuidModel
'location_geo',
'location_osm',
'is_published_on_hubs',
'downloads_count',
'posts_count',
'comments_count',
'is_premium',

View File

@ -70,34 +70,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', $podcast->id)
->where('podcast_id', $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', $podcast->id)
->where('podcast_id', $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', $podcast->id)
->groupBy('episodes.id')
->where('podcast_id', $podcast->id)
->orderBy('-`published_at`', '', false)
->orderBy('created_at', 'desc');
}

View File

@ -92,7 +92,7 @@ data_table(
[
'header' => lang('Episode.list.downloads'),
'cell' => function ($episode): string {
return downloads_abbr($episode->downloads);
return downloads_abbr($episode->downloads_count);
},
],
[