Compare commits

...

2 Commits

Author SHA1 Message Date
a1440baf3d #39 tweaking API rates
All checks were successful
API and ETL Build / build_api (push) Successful in 1m10s
API and ETL Build / build_etl (push) Successful in 1m45s
2025-09-11 20:53:28 -03:00
ddd99ec703 #34 add size based cache 2025-09-11 20:44:57 -03:00
7 changed files with 50 additions and 27 deletions

View File

@@ -27,13 +27,12 @@ namespace OpenCand.API.Config
// Default policy: 200 requests per minute with burst of 100
options.AddFixedWindowLimiter(policyName: DefaultPolicy, options =>
{
options.PermitLimit = 200;
options.PermitLimit = 400;
options.Window = TimeSpan.FromMinutes(1);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 100; // Burst capacity
});
// Candidato Search policy: 300 requests per minute with burst of 200
options.AddFixedWindowLimiter(policyName: CandidatoSearchPolicy, options =>
{
options.PermitLimit = 300;
@@ -42,22 +41,12 @@ namespace OpenCand.API.Config
options.QueueLimit = 200; // Burst capacity
});
// CPF Reveal policy: 15 requests per minute without burst
options.AddFixedWindowLimiter(policyName: CpfRevealPolicy, options =>
{
options.PermitLimit = 15;
options.PermitLimit = 20;
options.Window = TimeSpan.FromMinutes(1);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 0; // No burst
});
// CPF Reveal policy: 25 requests per minute with 10 burst
options.AddFixedWindowLimiter(policyName: EstatisticaPolicy, options =>
{
options.PermitLimit = 25;
options.Window = TimeSpan.FromMinutes(1);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 10; // No burst
options.QueueLimit = 5; // Burst capacity
});
options.OnRejected = async (context, token) =>
@@ -67,7 +56,7 @@ namespace OpenCand.API.Config
var retryAfter = GetRetryAfter(context);
if (retryAfter.HasValue)
{
context.HttpContext.Response.Headers.Add("Retry-After", retryAfter.Value.ToString());
context.HttpContext.Response.Headers.Append("Retry-After", retryAfter.Value.ToString());
}
await context.HttpContext.Response.WriteAsync(

View File

@@ -1,10 +1,13 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using OpenCand.API.Config;
namespace OpenCand.API.Controllers
{
[ApiController]
[Route("v1/[controller]")]
[Produces("application/json")]
[EnableRateLimiting(RateLimitingConfig.DefaultPolicy)]
public class BaseController : Controller
{

View File

@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.VisualBasic;
using OpenCand.API.Config;
using OpenCand.API.Model;
using OpenCand.API.Services;
@@ -9,7 +8,6 @@ using OpenCand.Core.Utils;
namespace OpenCand.API.Controllers
{
[EnableRateLimiting(RateLimitingConfig.DefaultPolicy)]
public class CandidatoController : BaseController
{
private readonly OpenCandService openCandService;

View File

@@ -1,12 +1,10 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using OpenCand.API.Config;
using OpenCand.API.Model;
using OpenCand.API.Services;
using static OpenCand.API.Model.GetValueSumRequest;
namespace OpenCand.API.Controllers
{
[EnableRateLimiting(RateLimitingConfig.EstatisticaPolicy)]
public class EstatisticaController : BaseController
{
private readonly EstatisticaService estatisticaService;

View File

@@ -36,7 +36,8 @@ namespace OpenCand.API
{
FileProvider = new PhysicalFileProvider(Path.Combine(workingDir, "fotos_cand")),
RequestPath = "/assets/fotos"
}); app.UseHttpsRedirection();
});
app.UseHttpsRedirection();
// Use rate limiting middleware
app.UseRateLimiter();
@@ -53,11 +54,18 @@ namespace OpenCand.API
app.Run();
}
private static void SetupServices(WebApplicationBuilder builder)
private static void SetupServices(WebApplicationBuilder builder)
{
builder.Services.Configure<FotosSettings>(builder.Configuration.GetSection("FotosSettings"));
builder.Services.AddMemoryCache();
// Configure memory cache with size limit from appsettings
var sizeLimitMB = builder.Configuration.GetValue<int>("CacheSettings:SizeLimitMB", 15);
builder.Services.AddMemoryCache(options =>
{
options.SizeLimit = sizeLimitMB * 1024L * 1024L; // Convert MB to bytes
});
builder.Services.AddScoped(provider => new OpenCandRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
builder.Services.AddScoped(provider => new CandidatoRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
builder.Services.AddScoped(provider => new BemCandidatoRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
@@ -66,7 +74,7 @@ namespace OpenCand.API
builder.Services.AddScoped<OpenCandService>();
builder.Services.AddScoped<EstatisticaService>();
// Add cache preload background service
builder.Services.AddHostedService<CachePreloadService>();
}

View File

@@ -1,5 +1,7 @@
using Microsoft.Extensions.Caching.Memory;
using Npgsql;
using System.Text.Json;
using System.Text;
namespace OpenCand.Repository
{
@@ -56,7 +58,8 @@ namespace OpenCand.Repository
_cache.Set(cacheKey, result, new MemoryCacheEntryOptions
{
SlidingExpiration = expiration ?? DefaultCacheExpiration,
Priority = priority ?? DefaultCachePriority
Priority = priority ?? DefaultCachePriority,
Size = EstimateSize(result)
});
}
@@ -99,7 +102,8 @@ namespace OpenCand.Repository
_cache.Set(cacheKey, result, new MemoryCacheEntryOptions
{
SlidingExpiration = expiration ?? DefaultCacheExpiration,
Priority = priority ?? DefaultCachePriority
Priority = priority ?? DefaultCachePriority,
Size = EstimateSize(result)
});
}
@@ -133,5 +137,25 @@ namespace OpenCand.Repository
{
return identifier != null ? $"{entityName}_{identifier}" : entityName;
}
/// <summary>
/// Estimates the memory size of an object by serializing it to JSON
/// </summary>
/// <typeparam name="T">Type of the object</typeparam>
/// <param name="obj">The object to estimate size for</param>
/// <returns>Estimated size in bytes</returns>
private static long EstimateSize<T>(T obj)
{
if (obj == null) return 0;
try
{
var json = JsonSerializer.Serialize(obj);
return Encoding.UTF8.GetByteCount(json);
}
catch
{
return 1024; // Default estimate if serialization fails
}
}
}
}

View File

@@ -12,5 +12,8 @@
"Path": "./fotos_cand",
"ApiBasePath": "http://localhost:5299/assets/fotos"
},
"CacheSettings": {
"SizeLimitMB": 15
},
"AllowedHosts": "*"
}