audit Run 16 Phase 5: Performance
- creator_tiers::check_upload_allowed: the tier and grandfathering lookups
are independent but ran sequentially on the per-upload hot path; run
them concurrently with try_join!.
- get_all_user_emails: was an uncapped full-table scan feeding bulk email;
cap at 50k with a WARN on hit (mirrors get_status_alert_subscribers) so
memory is bounded and a real overflow is visible.
- collections: add defensive LIMITs to the three user-facing list queries
(public collections, collection items, per-item membership dropdown).
Verified stale/already-bounded (no change): every RSS feed query already
carries a LIMIT (50 feed-only / 500 project-scoped); get_all_email_signups
already caps at 500.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 files changed,
+23 insertions,
-4 deletions
| 160 |
160 |
|
WHERE c.user_id = $1 AND c.is_public = true
|
| 161 |
161 |
|
GROUP BY c.id
|
| 162 |
162 |
|
ORDER BY c.updated_at DESC
|
|
163 |
+ |
LIMIT 500
|
| 163 |
164 |
|
"#,
|
| 164 |
165 |
|
)
|
| 165 |
166 |
|
.bind(user_id)
|
| 260 |
261 |
|
JOIN users u ON u.id = p.user_id
|
| 261 |
262 |
|
WHERE ci.collection_id = $1
|
| 262 |
263 |
|
ORDER BY ci.position
|
|
264 |
+ |
LIMIT 1000
|
| 263 |
265 |
|
"#,
|
| 264 |
266 |
|
)
|
| 265 |
267 |
|
.bind(collection_id)
|
| 367 |
369 |
|
FROM collections c
|
| 368 |
370 |
|
WHERE c.user_id = $1
|
| 369 |
371 |
|
ORDER BY c.updated_at DESC
|
|
372 |
+ |
LIMIT 500
|
| 370 |
373 |
|
"#,
|
| 371 |
374 |
|
)
|
| 372 |
375 |
|
.bind(user_id)
|
| 694 |
694 |
|
return Ok(max_storage);
|
| 695 |
695 |
|
}
|
| 696 |
696 |
|
|
| 697 |
|
- |
// Resolve effective tier
|
| 698 |
|
- |
let active_tier = get_active_creator_tier(pool, user_id).await?;
|
| 699 |
|
- |
let grandfathered = get_grandfathered_until(pool, user_id).await?;
|
|
697 |
+ |
// Resolve effective tier. These two lookups are independent, so run them
|
|
698 |
+ |
// concurrently rather than sequentially on the per-upload hot path.
|
|
699 |
+ |
let (active_tier, grandfathered) = tokio::try_join!(
|
|
700 |
+ |
get_active_creator_tier(pool, user_id),
|
|
701 |
+ |
get_grandfathered_until(pool, user_id),
|
|
702 |
+ |
)?;
|
| 700 |
703 |
|
|
| 701 |
704 |
|
let effective_tier = match active_tier {
|
| 702 |
705 |
|
Some(tier) => Some(tier),
|
| 794 |
794 |
|
}
|
| 795 |
795 |
|
|
| 796 |
796 |
|
/// Get all user emails for bulk notifications (e.g. shutdown notice).
|
|
797 |
+ |
///
|
|
798 |
+ |
/// Capped to bound memory on a full-table scan; if the cap is ever hit the WARN
|
|
799 |
+ |
/// fires so we know to switch to a paged dispatch model (mirrors
|
|
800 |
+ |
/// [`get_status_alert_subscribers`]).
|
| 797 |
801 |
|
#[tracing::instrument(skip_all)]
|
| 798 |
802 |
|
pub async fn get_all_user_emails(pool: &PgPool) -> Result<Vec<(String, Option<String>)>> {
|
|
803 |
+ |
const ALL_EMAILS_CAP: i64 = 50_000;
|
| 799 |
804 |
|
let rows = sqlx::query_as::<_, (String, Option<String>)>(
|
| 800 |
|
- |
"SELECT email, display_name FROM users ORDER BY created_at ASC",
|
|
805 |
+ |
"SELECT email, display_name FROM users ORDER BY created_at ASC LIMIT $1",
|
| 801 |
806 |
|
)
|
|
807 |
+ |
.bind(ALL_EMAILS_CAP)
|
| 802 |
808 |
|
.fetch_all(pool)
|
| 803 |
809 |
|
.await?;
|
| 804 |
810 |
|
|
|
811 |
+ |
if rows.len() as i64 == ALL_EMAILS_CAP {
|
|
812 |
+ |
tracing::warn!(
|
|
813 |
+ |
cap = ALL_EMAILS_CAP,
|
|
814 |
+ |
"get_all_user_emails hit its cap; some users omitted — switch to paged dispatch"
|
|
815 |
+ |
);
|
|
816 |
+ |
}
|
|
817 |
+ |
|
| 805 |
818 |
|
Ok(rows)
|
| 806 |
819 |
|
}
|
| 807 |
820 |
|
|