perf: optimize authorization scoreline and workers

This commit is contained in:
2026-07-30 09:12:27 +08:00
parent 4bea745b79
commit bedc77bffd
52 changed files with 21911 additions and 347 deletions

View File

@@ -43,6 +43,8 @@ internal sealed class ScorelineRecordConfiguration : IEntityTypeConfiguration<Sc
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.RegionId, entity.SchoolId, entity.MajorId, entity.Year });
builder.HasIndex(entity => new { entity.TenantId, entity.Year });
builder.HasIndex(entity => new { entity.TenantId, entity.Year, entity.SchoolName, entity.MajorName, entity.Id })
.IsDescending(false, true, false, false, false);
builder.HasOne<Region>()
.WithMany()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tiku.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class OptimizeScorelineQueries : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:PostgresExtension:citext", ",,")
.Annotation("Npgsql:PostgresExtension:ltree", ",,")
.Annotation("Npgsql:PostgresExtension:pg_trgm", ",,")
.OldAnnotation("Npgsql:PostgresExtension:citext", ",,")
.OldAnnotation("Npgsql:PostgresExtension:ltree", ",,");
migrationBuilder.Sql(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_scoreline_records_tenant_id_year_school_name_major_name_id ON scoreline_records (tenant_id, year DESC, school_name, major_name, id);",
suppressTransaction: true);
migrationBuilder.Sql(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_scoreline_records_field_values_jsonb_path ON scoreline_records USING gin (field_values jsonb_path_ops);",
suppressTransaction: true);
migrationBuilder.Sql(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_scoreline_records_school_name_trgm ON scoreline_records USING gin (school_name gin_trgm_ops) WHERE school_name IS NOT NULL;",
suppressTransaction: true);
migrationBuilder.Sql(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_scoreline_records_major_name_trgm ON scoreline_records USING gin (major_name gin_trgm_ops) WHERE major_name IS NOT NULL;",
suppressTransaction: true);
migrationBuilder.Sql(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_background_jobs_pending_due ON background_jobs (run_after, created_at, id) WHERE status = 'pending';",
suppressTransaction: true);
migrationBuilder.Sql(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_background_jobs_processing_lease ON background_jobs (lock_expires_at, created_at, id) WHERE status = 'processing';",
suppressTransaction: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DROP INDEX CONCURRENTLY IF EXISTS ix_background_jobs_processing_lease;", suppressTransaction: true);
migrationBuilder.Sql("DROP INDEX CONCURRENTLY IF EXISTS ix_background_jobs_pending_due;", suppressTransaction: true);
migrationBuilder.Sql("DROP INDEX CONCURRENTLY IF EXISTS ix_scoreline_records_major_name_trgm;", suppressTransaction: true);
migrationBuilder.Sql("DROP INDEX CONCURRENTLY IF EXISTS ix_scoreline_records_school_name_trgm;", suppressTransaction: true);
migrationBuilder.Sql("DROP INDEX CONCURRENTLY IF EXISTS ix_scoreline_records_field_values_jsonb_path;", suppressTransaction: true);
migrationBuilder.Sql("DROP INDEX CONCURRENTLY IF EXISTS ix_scoreline_records_tenant_id_year_school_name_major_name_id;", suppressTransaction: true);
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:PostgresExtension:citext", ",,")
.Annotation("Npgsql:PostgresExtension:ltree", ",,")
.OldAnnotation("Npgsql:PostgresExtension:citext", ",,")
.OldAnnotation("Npgsql:PostgresExtension:ltree", ",,")
.OldAnnotation("Npgsql:PostgresExtension:pg_trgm", ",,");
}
}
}

View File

@@ -23,6 +23,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext");
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree");
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "pg_trgm");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
@@ -1113,6 +1114,10 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasIndex("TenantId", "RegionId", "SchoolId", "MajorId", "Year")
.HasDatabaseName("ix_scoreline_records_tenant_id_region_id_school_id_major_id_ye~");
b.HasIndex("TenantId", "Year", "SchoolName", "MajorName", "Id")
.IsDescending(false, true, false, false, false)
.HasDatabaseName("ix_scoreline_records_tenant_id_year_school_name_major_name_id");
b.ToTable("scoreline_records", (string)null);
});

View File

@@ -34,6 +34,7 @@ public sealed class TikuDbContext(
public Guid CurrentTenantIdOrEmpty => tenantContext.TenantId ?? Guid.Empty;
public bool IsTenantResolved => tenantContext.IsResolved;
public bool IsSystemScope => tenantContext.IsSystem;
public long SaveVersion { get; private set; }
private static ITenantContext CreateToolingTenantContext()
{
@@ -220,6 +221,7 @@ public sealed class TikuDbContext(
modelBuilder.Entity<IdentityUserToken<Guid>>().ToTable("user_tokens");
modelBuilder.HasPostgresExtension("citext");
modelBuilder.HasPostgresExtension("ltree");
modelBuilder.HasPostgresExtension("pg_trgm");
modelBuilder.ApplyConfigurationsFromAssembly(typeof(TikuDbContext).Assembly);
modelBuilder.Entity<DataProtectionKey>().ToTable("data_protection_keys");
modelBuilder.AddInboxStateEntity();
@@ -322,15 +324,19 @@ public sealed class TikuDbContext(
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
UpdateTimestamps();
return base.SaveChanges(acceptAllChangesOnSuccess);
var result = base.SaveChanges(acceptAllChangesOnSuccess);
SaveVersion++;
return result;
}
public override Task<int> SaveChangesAsync(
public override async Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default)
{
UpdateTimestamps();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
SaveVersion++;
return result;
}
private void UpdateTimestamps()