Skip to main content

max / makenotwork

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>
Author: Max Johnson <me@maxj.phd> · 2026-07-02 13:47 UTC
Commit: 44c21780923e5fd4ab49207211612791f1e57b26
Parent: 0770f27
3 files changed, +23 insertions, -4 deletions
@@ -160,6 +160,7 @@ pub async fn get_public_collections_by_user(
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,6 +261,7 @@ pub async fn get_collection_items(
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,6 +369,7 @@ pub async fn get_user_collections_for_item(
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,9 +694,12 @@ pub async fn check_upload_allowed(
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,14 +794,27 @@ pub async fn count_users_summary(pool: &PgPool) -> Result<(i64, i64)> {
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