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 GeneticSharp;
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Backtests;
using Managing.Core; using Managing.Core;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Risk; using Managing.Domain.Risk;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
@@ -346,10 +348,50 @@ public class GeneticService : IGeneticService
_logger.LogInformation("Starting fresh genetic algorithm for request {RequestId}", request.RequestId); _logger.LogInformation("Starting fresh genetic algorithm for request {RequestId}", request.RequestId);
} }
// Create fitness function first // Load candles once at the beginning to avoid repeated database queries
var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger); // 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 // 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( var ga = new GeneticAlgorithm(
population, population,
fitness, fitness,
@@ -366,7 +408,7 @@ public class GeneticService : IGeneticService
TaskExecutor = new ParallelTaskExecutor TaskExecutor = new ParallelTaskExecutor
{ {
MinThreads = 2, MinThreads = 2,
MaxThreads = Environment.ProcessorCount MaxThreads = maxParallelThreads
} }
}; };
@@ -963,12 +1005,18 @@ public class TradingBotFitness : IFitness
private readonly GeneticRequest _request; private readonly GeneticRequest _request;
private GeneticAlgorithm _geneticAlgorithm; private GeneticAlgorithm _geneticAlgorithm;
private readonly ILogger<GeneticService> _logger; 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) ILogger<GeneticService> logger)
{ {
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_request = request; _request = request;
_candles = candles;
_logger = logger; _logger = logger;
} }
@@ -987,23 +1035,30 @@ public class TradingBotFitness : IFitness
var config = tradingBotChromosome.GetTradingBotConfig(_request); var config = tradingBotChromosome.GetTradingBotConfig(_request);
// Get current generation number (default to 0 if not available) // Run backtest synchronously using BacktestExecutor (no job creation, no DB writes)
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0; // Use semaphore to limit concurrent database operations (for indicator calculations)
// This prevents connection pool exhaustion when running large populations
// Run backtest using scoped service to avoid DbContext concurrency issues _dbSemaphore.Wait();
var lightBacktest = ServiceScopeHelpers.WithScopedService<IBacktester, LightBacktest>( LightBacktest lightBacktest;
try
{
lightBacktest = ServiceScopeHelpers.WithScopedService<BacktestExecutor, LightBacktest>(
_serviceScopeFactory, _serviceScopeFactory,
async backtester => await backtester.RunTradingBotBacktest( async executor => await executor.ExecuteAsync(
config, config,
_request.StartDate, _candles,
_request.EndDate,
_request.User, _request.User,
true, save: false, // Don't save backtest results for genetic algorithm
false, // Don't include candles withCandles: false,
_request.RequestId, requestId: _request.RequestId,
new GeneticBacktestMetadata(currentGeneration, _request.RequestId) metadata: new GeneticBacktestMetadata(_geneticAlgorithm?.GenerationsNumber ?? 0, _request.RequestId)
) )
).GetAwaiter().GetResult(); ).GetAwaiter().GetResult();
}
finally
{
_dbSemaphore.Release();
}
// Calculate multi-objective fitness based on backtest results // Calculate multi-objective fitness based on backtest results
var fitness = CalculateFitness(lightBacktest, config); var fitness = CalculateFitness(lightBacktest, config);