From 4aa2122b895d49dc521ad69404ba88dfd5dfcede Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Sat, 10 Oct 2026 13:35:56 -0300 Subject: [PATCH] Drop the internal daily cap on YouTube analytics jobs AnalyticsRateLimits capped YouTube analytics at 3,000 jobs a day on one project-wide key, assuming each job spent up to 2 Data API units and shared the 10,000-unit quota with uploads. Neither holds: a metrics job reads videos.list in batches of 50 ids, and videos.insert now has its own 100-call bucket. The console shows 16% of the Data API and 1.6% of the Analytics API used. The cap counted every job that passed the middleware, including discovery and duplicates, so the launch backfill of large channels exhausted it. Every YouTube metrics and discovery job was then released until the 24-hour window reset, and the ones past retryUntil failed together with MaxAttemptsExceededException each day at the same minute. A quota or 429 from Google is still retried as rate_limited; more quota is requested from Google when that happens. --- app/Support/Analytics/AnalyticsRateLimits.php | 8 ++++---- tests/Feature/Analytics/PublicationMetricsJobsTest.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Support/Analytics/AnalyticsRateLimits.php b/app/Support/Analytics/AnalyticsRateLimits.php index 7b73a376c..83d70754c 100644 --- a/app/Support/Analytics/AnalyticsRateLimits.php +++ b/app/Support/Analytics/AnalyticsRateLimits.php @@ -19,9 +19,10 @@ * documented ceiling: * - X: GET /2/users/:id/tweets allows 10,000 requests per 15 minutes per app (~666/min); guard 600/min. * - TikTok: 600 requests per minute per endpoint; guard 500/min. - * - YouTube: the Data API quota is 10,000 units a day per Google Cloud project, shared with publishing. - * An analytics job spends at most 2 units (playlistItems + a 50-id videos.list), so 3,000 jobs a day - * stay under 6,000 units and leave the rest for uploads. + * - YouTube has no shared guard. A metrics job reads `videos.list` in batches of 50 ids (one Data API + * unit for 50 videos) and one YouTube Analytics API report; uploads have their own quota. A + * `quotaExceeded` or 429 from Google is retried as rate_limited, and more quota is requested from + * Google when that happens. * - Bluesky: a PDS allows 3,000 requests per 5 minutes per IP over all endpoints (600/min, * docs.bsky.app "Rate Limits", `global-ip` in @atproto/pds rate-limits.ts), shared with publishing * through the same host; guard 500/min per PDS host. The public AppView documents no number. @@ -56,7 +57,6 @@ private static function shared(SocialAccount $account): ?Limit return match ($account->platform) { Platform::X => Limit::perMinute(600)->by("{$network}:app"), Platform::TikTok => Limit::perMinute(500)->by("{$network}:app"), - Platform::YouTube => Limit::perDay(3000)->by("{$network}:project"), Platform::Bluesky => Limit::perMinute(500)->by("{$network}:pds:".parse_url((string) data_get($account->meta, 'service', config('trypost.platforms.bluesky.default_service')), PHP_URL_HOST)), Platform::Mastodon => Limit::perMinute(50)->by("{$network}:instance:{$account->mastodonInstance()}"), default => null, diff --git a/tests/Feature/Analytics/PublicationMetricsJobsTest.php b/tests/Feature/Analytics/PublicationMetricsJobsTest.php index 5b94c7a3d..4232ec09e 100644 --- a/tests/Feature/Analytics/PublicationMetricsJobsTest.php +++ b/tests/Feature/Analytics/PublicationMetricsJobsTest.php @@ -308,7 +308,7 @@ function discoveredFor(AnalyticsPublication $publication): DiscoveredPublication 'instagram is limited per token' => [Platform::Instagram, null], 'facebook is limited per page token' => [Platform::Facebook, null], 'x shares the app limit' => [Platform::X, 'x:app'], - 'youtube shares the project quota' => [Platform::YouTube, 'youtube:project'], + 'youtube is limited only per channel' => [Platform::YouTube, null], 'tiktok shares the app limit' => [Platform::TikTok, 'tiktok:app'], ]);