RedisChatLeaderboard.cs 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. using Application.Abstractions.Chat;
  2. using Application.Abstractions.Data;
  3. using Microsoft.EntityFrameworkCore;
  4. using StackExchange.Redis;
  5. namespace Infrastructure.Chat;
  6. /// <summary>
  7. /// Redis Sorted Set 기반 채팅 리더보드 캐시. TTL 5초 — 만료 시 다음 호출이 DB GROUP BY 한 번 실행 후 재시드.
  8. /// 효과: 매 메시지 GROUP BY 회피 (n msg → 1 GROUP BY per TTL window). Trade-off: 최대 5초 stale.
  9. /// </summary>
  10. internal sealed class RedisChatLeaderboard(IConnectionMultiplexer redis) : IChatLeaderboard
  11. {
  12. private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(5);
  13. private static string Key(int sessionId) => $"chat-leader:session:{sessionId}";
  14. public async Task<(int MyScore, int HigherCount)?> GetRankAsync(int sessionId, int memberId, IAppDbContext db, CancellationToken ct = default)
  15. {
  16. var r = redis.GetDatabase();
  17. var key = Key(sessionId);
  18. var member = (RedisValue)memberId.ToString();
  19. // 캐시 미스 → DB GROUP BY 로 재구축. TTL 동안 후속 호출은 Redis only
  20. if (!await r.KeyExistsAsync(key))
  21. {
  22. var rows = await db.MemberExpLog.AsNoTracking()
  23. .Where(e => e.BroadcastSessionID == sessionId)
  24. .GroupBy(e => e.MemberID)
  25. .Select(g => new { MemberId = g.Key, Sum = g.Sum(x => x.Amount) })
  26. .ToListAsync(ct);
  27. if (rows.Count == 0)
  28. {
  29. return null;
  30. }
  31. var entries = rows.Select(x => new SortedSetEntry(x.MemberId.ToString(), x.Sum)).ToArray();
  32. await r.SortedSetAddAsync(key, entries);
  33. await r.KeyExpireAsync(key, CacheTtl);
  34. }
  35. var score = await r.SortedSetScoreAsync(key, member);
  36. if (!score.HasValue)
  37. {
  38. return null;
  39. }
  40. // ZCOUNT (score, +inf] = 본인보다 높은 점수자 수 (Exclude.Start 로 본인 점수 제외)
  41. var higher = await r.SortedSetLengthAsync(key, score.Value, double.PositiveInfinity, exclude: Exclude.Start);
  42. return ((int)score.Value, (int)higher);
  43. }
  44. }