Add genetic backtest to worker
This commit is contained in:
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
|
||||
|
||||
[Table("BacktestJobs")]
|
||||
[Table("Jobs")]
|
||||
public class JobEntity
|
||||
{
|
||||
[Key]
|
||||
@@ -16,7 +16,7 @@ public class JobEntity
|
||||
public int UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public int Status { get; set; } // BacktestJobStatus enum as int
|
||||
public int Status { get; set; } // JobStatus enum as int
|
||||
|
||||
[Required]
|
||||
public int JobType { get; set; } // JobType enum as int
|
||||
@@ -61,6 +61,16 @@ public class JobEntity
|
||||
[MaxLength(255)]
|
||||
public string? GeneticRequestId { get; set; }
|
||||
|
||||
public int RetryCount { get; set; } = 0;
|
||||
|
||||
public int MaxRetries { get; set; } = 3;
|
||||
|
||||
public DateTime? RetryAfter { get; set; }
|
||||
|
||||
public bool IsRetryable { get; set; } = true;
|
||||
|
||||
public int? FailureCategory { get; set; } // FailureCategory enum as int
|
||||
|
||||
// Navigation property
|
||||
public UserEntity? User { get; set; }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.PostgreSql;
|
||||
|
||||
public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
public class PostgreSqlJobRepository : IJobRepository
|
||||
{
|
||||
private readonly ManagingDbContext _context;
|
||||
private readonly ILogger<PostgreSqlJobRepository> _logger;
|
||||
@@ -20,7 +20,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<BacktestJob> CreateAsync(BacktestJob job)
|
||||
public async Task<Job> CreateAsync(Job job)
|
||||
{
|
||||
var entity = MapToEntity(job);
|
||||
_context.Jobs.Add(entity);
|
||||
@@ -28,10 +28,9 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
return MapToDomain(entity);
|
||||
}
|
||||
|
||||
public async Task<BacktestJob?> ClaimNextJobAsync(string workerId, JobType? jobType = null)
|
||||
public async Task<Job?> ClaimNextJobAsync(string workerId, JobType? jobType = null)
|
||||
{
|
||||
// Use execution strategy to support retry with transactions
|
||||
// FOR UPDATE SKIP LOCKED ensures only one worker can claim a specific job
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
@@ -42,10 +41,10 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
{
|
||||
// Build SQL query with optional job type filter
|
||||
var sql = @"
|
||||
SELECT * FROM ""BacktestJobs""
|
||||
SELECT * FROM ""Jobs""
|
||||
WHERE ""Status"" = {0}";
|
||||
|
||||
var parameters = new List<object> { (int)BacktestJobStatus.Pending };
|
||||
var parameters = new List<object> { (int)JobStatus.Pending };
|
||||
|
||||
if (jobType.HasValue)
|
||||
{
|
||||
@@ -70,7 +69,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
}
|
||||
|
||||
// Update the job status atomically
|
||||
job.Status = (int)BacktestJobStatus.Running;
|
||||
job.Status = (int)JobStatus.Running;
|
||||
job.AssignedWorkerId = workerId;
|
||||
job.StartedAt = DateTime.UtcNow;
|
||||
job.LastHeartbeat = DateTime.UtcNow;
|
||||
@@ -89,7 +88,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
});
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(BacktestJob job)
|
||||
public async Task UpdateAsync(Job job)
|
||||
{
|
||||
// Use AsTracking() to enable change tracking since DbContext uses NoTracking by default
|
||||
var entity = await _context.Jobs
|
||||
@@ -115,11 +114,16 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
entity.RequestId = job.RequestId;
|
||||
entity.GeneticRequestId = job.GeneticRequestId;
|
||||
entity.Priority = job.Priority;
|
||||
entity.RetryCount = job.RetryCount;
|
||||
entity.MaxRetries = job.MaxRetries;
|
||||
entity.RetryAfter = job.RetryAfter;
|
||||
entity.IsRetryable = job.IsRetryable;
|
||||
entity.FailureCategory = job.FailureCategory.HasValue ? (int)job.FailureCategory.Value : null;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BacktestJob>> GetByBundleRequestIdAsync(Guid bundleRequestId)
|
||||
public async Task<IEnumerable<Job>> GetByBundleRequestIdAsync(Guid bundleRequestId)
|
||||
{
|
||||
var entities = await _context.Jobs
|
||||
.Where(j => j.BundleRequestId == bundleRequestId)
|
||||
@@ -128,7 +132,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
return entities.Select(MapToDomain);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BacktestJob>> GetByUserIdAsync(int userId)
|
||||
public async Task<IEnumerable<Job>> GetByUserIdAsync(int userId)
|
||||
{
|
||||
var entities = await _context.Jobs
|
||||
.Where(j => j.UserId == userId)
|
||||
@@ -140,16 +144,16 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
/// <summary>
|
||||
/// Gets all running jobs assigned to a specific worker
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<BacktestJob>> GetRunningJobsByWorkerIdAsync(string workerId)
|
||||
public async Task<IEnumerable<Job>> GetRunningJobsByWorkerIdAsync(string workerId)
|
||||
{
|
||||
var entities = await _context.Jobs
|
||||
.Where(j => j.AssignedWorkerId == workerId && j.Status == (int)BacktestJobStatus.Running)
|
||||
.Where(j => j.AssignedWorkerId == workerId && j.Status == (int)JobStatus.Running)
|
||||
.ToListAsync();
|
||||
|
||||
return entities.Select(MapToDomain);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BacktestJob>> GetByGeneticRequestIdAsync(string geneticRequestId)
|
||||
public async Task<IEnumerable<Job>> GetByGeneticRequestIdAsync(string geneticRequestId)
|
||||
{
|
||||
var entities = await _context.Jobs
|
||||
.Where(j => j.GeneticRequestId == geneticRequestId)
|
||||
@@ -158,12 +162,12 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
return entities.Select(MapToDomain);
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<BacktestJob> Jobs, int TotalCount)> GetPaginatedAsync(
|
||||
public async Task<(IEnumerable<Job> Jobs, int TotalCount)> GetPaginatedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string sortBy = "CreatedAt",
|
||||
string sortOrder = "desc",
|
||||
BacktestJobStatus? status = null,
|
||||
JobStatus? status = null,
|
||||
JobType? jobType = null,
|
||||
int? userId = null,
|
||||
string? workerId = null,
|
||||
@@ -235,7 +239,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
return (jobs, totalCount);
|
||||
}
|
||||
|
||||
public async Task<BacktestJob?> GetByIdAsync(Guid jobId)
|
||||
public async Task<Job?> GetByIdAsync(Guid jobId)
|
||||
{
|
||||
var entity = await _context.Jobs
|
||||
.FirstOrDefaultAsync(j => j.Id == jobId);
|
||||
@@ -243,12 +247,12 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
return entity != null ? MapToDomain(entity) : null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BacktestJob>> GetStaleJobsAsync(int timeoutMinutes = 5)
|
||||
public async Task<IEnumerable<Job>> GetStaleJobsAsync(int timeoutMinutes = 5)
|
||||
{
|
||||
var timeoutThreshold = DateTime.UtcNow.AddMinutes(-timeoutMinutes);
|
||||
|
||||
var entities = await _context.Jobs
|
||||
.Where(j => j.Status == (int)BacktestJobStatus.Running &&
|
||||
.Where(j => j.Status == (int)JobStatus.Running &&
|
||||
(j.LastHeartbeat == null || j.LastHeartbeat < timeoutThreshold))
|
||||
.ToListAsync();
|
||||
|
||||
@@ -262,13 +266,13 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
// Use AsTracking() to enable change tracking since DbContext uses NoTracking by default
|
||||
var staleJobs = await _context.Jobs
|
||||
.AsTracking()
|
||||
.Where(j => j.Status == (int)BacktestJobStatus.Running &&
|
||||
.Where(j => j.Status == (int)JobStatus.Running &&
|
||||
(j.LastHeartbeat == null || j.LastHeartbeat < timeoutThreshold))
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var job in staleJobs)
|
||||
{
|
||||
job.Status = (int)BacktestJobStatus.Pending;
|
||||
job.Status = (int)JobStatus.Pending;
|
||||
job.AssignedWorkerId = null;
|
||||
job.LastHeartbeat = null;
|
||||
}
|
||||
@@ -299,7 +303,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
// Query 1: Status summary
|
||||
var statusSummarySql = @"
|
||||
SELECT ""Status"", COUNT(*) as Count
|
||||
FROM ""BacktestJobs""
|
||||
FROM ""Jobs""
|
||||
GROUP BY ""Status""
|
||||
ORDER BY ""Status""";
|
||||
|
||||
@@ -322,7 +326,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
// Query 2: Job type summary
|
||||
var jobTypeSummarySql = @"
|
||||
SELECT ""JobType"", COUNT(*) as Count
|
||||
FROM ""BacktestJobs""
|
||||
FROM ""Jobs""
|
||||
GROUP BY ""JobType""
|
||||
ORDER BY ""JobType""";
|
||||
|
||||
@@ -345,7 +349,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
// Query 3: Status + Job type summary
|
||||
var statusTypeSummarySql = @"
|
||||
SELECT ""Status"", ""JobType"", COUNT(*) as Count
|
||||
FROM ""BacktestJobs""
|
||||
FROM ""Jobs""
|
||||
GROUP BY ""Status"", ""JobType""
|
||||
ORDER BY ""Status"", ""JobType""";
|
||||
|
||||
@@ -369,7 +373,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
// Query 4: Total count
|
||||
var totalCountSql = @"
|
||||
SELECT COUNT(*) as Count
|
||||
FROM ""BacktestJobs""";
|
||||
FROM ""Jobs""";
|
||||
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
@@ -382,7 +386,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
{
|
||||
StatusCounts = statusCounts.Select(s => new JobStatusCount
|
||||
{
|
||||
Status = (BacktestJobStatus)s.Status,
|
||||
Status = (JobStatus)s.Status,
|
||||
Count = s.Count
|
||||
}).ToList(),
|
||||
JobTypeCounts = jobTypeCounts.Select(j => new JobTypeCount
|
||||
@@ -392,7 +396,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
}).ToList(),
|
||||
StatusTypeCounts = statusTypeCounts.Select(st => new JobStatusTypeCount
|
||||
{
|
||||
Status = (BacktestJobStatus)st.Status,
|
||||
Status = (JobStatus)st.Status,
|
||||
JobType = (JobType)st.JobType,
|
||||
Count = st.Count
|
||||
}).ToList(),
|
||||
@@ -430,7 +434,7 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
private static JobEntity MapToEntity(BacktestJob job)
|
||||
private static JobEntity MapToEntity(Job job)
|
||||
{
|
||||
return new JobEntity
|
||||
{
|
||||
@@ -452,18 +456,23 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
ResultJson = job.ResultJson,
|
||||
ErrorMessage = job.ErrorMessage,
|
||||
RequestId = job.RequestId,
|
||||
GeneticRequestId = job.GeneticRequestId
|
||||
GeneticRequestId = job.GeneticRequestId,
|
||||
RetryCount = job.RetryCount,
|
||||
MaxRetries = job.MaxRetries,
|
||||
RetryAfter = job.RetryAfter,
|
||||
IsRetryable = job.IsRetryable,
|
||||
FailureCategory = job.FailureCategory.HasValue ? (int)job.FailureCategory.Value : null
|
||||
};
|
||||
}
|
||||
|
||||
private static BacktestJob MapToDomain(JobEntity entity)
|
||||
private static Job MapToDomain(JobEntity entity)
|
||||
{
|
||||
return new BacktestJob
|
||||
return new Job
|
||||
{
|
||||
Id = entity.Id,
|
||||
BundleRequestId = entity.BundleRequestId,
|
||||
UserId = entity.UserId,
|
||||
Status = (BacktestJobStatus)entity.Status,
|
||||
Status = (JobStatus)entity.Status,
|
||||
JobType = (JobType)entity.JobType,
|
||||
Priority = entity.Priority,
|
||||
ConfigJson = entity.ConfigJson,
|
||||
@@ -478,7 +487,12 @@ public class PostgreSqlJobRepository : IBacktestJobRepository
|
||||
ResultJson = entity.ResultJson,
|
||||
ErrorMessage = entity.ErrorMessage,
|
||||
RequestId = entity.RequestId,
|
||||
GeneticRequestId = entity.GeneticRequestId
|
||||
GeneticRequestId = entity.GeneticRequestId,
|
||||
RetryCount = entity.RetryCount,
|
||||
MaxRetries = entity.MaxRetries,
|
||||
RetryAfter = entity.RetryAfter,
|
||||
IsRetryable = entity.IsRetryable,
|
||||
FailureCategory = entity.FailureCategory.HasValue ? (FailureCategory)entity.FailureCategory.Value : null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user