Add cache for user agent/name
This commit is contained in:
1469
src/Managing.Infrastructure.Database/Migrations/20251009200022_AddUserIndexes.Designer.cs
generated
Normal file
1469
src/Managing.Infrastructure.Database/Migrations/20251009200022_AddUserIndexes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Managing.Infrastructure.Databases.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddUserIndexes : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Users_AgentName",
|
||||||
|
table: "Users",
|
||||||
|
column: "AgentName");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Users_AgentName",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1246,6 +1246,8 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AgentName");
|
||||||
|
|
||||||
b.HasIndex("Name")
|
b.HasIndex("Name")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ public class ManagingDbContext : DbContext
|
|||||||
entity.Property(e => e.AgentName).HasMaxLength(255);
|
entity.Property(e => e.AgentName).HasMaxLength(255);
|
||||||
entity.Property(e => e.AvatarUrl).HasMaxLength(500);
|
entity.Property(e => e.AvatarUrl).HasMaxLength(500);
|
||||||
entity.Property(e => e.TelegramChannel).HasMaxLength(255);
|
entity.Property(e => e.TelegramChannel).HasMaxLength(255);
|
||||||
|
|
||||||
|
// Create indexes for performance
|
||||||
|
entity.HasIndex(e => e.Name).IsUnique();
|
||||||
|
entity.HasIndex(e => e.AgentName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure GeneticRequest entity
|
// Configure GeneticRequest entity
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
using Managing.Infrastructure.Databases.PostgreSql.Entities;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -7,29 +9,58 @@ namespace Managing.Infrastructure.Databases.PostgreSql;
|
|||||||
|
|
||||||
public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserRepository
|
public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserRepository
|
||||||
{
|
{
|
||||||
public PostgreSqlUserRepository(ManagingDbContext context, ILogger<SqlQueryLogger> logger, SentrySqlMonitoringService sentryMonitoringService)
|
private readonly ICacheService _cacheService;
|
||||||
|
|
||||||
|
public PostgreSqlUserRepository(ManagingDbContext context, ILogger<SqlQueryLogger> logger,
|
||||||
|
SentrySqlMonitoringService sentryMonitoringService, ICacheService cacheService)
|
||||||
: base(context, logger, sentryMonitoringService)
|
: base(context, logger, sentryMonitoringService)
|
||||||
{
|
{
|
||||||
|
_cacheService = cacheService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User> GetUserByAgentNameAsync(string agentName)
|
public async Task<User> GetUserByAgentNameAsync(string agentName)
|
||||||
{
|
{
|
||||||
return await ExecuteWithLoggingAsync(async () =>
|
return await ExecuteWithLoggingAsync(async () =>
|
||||||
{
|
{
|
||||||
|
// Check cache first for frequently accessed users
|
||||||
|
var cacheKey = $"user_agent_{agentName}";
|
||||||
|
var cachedUser = _cacheService.GetValue<User>(cacheKey);
|
||||||
|
if (cachedUser != null)
|
||||||
|
{
|
||||||
|
return cachedUser;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||||
|
|
||||||
|
// Optimized query with explicit SELECT to avoid loading unnecessary data
|
||||||
var userEntity = await _context.Users
|
var userEntity = await _context.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(u => u.AgentName == agentName)
|
.Where(u => u.AgentName == agentName)
|
||||||
|
.Select(u => new UserEntity
|
||||||
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
Name = u.Name,
|
||||||
|
AgentName = u.AgentName,
|
||||||
|
AvatarUrl = u.AvatarUrl,
|
||||||
|
TelegramChannel = u.TelegramChannel
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync()
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return PostgreSqlMappers.Map(userEntity ?? throw new InvalidOperationException("User not found"));
|
if (userEntity == null)
|
||||||
|
throw new InvalidOperationException($"User with agent name '{agentName}' not found");
|
||||||
|
|
||||||
|
var user = PostgreSqlMappers.Map(userEntity);
|
||||||
|
|
||||||
|
// Cache user for 5 minutes since user data doesn't change frequently
|
||||||
|
_cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Always ensure the connection is closed after the operation
|
|
||||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||||
}
|
}
|
||||||
}, nameof(GetUserByAgentNameAsync), ("agentName", agentName));
|
}, nameof(GetUserByAgentNameAsync), ("agentName", agentName));
|
||||||
@@ -39,20 +70,45 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
|
|||||||
{
|
{
|
||||||
return await ExecuteWithLoggingAsync(async () =>
|
return await ExecuteWithLoggingAsync(async () =>
|
||||||
{
|
{
|
||||||
|
// Check cache first for frequently accessed users
|
||||||
|
var cacheKey = $"user_name_{name}";
|
||||||
|
var cachedUser = _cacheService.GetValue<User>(cacheKey);
|
||||||
|
if (cachedUser != null)
|
||||||
|
{
|
||||||
|
return cachedUser;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||||
|
|
||||||
|
// Optimized query with explicit SELECT to avoid loading unnecessary data
|
||||||
var userEntity = await _context.Users
|
var userEntity = await _context.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(u => u.Name == name)
|
.Where(u => u.Name == name)
|
||||||
|
.Select(u => new UserEntity
|
||||||
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
Name = u.Name,
|
||||||
|
AgentName = u.AgentName,
|
||||||
|
AvatarUrl = u.AvatarUrl,
|
||||||
|
TelegramChannel = u.TelegramChannel
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync()
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return PostgreSqlMappers.Map(userEntity ?? throw new InvalidOperationException("User not found"));
|
if (userEntity == null)
|
||||||
|
throw new InvalidOperationException($"User with name '{name}' not found");
|
||||||
|
|
||||||
|
var user = PostgreSqlMappers.Map(userEntity);
|
||||||
|
|
||||||
|
// Cache user for 5 minutes since user data doesn't change frequently
|
||||||
|
_cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Always ensure the connection is closed after the operation
|
|
||||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||||
}
|
}
|
||||||
}, nameof(GetUserByNameAsync), ("name", name));
|
}, nameof(GetUserByNameAsync), ("name", name));
|
||||||
@@ -92,8 +148,12 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
|
|||||||
.FirstOrDefaultAsync(u => u.Name == user.Name)
|
.FirstOrDefaultAsync(u => u.Name == user.Name)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
string? oldAgentName = null;
|
||||||
if (existingUser != null)
|
if (existingUser != null)
|
||||||
{
|
{
|
||||||
|
// Capture old AgentName before updating for cache invalidation
|
||||||
|
oldAgentName = existingUser.AgentName;
|
||||||
|
|
||||||
// Update existing user
|
// Update existing user
|
||||||
existingUser.AgentName = user.AgentName;
|
existingUser.AgentName = user.AgentName;
|
||||||
existingUser.AvatarUrl = user.AvatarUrl;
|
existingUser.AvatarUrl = user.AvatarUrl;
|
||||||
@@ -113,10 +173,37 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
|
|||||||
// Update the user object with the database-generated ID after save
|
// Update the user object with the database-generated ID after save
|
||||||
await _context.SaveChangesAsync().ConfigureAwait(false);
|
await _context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
user.Id = userEntity.Id;
|
user.Id = userEntity.Id;
|
||||||
|
|
||||||
|
// Cache the new user
|
||||||
|
var newUserNameCacheKey = $"user_name_{user.Name}";
|
||||||
|
var newUserAgentCacheKey = $"user_agent_{user.AgentName}";
|
||||||
|
_cacheService.SaveValue(newUserNameCacheKey, user, TimeSpan.FromMinutes(5));
|
||||||
|
if (!string.IsNullOrEmpty(user.AgentName))
|
||||||
|
{
|
||||||
|
_cacheService.SaveValue(newUserAgentCacheKey, user, TimeSpan.FromMinutes(5));
|
||||||
|
}
|
||||||
return; // Exit early since we already saved
|
return; // Exit early since we already saved
|
||||||
}
|
}
|
||||||
|
|
||||||
await _context.SaveChangesAsync().ConfigureAwait(false);
|
await _context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Invalidate cache for updated user - handle both old and new AgentName
|
||||||
|
var nameCacheKey = $"user_name_{user.Name}";
|
||||||
|
_cacheService.RemoveValue(nameCacheKey);
|
||||||
|
|
||||||
|
// Invalidate old AgentName cache if it existed
|
||||||
|
if (!string.IsNullOrEmpty(oldAgentName))
|
||||||
|
{
|
||||||
|
var oldAgentCacheKey = $"user_agent_{oldAgentName}";
|
||||||
|
_cacheService.RemoveValue(oldAgentCacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate new AgentName cache if it exists
|
||||||
|
if (!string.IsNullOrEmpty(user.AgentName))
|
||||||
|
{
|
||||||
|
var newAgentCacheKey = $"user_agent_{user.AgentName}";
|
||||||
|
_cacheService.RemoveValue(newAgentCacheKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user