forked from gongxuegit/tiku-backend.net
perf: remove backoffice catalog query amplification
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user