perf: remove backoffice catalog query amplification

This commit is contained in:
2026-07-30 09:50:05 +08:00
parent bedc77bffd
commit 98585aa521
8 changed files with 560 additions and 228 deletions

View File

@@ -65,34 +65,79 @@ internal sealed class FeatureAccessService(
CancellationToken cancellationToken = default)
{
var now = DateTimeOffset.UtcNow;
var subscription = await CurrentWritableSubscriptionAsync(tenantId, now, cancellationToken);
if (subscription is null)
var rows = await dbContext.Database.SqlQuery<QuotaSummaryProjection>($"""
WITH current_subscription AS (
SELECT subscription.id,
subscription.base_offering_version_id,
subscription.current_period_start,
subscription.current_period_end
FROM tenant_saas_subscriptions AS subscription
WHERE subscription.tenant_id = {tenantId}
AND subscription.status IN ('trial', 'active')
AND subscription.starts_at <= {now}
AND subscription.current_period_end > {now}
ORDER BY subscription.updated_at DESC
LIMIT 1
),
version_ids AS (
SELECT subscription.base_offering_version_id AS offering_version_id
FROM current_subscription AS subscription
UNION
SELECT item.offering_version_id
FROM tenant_saas_subscription_items AS item
INNER JOIN current_subscription AS subscription
ON subscription.id = item.subscription_id
WHERE item.tenant_id = {tenantId}
AND item.status = 'active'
AND item.starts_at <= {now}
AND item.ends_at > {now}
),
quota_limits AS (
SELECT definition.metric_code,
sum(definition.limit_value)::bigint AS limit_value
FROM saas_offering_version_limits AS definition
WHERE definition.offering_version_id IN (
SELECT version.offering_version_id FROM version_ids AS version)
GROUP BY definition.metric_code
)
SELECT limits.metric_code AS "MetricCode",
COALESCE(usage.used_value, 0)::bigint AS "Used",
limits.limit_value AS "Limit",
COALESCE(usage.period_start, subscription.current_period_start) AS "PeriodStart",
COALESCE(usage.period_end, subscription.current_period_end) AS "PeriodEnd"
FROM quota_limits AS limits
CROSS JOIN current_subscription AS subscription
LEFT JOIN LATERAL (
SELECT current_usage.used_value,
current_usage.period_start,
current_usage.period_end
FROM tenant_feature_usage AS current_usage
WHERE current_usage.tenant_id = {tenantId}
AND current_usage.metric_code = limits.metric_code
AND current_usage.period_start <= {now}
AND current_usage.period_end > {now}
ORDER BY current_usage.period_start DESC
LIMIT 1
) AS usage ON TRUE
ORDER BY limits.metric_code
""")
.ToArrayAsync(cancellationToken);
var result = new List<FeatureQuotaSnapshot>(rows.Length);
foreach (var row in rows)
{
return [];
}
var limits = await ResolveLimitsAsync(tenantId, subscription.Id, subscription.BaseOfferingVersionId, now, cancellationToken);
var usages = await dbContext.TenantFeatureUsages.AsNoTracking()
.Where(value => value.TenantId == tenantId && value.PeriodStart <= now && value.PeriodEnd > now)
.ToDictionaryAsync(value => value.MetricCode, StringComparer.Ordinal, cancellationToken);
var result = new List<FeatureQuotaSnapshot>();
foreach (var limit in limits)
{
usages.TryGetValue(limit.Key, out var usage);
var used = usage?.UsedValue ?? 0;
var percent = limit.Value == 0 ? 100 : (int)Math.Min(100, used * 100 / limit.Value);
var percent = row.Limit == 0 ? 100 : (int)Math.Min(100, row.Used * 100 / row.Limit);
result.Add(new FeatureQuotaSnapshot(
limit.Key,
used,
limit.Value,
row.MetricCode,
row.Used,
row.Limit,
percent,
percent >= 80,
used >= limit.Value,
usage?.PeriodStart ?? subscription.CurrentPeriodStart,
usage?.PeriodEnd ?? subscription.CurrentPeriodEnd));
row.Used >= row.Limit,
row.PeriodStart,
row.PeriodEnd));
}
return result.OrderBy(value => value.MetricCode, StringComparer.Ordinal).ToArray();
return result;
}
public async Task<bool> TryConsumeQuotaAsync(
@@ -266,4 +311,11 @@ internal sealed class FeatureAccessService(
Guid BaseOfferingVersionId,
DateTimeOffset CurrentPeriodStart,
DateTimeOffset CurrentPeriodEnd);
private sealed record QuotaSummaryProjection(
string MetricCode,
long Used,
long Limit,
DateTimeOffset PeriodStart,
DateTimeOffset PeriodEnd);
}