chore(perf): improve podcast adding performance

This commit is contained in:
Phan An 2024-06-07 14:11:45 +02:00
parent 26ce2fe916
commit 8bd499dd85
4 changed files with 32 additions and 24 deletions

View file

@ -16,7 +16,6 @@ use Illuminate\Support\Str;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use PhanAn\Poddle\Values\CategoryCollection; use PhanAn\Poddle\Values\CategoryCollection;
use PhanAn\Poddle\Values\ChannelMetadata; use PhanAn\Poddle\Values\ChannelMetadata;
use PhanAn\Poddle\Values\Episode as EpisodeDTO;
/** /**
* @property-read string $id * @property-read string $id
@ -82,21 +81,6 @@ class Podcast extends Model
->withTimestamps(); ->withTimestamps();
} }
public function addEpisodeByDTO(EpisodeDTO $dto): Episode
{
return $this->episodes()->create([
'title' => $dto->title,
'lyrics' => '',
'path' => $dto->enclosure->url,
'created_at' => $dto->metadata->pubDate ?: now(),
'episode_metadata' => $dto->metadata,
'episode_guid' => $dto->guid,
'length' => $dto->metadata->duration ?? 0,
'mtime' => time(),
'is_public' => true,
]);
}
/** @return array<mixed> */ /** @return array<mixed> */
public function toSearchableArray(): array public function toSearchableArray(): array
{ {

View file

@ -99,9 +99,7 @@ class Song extends Model
protected static function booted(): void protected static function booted(): void
{ {
static::creating(static function (self $song): void { static::creating(static fn (Song $song) => $song->id ??= Str::uuid()->toString());
$song->id ??= Str::uuid()->toString();
});
} }
public static function query(?PlayableType $type = null, ?User $user = null): SongBuilder public static function query(?PlayableType $type = null, ?User $user = null): SongBuilder

View file

@ -17,6 +17,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use PhanAn\Poddle\Poddle; use PhanAn\Poddle\Poddle;
use PhanAn\Poddle\Values\Episode as EpisodeValue; use PhanAn\Poddle\Values\Episode as EpisodeValue;
use PhanAn\Poddle\Values\EpisodeCollection; use PhanAn\Poddle\Values\EpisodeCollection;
@ -118,13 +119,38 @@ class PodcastService
private function synchronizeEpisodes(Podcast $podcast, EpisodeCollection $episodeCollection): void private function synchronizeEpisodes(Podcast $podcast, EpisodeCollection $episodeCollection): void
{ {
$existingEpisodeGuids = $this->songRepository->getEpisodeGuidsByPodcast($podcast); $existingEpisodeGuids = $this->songRepository->getEpisodeGuidsByPodcast($podcast);
$records = [];
$ids = [];
/** @var EpisodeValue $episodeValue */ /** @var EpisodeValue $episodeValue */
foreach ($episodeCollection as $episodeValue) { foreach ($episodeCollection as $episodeValue) {
if (!in_array($episodeValue->guid->value, $existingEpisodeGuids, true)) { if (!in_array($episodeValue->guid->value, $existingEpisodeGuids, true)) {
$podcast->addEpisodeByDTO($episodeValue); $id = Str::uuid()->toString();
$ids[] = $id;
$records[] = [
'id' => $id,
'podcast_id' => $podcast->id,
'title' => $episodeValue->title,
'lyrics' => '',
'path' => $episodeValue->enclosure->url,
'created_at' => $episodeValue->metadata->pubDate ?: now(),
'updated_at' => $episodeValue->metadata->pubDate ?: now(),
'episode_metadata' => $episodeValue->metadata->toJson(),
'episode_guid' => $episodeValue->guid,
'length' => $episodeValue->metadata->duration ?? 0,
'mtime' => time(),
'is_public' => true,
];
} }
} }
// We use insert() instead of $podcast->episodes()->createMany() for better performance,
// as the latter would trigger a separate query for each episode.
Episode::insert($records);
// Since insert() doesn't trigger model events, Scout operations will not be called.
// We have to manually update the search index.
Episode::query()->whereIn('id', $ids)->searchable();
} }
private function subscribeUserToPodcast(User $user, Podcast $podcast): void private function subscribeUserToPodcast(User $user, Podcast $podcast): void

View file

@ -149,13 +149,13 @@ export const songStore = {
getShareableUrl: (song: Playable) => `${window.BASE_URL}#/song/${song.id}`, getShareableUrl: (song: Playable) => `${window.BASE_URL}#/song/${song.id}`,
syncWithVault (playables: MaybeArray<Playable>) { syncWithVault (playables: MaybeArray<Playable>) {
return arrayify(playables).map(song => { return arrayify(playables).map(playable => {
let local = this.byId(song.id) let local = this.byId(playable.id)
if (local) { if (local) {
merge(local, song) merge(local, playable)
} else { } else {
local = reactive(song) local = reactive(playable)
local.playback_state = 'Stopped' local.playback_state = 'Stopped'
this.watchPlayCount(local) this.watchPlayCount(local)
this.vault.set(local.id, local) this.vault.set(local.id, local)