using Application.Abstractions.Data; using Application.Abstractions.YouTube; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Infrastructure.YouTube; /// /// 1시간마다 활성 채널의 YouTube 정보를 갱신하여 Redis 캐시에 저장 /// channels.list는 id 파라미터로 최대 50개 채널을 한 번에 조회 가능 (1 unit/요청) /// 채널 50개 이하 → 1시간에 1 unit만 사용 /// internal sealed class YouTubeChannelCacheRefreshService( IServiceScopeFactory scopeFactory, IYouTubeApiService youTubeApi, IYouTubeChannelCache channelCache, ILogger logger ) : BackgroundService { private static readonly TimeSpan RefreshInterval = TimeSpan.FromHours(1); private static readonly TimeSpan InitialDelay = TimeSpan.FromSeconds(15); private const int BatchSize = 50; // YouTube API 최대 50개/요청 protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.Delay(InitialDelay, stoppingToken); logger.LogInformation("[ChannelCache] 채널 캐시 갱신 서비스 시작 — 주기: {Interval}시간", RefreshInterval.TotalHours); while (!stoppingToken.IsCancellationRequested) { try { await RefreshAllChannelsAsync(stoppingToken); } catch (Exception ex) { logger.LogError(ex, "[ChannelCache] 채널 캐시 갱신 중 오류"); } await Task.Delay(RefreshInterval, stoppingToken); } } private async Task RefreshAllChannelsAsync(CancellationToken ct) { // DB에서 활성 채널 SID 목록 조회 (Scoped DbContext) List channelIds; using (var scope = scopeFactory.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); channelIds = await db.Channel.AsNoTracking().Where(c => c.IsActive).Select(c => c.SID).ToListAsync(ct); } if (channelIds.Count == 0) { return; } // 50개씩 배치로 YouTube API 호출 var refreshed = 0; for (var i = 0; i < channelIds.Count; i += BatchSize) { var batch = channelIds.Skip(i).Take(BatchSize).ToList(); var results = await youTubeApi.GetChannelsByIdsAsync(batch, ct); foreach (var info in results) { await channelCache.SetAsync(info); refreshed++; } // 배치 간 1초 대기 (API rate limit 방어) if (i + BatchSize < channelIds.Count) { await Task.Delay(TimeSpan.FromSeconds(1), ct); } } logger.LogInformation("[ChannelCache] {Refreshed}/{Total} 채널 캐시 갱신 완료", refreshed, channelIds.Count); } }