Athena.Cache.SourceGenerator 1.1.9

dotnet add package Athena.Cache.SourceGenerator --version 1.1.9
                    
NuGet\Install-Package Athena.Cache.SourceGenerator -Version 1.1.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Athena.Cache.SourceGenerator" Version="1.1.9">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Athena.Cache.SourceGenerator" Version="1.1.9" />
                    
Directory.Packages.props
<PackageReference Include="Athena.Cache.SourceGenerator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Athena.Cache.SourceGenerator --version 1.1.9
                    
#r "nuget: Athena.Cache.SourceGenerator, 1.1.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Athena.Cache.SourceGenerator@1.1.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Athena.Cache.SourceGenerator&version=1.1.9
                    
Install as a Cake Addin
#tool nuget:?package=Athena.Cache.SourceGenerator&version=1.1.9
                    
Install as a Cake Tool

๐Ÿ›๏ธ Athena.Cache

CI codecov NuGet Core Downloads NuGet Redis Downloads License: MIT

Smart caching library for ASP.NET Core with automatic query parameter key generation and table-based cache invalidation.

Athena.Cache๋Š” ASP.NET Core ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ์ง€๋Šฅํ˜• ์บ์‹ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ์บ์‹œ ํ‚ค๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ์บ์‹œ๋ฅผ ์ž๋™์œผ๋กœ ๋ฌดํšจํ™”ํ•ฉ๋‹ˆ๋‹ค.

โœจ ์ฃผ์š” ๊ธฐ๋Šฅ

  • ๐ŸŽฏ Source Generator: ์ปดํŒŒ์ผ ํƒ€์ž„์— ์บ์‹œ ์„ค์ • ์ž๋™ ์ƒ์„ฑ, AOT ํ˜ธํ™˜
  • ๐Ÿ”‘ ์ž๋™ ์บ์‹œ ํ‚ค ์ƒ์„ฑ: ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ โ†’ SHA256 ํ•ด์‹œ ํ‚ค ์ž๋™ ๋ณ€ํ™˜
  • ๐Ÿ—‚๏ธ ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ๋ฌดํšจํ™”: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ์บ์‹œ ์ž๋™ ์‚ญ์ œ
  • ๐Ÿ—๏ธ Convention ๊ธฐ๋ฐ˜ ์ถ”๋ก : ์ปจํŠธ๋กค๋Ÿฌ๋ช…์—์„œ ํ…Œ์ด๋ธ”๋ช… ์ž๋™ ์ถ”๋ก , ์ค‘๋ณต ์„ ์–ธ ๋ถˆํ•„์š”
  • ๐Ÿš€ ๋‹ค์ค‘ ๋ฐฑ์—”๋“œ ์ง€์›: MemoryCache, Redis, Valkey ์ง€์›
  • ๐ŸŽจ ์„ ์–ธ์  ์บ์‹ฑ: [AthenaCache], [CacheInvalidateOn] ์–ดํŠธ๋ฆฌ๋ทฐํŠธ
  • โšก ๊ณ ์„ฑ๋Šฅ: ๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ ํ™˜๊ฒฝ์— ์ตœ์ ํ™”, Primary Constructor ์‚ฌ์šฉ
  • ๐Ÿง  ์ œ๋กœ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น: 5๋‹จ๊ณ„ ์ตœ์ ํ™”๋กœ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น 90-98% ๊ฐ์†Œ
  • ๐Ÿ”„ ์ž๋™ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ: ์‹ค์‹œ๊ฐ„ GC ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์ž๋™ ์บ์‹œ ์ •๋ฆฌ
  • ๐Ÿ”ง ์‰ฌ์šด ํ†ตํ•ฉ: ๋‹จ์ผ ํŒจํ‚ค์ง€ ์„ค์น˜๋กœ ๋ชจ๋“  ๊ธฐ๋Šฅ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ๐Ÿงช ์™„์ „ํ•œ ํ…Œ์ŠคํŠธ: ํฌ๊ด„์ ์ธ ๋‹จ์œ„ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘

์„ค์น˜

# ๊ธฐ๋ณธ ํŒจํ‚ค์ง€ (MemoryCache + Source Generator ํฌํ•จ)
dotnet add package Athena.Cache.Core

# Redis ์ง€์› (์„ ํƒ์‚ฌํ•ญ)
dotnet add package Athena.Cache.Redis

๐ŸŽฏ ํ†ตํ•ฉ ํŒจํ‚ค์ง€: Athena.Cache.Core๋งŒ ์„ค์น˜ํ•˜๋ฉด Source Generator๊ฐ€ ์ž๋™์œผ๋กœ ํฌํ•จ๋˜์–ด ์ปดํŒŒ์ผ ํƒ€์ž„์— ์บ์‹œ ์„ค์ •์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์„ค์ •

// Program.cs
using Athena.Cache.Core.Extensions;

var builder = WebApplication.CreateBuilder(args);

// ๊ฐœ๋ฐœ ํ™˜๊ฒฝ (MemoryCache)
builder.Services.AddAthenaCacheComplete(options => {
    options.Namespace = "MyApp_DEV";
    options.DefaultExpirationMinutes = 30;
    options.Logging.LogCacheHitMiss = true; // ์บ์‹œ ํžˆํŠธ/๋ฏธ์Šค ๋กœ๊น…
});

// ์šด์˜ ํ™˜๊ฒฝ (Redis)
builder.Services.AddAthenaCacheRedisComplete(
    athena => {
        athena.Namespace = "MyApp_PROD";
        athena.DefaultExpirationMinutes = 60;
    },
    redis => {
        redis.ConnectionString = "localhost:6379";
        redis.DatabaseId = 1;
    });

var app = builder.Build();

// ๐Ÿ”ง ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€ (์ค‘์š”: ๋ผ์šฐํŒ… ํ›„, ์ปจํŠธ๋กค๋Ÿฌ ์ „์— ์ถ”๊ฐ€)
app.UseRouting();
app.UseAthenaCache();  // ๋ผ์šฐํŒ… ํ›„์— ์ถ”๊ฐ€
app.MapControllers();

app.Run();

์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์‚ฌ์šฉ

using Athena.Cache.Core.Attributes;
using Athena.Cache.Core.Enums;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _userService;

    public UsersController(IUserService userService)
    {
        _userService = userService;
    }

    [HttpGet]
    [AthenaCache(ExpirationMinutes = 30)]
    [CacheInvalidateOn("Users")]
    public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers(
        [FromQuery] string? search = null,
        [FromQuery] int page = 1)
    {
        // ๐Ÿš€ Source Generator๊ฐ€ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ด ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์Šค์บ”ํ•˜์—ฌ
        // ์บ์‹œ ์„ค์ •์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ณ„๋„ ์„ค์ • ๋ถˆํ•„์š”!
        var users = await _userService.GetUsersAsync(search, page);
        return Ok(users);
    }

    [HttpGet("{id}")]
    [AthenaCache(ExpirationMinutes = 60)]
    [CacheInvalidateOn("Users")]
    [CacheInvalidateOn("Orders", InvalidationType.Pattern, "User_*")]
    public async Task<ActionResult<UserDto>> GetUser(int id)
    {
        var user = await _userService.GetUserByIdAsync(id);
        if (user == null) return NotFound();
        return Ok(user);
    }

    [HttpPost]
    public async Task<ActionResult<UserDto>> CreateUser([FromBody] User user)
    {
        var createdUser = await _userService.CreateUserAsync(user);
        // Users ํ…Œ์ด๋ธ” ๊ด€๋ จ ์บ์‹œ๊ฐ€ ์ž๋™ ๋ฌดํšจํ™”๋จ
        return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, createdUser);
    }

    // ์บ์‹œ ๋น„ํ™œ์„ฑํ™” ์˜ˆ์ œ
    [HttpGet("no-cache")]
    [NoCache]  // ์ด ์•ก์…˜์€ ์บ์‹ฑํ•˜์ง€ ์•Š์Œ
    public async Task<ActionResult<IEnumerable<UserDto>>> GetUsersWithoutCache()
    {
        var users = await _userService.GetUsersAsync();
        return Ok(users);
    }
}

๐Ÿ“Š ์บ์‹œ ์ƒํƒœ ํ™•์ธ

Athena.Cache๋Š” HTTP ํ—ค๋”๋ฅผ ํ†ตํ•ด ์บ์‹œ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

# ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ (์บ์‹œ ๋ฏธ์Šค)
curl -v http://localhost:5000/api/users
# ์‘๋‹ต ํ—ค๋”: X-Athena-Cache: MISS

# ๋‘ ๋ฒˆ์งธ ์š”์ฒญ (์บ์‹œ ํžˆํŠธ)
curl -v http://localhost:5000/api/users  
# ์‘๋‹ต ํ—ค๋”: X-Athena-Cache: HIT

๐ŸŽฏ Convention ๊ธฐ๋ฐ˜ ์บ์‹ฑ (๊ถŒ์žฅ)

Convention ๊ธฐ๋ฐ˜ ์บ์‹ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด CacheInvalidateOn ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ๋ฐ˜๋ณต ์„ ์–ธํ•  ํ•„์š” ์—†์ด, ์ปจํŠธ๋กค๋Ÿฌ๋ช…์—์„œ ํ…Œ์ด๋ธ”๋ช…์„ ์ž๋™์œผ๋กœ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์„ค์ • (Convention ํ™œ์„ฑํ™”)

// Program.cs
builder.Services.AddAthenaCacheComplete(options => {
    options.Namespace = "MyApp";
    options.DefaultExpirationMinutes = 30;
    
    // Convention ๊ธฐ๋ฐ˜ ํ…Œ์ด๋ธ”๋ช… ์ถ”๋ก  ํ™œ์„ฑํ™” (๊ธฐ๋ณธ๊ฐ’: true)
    options.Convention.Enabled = true;
    options.Convention.UsePluralizer = true; // UsersController โ†’ Users
});

๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ๋ฒ•

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase  // ์ž๋™์œผ๋กœ "Users" ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๊ฒฐ
{
    // โœ… Convention ์‚ฌ์šฉ: CacheInvalidateOn ์ค‘๋ณต ์„ ์–ธ ๋ถˆํ•„์š”
    [HttpGet]
    [AthenaCache(ExpirationMinutes = 30)]
    public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers()
    {
        // ์ž๋™์œผ๋กœ Users ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ์บ์‹œ ๋ฌดํšจํ™”
        return Ok(await _userService.GetUsersAsync());
    }

    [HttpPost]
    public async Task<ActionResult<UserDto>> CreateUser([FromBody] User user)
    {
        // ์ž๋™์œผ๋กœ Users ํ…Œ์ด๋ธ” ๊ด€๋ จ ์บ์‹œ ๋ฌดํšจํ™”
        var createdUser = await _userService.CreateUserAsync(user);
        return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, createdUser);
    }

    // ์ถ”๊ฐ€ ํ…Œ์ด๋ธ”์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋ช…์‹œ์  ์„ ์–ธ
    [HttpGet("{id}")]
    [AthenaCache(ExpirationMinutes = 60)]
    [CacheInvalidateOn("Orders", InvalidationType.Pattern, "User_*")] // Users๋Š” ์ž๋™, Orders๋งŒ ๋ช…์‹œ
    public async Task<ActionResult<UserDto>> GetUser(int id)
    {
        return Ok(await _userService.GetUserByIdAsync(id));
    }
}

๊ณ ๊ธ‰ Convention ์„ค์ •

// ๋‹ค์ค‘ ํ…Œ์ด๋ธ” ๋งคํ•‘
builder.Services.AddAthenaCacheComplete(options => {
    options.Convention.ControllerTableMappings = new Dictionary<string, string[]>
    {
        ["UsersController"] = ["Users", "UserProfiles"],
        ["OrdersController"] = ["Orders", "OrderItems", "Users"]
    };
    
    // ์ปค์Šคํ…€ ์ถ”๋ก  ํ•จ์ˆ˜
    options.Convention.CustomMultiTableInferrer = controllerName =>
    {
        // UsersOrdersController โ†’ ["Users", "Orders"]
        if (controllerName.Contains("Users") && controllerName.Contains("Orders"))
            return ["Users", "Orders"];
            
        return [controllerName.Replace("Controller", "")];
    };
});

Convention vs ๋ช…์‹œ์  ์„ ์–ธ ๋น„๊ต

// โŒ ๊ธฐ์กด ๋ฐฉ์‹: ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— ์ค‘๋ณต ์„ ์–ธ
public class UsersController : ControllerBase
{
    [HttpGet]
    [AthenaCache(ExpirationMinutes = 30)]
    [CacheInvalidateOn("Users")]  // ์ค‘๋ณต
    public async Task<IActionResult> GetUsers() { ... }

    [HttpPost]
    [CacheInvalidateOn("Users")]  // ์ค‘๋ณต
    public async Task<IActionResult> CreateUser() { ... }

    [HttpPut("{id}")]
    [CacheInvalidateOn("Users")]  // ์ค‘๋ณต
    public async Task<IActionResult> UpdateUser() { ... }
}

// โœ… Convention ๋ฐฉ์‹: ์ž๋™ ์ถ”๋ก ์œผ๋กœ ๊ฐ„์†Œํ™”
public class UsersController : ControllerBase
{
    [HttpGet]
    [AthenaCache(ExpirationMinutes = 30)]
    public async Task<IActionResult> GetUsers() { ... }  // Users ํ…Œ์ด๋ธ” ์ž๋™ ์—ฐ๊ฒฐ

    [HttpPost]
    public async Task<IActionResult> CreateUser() { ... }  // Users ํ…Œ์ด๋ธ” ์ž๋™ ๋ฌดํšจํ™”

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateUser() { ... }  // Users ํ…Œ์ด๋ธ” ์ž๋™ ๋ฌดํšจํ™”
}

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ

๊ธฐ์กด ํ”„๋กœ์ ํŠธ์—์„œ Convention์œผ๋กœ ์ „ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•:

  1. Convention ํ™œ์„ฑํ™”
options.Convention.Enabled = true;
  1. ์ค‘๋ณต ์„ ์–ธ ์ œ๊ฑฐ (์„ ํƒ์‚ฌํ•ญ)
// ์ œ๊ฑฐ ๊ฐ€๋Šฅ: ์ปจํŠธ๋กค๋Ÿฌ๋ช…๊ณผ ๋™์ผํ•œ ํ…Œ์ด๋ธ”์˜ CacheInvalidateOn
[CacheInvalidateOn("Users")] // UsersController์—์„œ ์ œ๊ฑฐ ๊ฐ€๋Šฅ
  1. ์ ์ง„์  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
    • Convention๊ณผ ๋ช…์‹œ์  ์„ ์–ธ์ด ํ•จ๊ป˜ ์ž‘๋™
    • ๋ช…์‹œ์  ์„ ์–ธ์ด ์šฐ์„  ์ ์šฉ๋˜์–ด ๊ธฐ์กด ์ฝ”๋“œ ์˜ํ–ฅ ์—†์Œ

Convention ๋น„ํ™œ์„ฑํ™”

ํŠน์ • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ Convention ์ถ”๋ก ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  ์ˆ˜๋™์œผ๋กœ ์บ์‹œ๋ฅผ ์ œ์–ดํ•˜๋ ค๋Š” ๊ฒฝ์šฐ:

๋ฐฉ๋ฒ• 1: NoConventionInvalidation ์–ดํŠธ๋ฆฌ๋ทฐํŠธ
[ApiController]
[NoConventionInvalidation]  // Convention ์ถ”๋ก  ๋น„ํ™œ์„ฑํ™”
public class ReportsController : ControllerBase
{
    private readonly ICacheInvalidator _cacheInvalidator;

    [HttpGet]
    [AthenaCache(ExpirationMinutes = 120)]
    public async Task<IActionResult> GetReport()
    {
        // ์บ์‹ฑ๋งŒ ํ•˜๊ณ , ์ž๋™ ๋ฌดํšจํ™” ์—†์Œ
        return Ok(data);
    }

    [HttpPost("refresh")]
    public async Task<IActionResult> RefreshData()
    {
        // ์ˆ˜๋™์œผ๋กœ ์บ์‹œ ๋ฌดํšจํ™”
        await _cacheInvalidator.InvalidateByPatternAsync("*GetReport*");
        return Ok();
    }
}
๋ฐฉ๋ฒ• 2: ์ „์—ญ ์„ค์ •์œผ๋กœ ์ œ์™ธ
builder.Services.AddAthenaCacheComplete(options => {
    options.Convention.Enabled = true;
    options.Convention.ExcludedControllers.Add("ReportsController");
    options.Convention.ExcludedControllers.Add("AnalyticsController");
});
๋ฐฉ๋ฒ• 3: ์ „์ฒด Convention ๋น„ํ™œ์„ฑํ™”
builder.Services.AddAthenaCacheComplete(options => {
    options.Convention.Enabled = false;  // ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋น„ํ™œ์„ฑํ™”
});

์ˆ˜๋™ ์บ์‹œ ์ œ์–ด ํŒจํ„ด

public class ManualCacheController : ControllerBase
{
    private readonly ICacheInvalidator _invalidator;

    [HttpGet]
    [AthenaCache]
    [NoConventionInvalidation]
    public async Task<IActionResult> GetData() { ... }

    [HttpPost]
    [NoConventionInvalidation]
    public async Task<IActionResult> UpdateData()
    {
        // ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰
        var result = await _service.UpdateAsync();
        
        // ์„ ํƒ์  ์บ์‹œ ๋ฌดํšจํ™”
        if (result.ShouldInvalidateCache)
        {
            await _invalidator.InvalidateByPatternAsync("GetData*");
        }
        
        return Ok(result);
    }
}

๐Ÿ› ๏ธ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

์ปค์Šคํ…€ ์บ์‹œ ํ‚ค

[AthenaCache(
    ExpirationMinutes = 45,
    CustomKeyPrefix = "UserStats",
    ExcludeParameters = new[] { "debug", "trace" }
)]
public async Task<IActionResult> GetUserStatistics(int userId, bool debug = false)
{
    // debug ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์บ์‹œ ํ‚ค ์ƒ์„ฑ์—์„œ ์ œ์™ธ๋จ
    return Ok(stats);
}

ํŒจํ„ด ๊ธฐ๋ฐ˜ ๋ฌดํšจํ™”

[CacheInvalidateOn("Users", InvalidationType.All)]                      // ๋ชจ๋“  Users ๊ด€๋ จ ์บ์‹œ
[CacheInvalidateOn("Orders", InvalidationType.Pattern, "User_*")]       // User_* ํŒจํ„ด ์บ์‹œ
[CacheInvalidateOn("Products", InvalidationType.Related, "Categories")] // ์—ฐ๊ด€ ํ…Œ์ด๋ธ”๊นŒ์ง€
public async Task<IActionResult> GetUserOrders(int userId) { ... }

์ˆ˜๋™ ์บ์‹œ ๊ด€๋ฆฌ

public class UserService
{
    private readonly IAthenaCache _cache;
    private readonly ICacheInvalidator _invalidator;

    public UserService(IAthenaCache cache, ICacheInvalidator invalidator)
    {
        _cache = cache;
        _invalidator = invalidator;
    }

    public async Task InvalidateUserCaches(int userId)
    {
        // ํŠน์ • ์‚ฌ์šฉ์ž ๊ด€๋ จ ์บ์‹œ๋งŒ ์‚ญ์ œ
        await _invalidator.InvalidateByPatternAsync($"User_{userId}_*");
    }

    public async Task<CacheStatistics> GetCacheStats()
    {
        return await _cache.GetStatisticsAsync();
    }
}

๐Ÿ“Š ์„ฑ๋Šฅ

๐Ÿš€ ๊ธฐ๋ณธ ์„ฑ๋Šฅ ์ง€ํ‘œ

  • ๋†’์€ ์ฒ˜๋ฆฌ๋Ÿ‰: Redis ๊ธฐ์ค€ 10,000+ requests/second
  • ๋‚ฎ์€ ์ง€์—ฐ์‹œ๊ฐ„: ์บ์‹œ ํ‚ค ์ƒ์„ฑ 1ms ๋ฏธ๋งŒ
  • ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ: ์ตœ์ ํ™”๋œ ์ง๋ ฌํ™” ๋ฐ ์••์ถ•
  • ํ™•์žฅ ๊ฐ€๋Šฅ: ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค ๋ถ„์‚ฐ ๋ฌดํšจํ™” ์ง€์›

๐Ÿง  ์ œ๋กœ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ์ตœ์ ํ™” ๊ฒฐ๊ณผ

์ตœ์ ํ™” ์˜์—ญ ๊ฐœ์„ ์œจ ์ฃผ์š” ๊ธฐ๋ฒ•
๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น 90-98% ๊ฐ์†Œ ์ปฌ๋ ‰์…˜ ํ’€๋ง, Span/Memory, ์บ์‹ฑ
GC ์••๋ฐ• ~90% ๊ฐ์†Œ ๊ฐ’ ํƒ€์ž…, ๋ฌธ์ž์—ด ์ธํ„ฐ๋‹, ์ž๋™ ์ •๋ฆฌ
๋ฌธ์ž์—ด ์ฒ˜๋ฆฌ ~98% ๊ฐ์†Œ StringBuilder ํ’€๋ง, ์•ฝํ•œ ์ฐธ์กฐ ์ธํ„ฐ๋‹
์ปฌ๋ ‰์…˜ ์—ฐ์‚ฐ ~95% ๊ฐ์†Œ LINQ ์ œ๊ฑฐ, ์ˆ˜๋™ ๋ฃจํ”„, ArrayPool
๋ฐ•์‹ฑ/์–ธ๋ฐ•์‹ฑ 100% ์ œ๊ฑฐ ๊ฐ’ ํƒ€์ž… ๊ตฌ์กฐ์ฒด, ์ œ๋„ค๋ฆญ ํ†ต๊ณ„

๐Ÿ“ˆ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๋ฐ๋ชจ

# ์„ฑ๋Šฅ ๋น„๊ต ํ…Œ์ŠคํŠธ ์‹คํ–‰
curl -X POST "http://localhost:5000/api/ZeroMemoryDemo/performance-comparison?iterations=10000"

# ๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ ํ™•์ธ
curl "http://localhost:5000/api/ZeroMemoryDemo/memory-status"

# ๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ• ์‹œ๋ฎฌ๋ ˆ์ด์…˜
curl -X POST "http://localhost:5000/api/ZeroMemoryDemo/memory-pressure-test"

๐Ÿ“‹ ์ƒ์„ธ ๊ฐ€์ด๋“œ: ์ œ๋กœ ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™” ๊ธฐ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ docs/ZeroMemoryOptimization.md๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๐Ÿ”ง ์„ค์ • ์˜ต์…˜

์ „์—ญ ์„ค์ •

services.AddAthenaCacheComplete(options => {
    options.Namespace = "MyApp";              // ๋„ค์ž„์ŠคํŽ˜์ด์Šค (ํ™˜๊ฒฝ ๋ถ„๋ฆฌ)
    options.VersionKey = "v1.0";              // ๋ฒ„์ „ ํ‚ค (๋ฐฐํฌ ์‹œ ์บ์‹œ ๋ถ„๋ฆฌ)
    options.DefaultExpirationMinutes = 30;    // ๊ธฐ๋ณธ ๋งŒ๋ฃŒ ์‹œ๊ฐ„
    options.MaxRelatedDepth = 3;              // ์—ฐ์‡„ ๋ฌดํšจํ™” ์ตœ๋Œ€ ๊นŠ์ด
    options.StartupCacheCleanup = CleanupMode.ExpireShorten; // ์‹œ์ž‘ ์‹œ ์ •๋ฆฌ ๋ฐฉ์‹
    
    // ๋กœ๊น… ์„ค์ •
    options.Logging.LogCacheHitMiss = true;   // ํžˆํŠธ/๋ฏธ์Šค ๋กœ๊น…
    options.Logging.LogInvalidation = true;   // ๋ฌดํšจํ™” ๋กœ๊น…
    
    // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    options.ErrorHandling.SilentFallback = true; // ์กฐ์šฉํ•œ ํด๋ฐฑ
});

Redis ์„ค์ •

services.AddAthenaCacheRedisComplete(
    athena => { /* Athena ์„ค์ • */ },
    redis => {
        redis.ConnectionString = "localhost:6379";
        redis.DatabaseId = 1;
        redis.KeyPrefix = "MyApp";
        redis.BatchSize = 1000;
        redis.ConnectTimeoutSeconds = 5;
        redis.RetryCount = 3;
    });

๐Ÿงช ํ…Œ์ŠคํŠธ

# ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰
dotnet test

# ์ปค๋ฒ„๋ฆฌ์ง€ ํฌํ•จ
dotnet test --collect:"XPlat Code Coverage"

# ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (Redis ํ•„์š”)
docker run -d -p 6379:6379 redis:7-alpine
dotnet test --filter Category=Integration

๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜

ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ

  • ๐ŸŽฏ Source Generator: ์ปดํŒŒ์ผ ํƒ€์ž„์— Controller ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์Šค์บ”ํ•˜์—ฌ ์บ์‹œ ์„ค์ • ์ž๋™ ์ƒ์„ฑ
  • ICacheConfigurationRegistry: ์ƒ์„ฑ๋œ ์บ์‹œ ์„ค์ •์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ
  • ICacheKeyGenerator: ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ โ†’ ์บ์‹œ ํ‚ค ๋ณ€ํ™˜
  • ICacheInvalidator: ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ์บ์‹œ ๋ฌดํšจํ™” ๊ด€๋ฆฌ
  • IAthenaCache: ์บ์‹œ ์ œ๊ณต์ž ์ถ”์ƒํ™” (Memory/Redis/Valkey)
  • AthenaCacheMiddleware: HTTP ์š”์ฒญ ๊ฐ€๋กœ์ฑ„๊ธฐ ๋ฐ ์บ์‹ฑ (Primary Constructor ์‚ฌ์šฉ)

๋™์ž‘ ์›๋ฆฌ

  1. ๐Ÿ”ง ์ปดํŒŒ์ผ ํƒ€์ž„: Source Generator๊ฐ€ Controller๋ฅผ ์Šค์บ”ํ•˜์—ฌ ์บ์‹œ ์„ค์ • ํด๋ž˜์Šค ์ž๋™ ์ƒ์„ฑ
  2. ๐Ÿš€ ๋Ÿฐํƒ€์ž„ ์ดˆ๊ธฐํ™”: ์ƒ์„ฑ๋œ ์„ค์ •์ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉ, ์—†์œผ๋ฉด Reflection ๋ฐฑ์—… ์‚ฌ์šฉ
  3. ๐Ÿ“ก ์š”์ฒญ ๊ฐ€๋กœ์ฑ„๊ธฐ: ๋ฏธ๋“ค์›จ์–ด๊ฐ€ GET ์š”์ฒญ์„ ๋ผ์šฐํŒ… ์ •๋ณด์™€ ํ•จ๊ป˜ ๊ฐ€๋กœ์ฑ”
  4. ๐Ÿ”‘ ํ‚ค ์ƒ์„ฑ: ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ •๋ ฌํ•˜์—ฌ SHA256 ํ•ด์‹œ ์ƒ์„ฑ
  5. ๐Ÿ’พ ์บ์‹œ ํ™•์ธ: ์ƒ์„ฑ๋œ ํ‚ค๋กœ ์บ์‹œ์—์„œ ์‘๋‹ต ์กฐํšŒ
  6. ๐Ÿ“ฆ ์‘๋‹ต ์บ์‹ฑ: ์บ์‹œ ๋ฏธ์Šค ์‹œ ์‘๋‹ต์„ ์บ์‹œ์— ์ €์žฅ ํ›„ ํ…Œ์ด๋ธ” ์ถ”์  ๋“ฑ๋ก
  7. ๐Ÿ”„ ๋ฌดํšจํ™”: ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ์บ์‹œ ์ž๋™ ์‚ญ์ œ

๐ŸŽฏ Source Generator ์žฅ์ 

  • โšก ์„ฑ๋Šฅ: ๋Ÿฐํƒ€์ž„ Reflection ๋ถˆํ•„์š”, ์ปดํŒŒ์ผ ํƒ€์ž„ ์ตœ์ ํ™”
  • ๐Ÿ›ก๏ธ ํƒ€์ž… ์•ˆ์ „์„ฑ: ์ปดํŒŒ์ผ ํƒ€์ž„์— ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ๊ฒ€์ฆ
  • ๐Ÿš€ AOT ํ˜ธํ™˜: Native AOT ๋ฐฐํฌ ์ง€์›
  • ๐Ÿ”ง ์ž๋™ ๊ด€๋ฆฌ: ๋ณ„๋„ ์„ค์ • ํŒŒ์ผ ๋ถˆํ•„์š”, ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋งŒ์œผ๋กœ ์™„์„ฑ

๐Ÿ”„ ์บ์‹œ ๋ฌดํšจํ™” ์ „๋žต

1. ์ฆ‰์‹œ ๋ฌดํšจํ™” (Immediate)

[CacheInvalidateOn("Users", InvalidationType.All)]

2. ํŒจํ„ด ๋ฌดํšจํ™” (Pattern-based)

[CacheInvalidateOn("Users", InvalidationType.Pattern, "User_*")]
[CacheInvalidateOn("Users", InvalidationType.Related, "Orders", "Profiles")]

๐Ÿ“ฆ ์ถ”๊ฐ€ ํŒจํ‚ค์ง€

# Source Generator (์„ ํƒ์  - Core์— ํฌํ•จ๋˜์ง€๋งŒ ๋…๋ฆฝ ์„ค์น˜ ๊ฐ€๋Šฅ)
dotnet add package Athena.Cache.SourceGenerator

# ๋ถ„์„ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง (์„ ํƒ์ )
dotnet add package Athena.Cache.Analytics

๐Ÿ“‹ ๋ฆด๋ฆฌ์ฆˆ ์ •๋ณด: ์ž์„ธํ•œ ๋ฆด๋ฆฌ์ฆˆ ํ”„๋กœ์„ธ์Šค์™€ ๋ฒ„์ „ ๊ด€๋ฆฌ ์ „๋žต์€ RELEASE.md๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๐Ÿค ๊ธฐ์—ฌํ•˜๊ธฐ

  1. ์ €์žฅ์†Œ ํฌํฌ
  2. ๊ธฐ๋Šฅ ๋ธŒ๋žœ์น˜ ์ƒ์„ฑ (git checkout -b feature/amazing-feature)
  3. ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ปค๋ฐ‹ (git commit -m 'Add amazing feature')
  4. ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œ (git push origin feature/amazing-feature)
  5. Pull Request ์˜คํ”ˆ

๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

git clone https://github.com/jhbrunoK/Athena.Cache.git
cd Athena.Cache
dotnet restore
dotnet build
dotnet test

๐Ÿ“ ๋ผ์ด์„ผ์Šค

์ด ํ”„๋กœ์ ํŠธ๋Š” MIT ๋ผ์ด์„ผ์Šค ํ•˜์— ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ LICENSE ํŒŒ์ผ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

๐Ÿ™ ๊ฐ์‚ฌ์˜ ๋ง

  • ์ „๋žต๊ณผ ์ง€ํ˜œ์˜ ์—ฌ์‹  ์•„ํ…Œ๋‚˜์—์„œ ์˜๊ฐ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค
  • ASP.NET Core ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ์œ„ํ•ด ์ œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค
  • ๋ชจ๋“  ๊ธฐ์—ฌ์ž๋ถ„๋“ค๊ป˜ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค

๐Ÿ“ž ์ง€์› ๋ฐ ๋ฌธ์˜

  • ๐Ÿ› ๋ฒ„๊ทธ ๋ฆฌํฌํŠธ: GitHub Issues
  • ๐Ÿ’ก ๊ธฐ๋Šฅ ์š”์ฒญ: GitHub Discussions
  • ๐Ÿ“ง ์ด๋ฉ”์ผ: bobhappy2000@gmail.com

โค๏ธ ๊ณ ์„ฑ๋Šฅ ASP.NET Core ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•ด ์ œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.9 62 8/2/2025
1.1.6 101 7/27/2025
1.1.5 101 7/27/2025
1.1.3 99 7/27/2025
1.0.4 113 7/27/2025

v1.1.9: Source Generator for compile-time cache configuration