Fix genetic db connection pool

This commit is contained in:
2025-11-10 02:40:00 +07:00
parent 51a227e27e
commit ecf07a7863

View File

@@ -2,9 +2,11 @@ using System.Text.Json;
using GeneticSharp;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Backtests;
using Managing.Core;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk;
using Managing.Domain.Scenarios;
@@ -346,10 +348,50 @@ public class GeneticService : IGeneticService
_logger.LogInformation("Starting fresh genetic algorithm for request {RequestId}", request.RequestId);
}
// Create fitness function first
var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger);
// Load candles once at the beginning to avoid repeated database queries
// This significantly reduces database connections during genetic algorithm execution
_logger.LogInformation("Loading candles for genetic algorithm {RequestId}: {Ticker} on {Timeframe} from {StartDate} to {EndDate}",
request.RequestId, request.Ticker, request.Timeframe, request.StartDate, request.EndDate);
HashSet<Candle> candles;
try
{
candles = await ServiceScopeHelpers.WithScopedService<ICandleRepository, HashSet<Candle>>(
_serviceScopeFactory,
async candleRepository => await candleRepository.GetCandles(
TradingExchanges.Evm, // Default exchange for genetic algorithms
request.Ticker,
request.Timeframe,
request.StartDate,
request.EndDate
)
);
if (candles == null || candles.Count == 0)
{
throw new InvalidOperationException(
$"No candles found for {request.Ticker} on {request.Timeframe} from {request.StartDate} to {request.EndDate}");
}
_logger.LogInformation("Loaded {CandleCount} candles for genetic algorithm {RequestId}",
candles.Count, request.RequestId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load candles for genetic algorithm {RequestId}", request.RequestId);
throw;
}
// Create fitness function with pre-loaded candles to avoid database queries during evaluation
var fitness = new TradingBotFitness(_serviceScopeFactory, request, candles, _logger);
// Create genetic algorithm with better configuration
// Limit parallelism to prevent database connection pool exhaustion
// Each fitness evaluation creates a database connection, so we need to limit
// the number of parallel evaluations to stay within connection pool limits
// Default PostgreSQL pool is 20 connections, so we limit to 4 threads per genetic job
// This leaves room for other operations and multiple concurrent genetic jobs
var maxParallelThreads = Math.Min(4, Environment.ProcessorCount);
var ga = new GeneticAlgorithm(
population,
fitness,
@@ -366,7 +408,7 @@ public class GeneticService : IGeneticService
TaskExecutor = new ParallelTaskExecutor
{
MinThreads = 2,
MaxThreads = Environment.ProcessorCount
MaxThreads = maxParallelThreads
}
};
@@ -963,12 +1005,18 @@ public class TradingBotFitness : IFitness
private readonly GeneticRequest _request;
private GeneticAlgorithm _geneticAlgorithm;
private readonly ILogger<GeneticService> _logger;
private readonly HashSet<Candle> _candles;
private static readonly SemaphoreSlim _dbSemaphore = new SemaphoreSlim(4, 4); // Limit concurrent DB operations
public TradingBotFitness(IServiceScopeFactory serviceScopeFactory, GeneticRequest request,
public TradingBotFitness(
IServiceScopeFactory serviceScopeFactory,
GeneticRequest request,
HashSet<Candle> candles,
ILogger<GeneticService> logger)
{
_serviceScopeFactory = serviceScopeFactory;
_request = request;
_candles = candles;
_logger = logger;
}
@@ -987,23 +1035,30 @@ public class TradingBotFitness : IFitness
var config = tradingBotChromosome.GetTradingBotConfig(_request);
// Get current generation number (default to 0 if not available)
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
// Run backtest using scoped service to avoid DbContext concurrency issues
var lightBacktest = ServiceScopeHelpers.WithScopedService<IBacktester, LightBacktest>(
_serviceScopeFactory,
async backtester => await backtester.RunTradingBotBacktest(
config,
_request.StartDate,
_request.EndDate,
_request.User,
true,
false, // Don't include candles
_request.RequestId,
new GeneticBacktestMetadata(currentGeneration, _request.RequestId)
)
).GetAwaiter().GetResult();
// Run backtest synchronously using BacktestExecutor (no job creation, no DB writes)
// Use semaphore to limit concurrent database operations (for indicator calculations)
// This prevents connection pool exhaustion when running large populations
_dbSemaphore.Wait();
LightBacktest lightBacktest;
try
{
lightBacktest = ServiceScopeHelpers.WithScopedService<BacktestExecutor, LightBacktest>(
_serviceScopeFactory,
async executor => await executor.ExecuteAsync(
config,
_candles,
_request.User,
save: false, // Don't save backtest results for genetic algorithm
withCandles: false,
requestId: _request.RequestId,
metadata: new GeneticBacktestMetadata(_geneticAlgorithm?.GenerationsNumber ?? 0, _request.RequestId)
)
).GetAwaiter().GetResult();
}
finally
{
_dbSemaphore.Release();
}
// Calculate multi-objective fitness based on backtest results
var fitness = CalculateFitness(lightBacktest, config);