YouTubeChannelCacheRefreshService.cs 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.YouTube;
  3. using Microsoft.EntityFrameworkCore;
  4. using Microsoft.Extensions.DependencyInjection;
  5. using Microsoft.Extensions.Hosting;
  6. using Microsoft.Extensions.Logging;
  7. namespace Infrastructure.YouTube;
  8. /// <summary>
  9. /// 1시간마다 활성 채널의 YouTube 정보를 갱신하여 Redis 캐시에 저장
  10. /// channels.list는 id 파라미터로 최대 50개 채널을 한 번에 조회 가능 (1 unit/요청)
  11. /// 채널 50개 이하 → 1시간에 1 unit만 사용
  12. /// </summary>
  13. internal sealed class YouTubeChannelCacheRefreshService(
  14. IServiceScopeFactory scopeFactory,
  15. IYouTubeApiService youTubeApi,
  16. IYouTubeChannelCache channelCache,
  17. ILogger<YouTubeChannelCacheRefreshService> logger
  18. ) : BackgroundService
  19. {
  20. private static readonly TimeSpan RefreshInterval = TimeSpan.FromHours(1);
  21. private static readonly TimeSpan InitialDelay = TimeSpan.FromSeconds(15);
  22. private const int BatchSize = 50; // YouTube API 최대 50개/요청
  23. protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  24. {
  25. await Task.Delay(InitialDelay, stoppingToken);
  26. logger.LogInformation("[ChannelCache] 채널 캐시 갱신 서비스 시작 — 주기: {Interval}시간", RefreshInterval.TotalHours);
  27. while (!stoppingToken.IsCancellationRequested)
  28. {
  29. try
  30. {
  31. await RefreshAllChannelsAsync(stoppingToken);
  32. }
  33. catch (Exception ex)
  34. {
  35. logger.LogError(ex, "[ChannelCache] 채널 캐시 갱신 중 오류");
  36. }
  37. await Task.Delay(RefreshInterval, stoppingToken);
  38. }
  39. }
  40. private async Task RefreshAllChannelsAsync(CancellationToken ct)
  41. {
  42. // DB에서 활성 채널 SID 목록 조회 (Scoped DbContext)
  43. List<string> channelIds;
  44. using (var scope = scopeFactory.CreateScope())
  45. {
  46. var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
  47. channelIds = await db.Channel.AsNoTracking().Where(c => c.IsActive).Select(c => c.SID).ToListAsync(ct);
  48. }
  49. if (channelIds.Count == 0)
  50. {
  51. return;
  52. }
  53. // 50개씩 배치로 YouTube API 호출
  54. var refreshed = 0;
  55. for (var i = 0; i < channelIds.Count; i += BatchSize)
  56. {
  57. var batch = channelIds.Skip(i).Take(BatchSize).ToList();
  58. var results = await youTubeApi.GetChannelsByIdsAsync(batch, ct);
  59. foreach (var info in results)
  60. {
  61. await channelCache.SetAsync(info);
  62. refreshed++;
  63. }
  64. // 배치 간 1초 대기 (API rate limit 방어)
  65. if (i + BatchSize < channelIds.Count)
  66. {
  67. await Task.Delay(TimeSpan.FromSeconds(1), ct);
  68. }
  69. }
  70. logger.LogInformation("[ChannelCache] {Refreshed}/{Total} 채널 캐시 갱신 완료", refreshed, channelIds.Count);
  71. }
  72. }