YouTube Data API v3 gives you 10,000 quota units per day. When you're fetching trending videos from 9 regions, quota management becomes critical. Here's how TopVideoHub handles it.
Understanding Quota Costs
| Operation | Cost | Notes |
|---|---|---|
videos.list |
1 unit | Per request, up to 50 results |
search.list |
100 units | Expensive! Avoid when possible |
channels.list |
1 unit | Per request |
videoCategories.list |
1 unit | Fetch once, cache forever |
playlistItems.list |
1 unit | Per request |
The key insight: videos.list with chart=mostPopular costs 1 unit and returns trending videos. Never use search.list for trending — it costs 100x more.
Quota Tracking System
class QuotaTracker {
private const int DAILY_LIMIT = 10000;
private const int WARNING_THRESHOLD = 8000;
private const int CRITICAL_THRESHOLD = 9000;
public function __construct(private readonly \PDO $db) {
$this->ensureTable();
}
private function ensureTable(): void {
$this->db->exec("
CREATE TABLE IF NOT EXISTS api_quota (
id INTEGER PRIMARY KEY,
key_id TEXT NOT NULL,
date TEXT NOT NULL,
operation TEXT NOT NULL,
cost INTEGER NOT NULL,
region TEXT,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_quota_date
ON api_quota(key_id, date);
");
}
public function record(string $keyId, string $operation, int $cost, ?string $region = null): void {
$this->db->prepare(
"INSERT INTO api_quota (key_id, date, operation, cost, region)
VALUES (?, ?, ?, ?, ?)"
)->execute([$keyId, date('Y-m-d'), $operation, $cost, $region]);
}
public function usedToday(string $keyId): int {
return (int) $this->db->prepare(
"SELECT COALESCE(SUM(cost), 0) FROM api_quota
WHERE key_id = ? AND date = ?"
)->execute([$keyId, date('Y-m-d')])
->fetchColumn();
}
public function remainingToday(string $keyId): int {
return self::DAILY_LIMIT - $this->usedToday($keyId);
}
public function status(string $keyId): string {
$used = $this->usedToday($keyId);
return match(true) {
$used >= self::CRITICAL_THRESHOLD => 'CRITICAL',
$used >= self::WARNING_THRESHOLD => 'WARNING',
default => 'OK',
};
}
}
Multi-Key Rotation
Multiple API keys spread the quota load:
class KeyRotator {
public function __construct(
private readonly \PDO $db,
private readonly QuotaTracker $quota,
) {}
public function getBestKey(): ?string {
$keys = $this->db->query(
"SELECT key_value FROM api_keys WHERE active = 1"
)->fetchAll(\PDO::FETCH_COLUMN);
if (empty($keys)) return null;
// Find key with most remaining quota
$best = null;
$bestRemaining = -1;
foreach ($keys as $key) {
$remaining = $this->quota->remainingToday($key);
if ($remaining > $bestRemaining) {
$best = $key;
$bestRemaining = $remaining;
}
}
// Don't return a key that's critically low
if ($bestRemaining < 500) {
return null; // All keys exhausted
}
return $best;
}
}
Staggered Cron Schedules
Different deployment targets fetch at different intervals to spread load:
# Site 1 (dwv): Every 2 hours — 12 fetches/day
20 */2 * * * php /path/to/fetch_videos.php
# Site 2 (tvh): Every 4 hours — 6 fetches/day
28 */4 * * * php /path/to/fetch_videos.php
# Site 3 (vvv): Every 7 hours — ~3 fetches/day
35 */7 * * * php /path/to/fetch_videos.php
Smart Fetching: Prioritize What Changed
Don't re-fetch regions where nothing changed:
function shouldFetchRegion(string $region, \PDO $db): bool {
$lastFetch = $db->prepare(
"SELECT MAX(fetched_at) FROM video_regions WHERE region = ?"
)->execute([$region])->fetchColumn();
if ($lastFetch === null) return true;
$elapsed = time() - strtotime($lastFetch);
// High-traffic regions: fetch more often
$interval = match($region) {
'US', 'JP', 'KR' => 7200, // 2 hours
'GB', 'TW', 'SG' => 14400, // 4 hours
'VN', 'TH', 'HK' => 25200, // 7 hours
};
return $elapsed >= $interval;
}
Quota Budget Calculator
Before fetching, calculate if we have enough quota:
function calculateFetchBudget(array $regions, QuotaTracker $quota, string $keyId): array {
$remaining = $quota->remainingToday($keyId);
// Each region fetch costs:
// - 1 unit for videos.list (trending)
// - Optionally 1 unit per category (up to 10)
$costPerRegion = 1; // Just trending, no category breakdown
$totalCost = count($regions) * $costPerRegion;
if ($totalCost > $remaining) {
// Prioritize: fetch high-traffic regions first
$priority = ['JP', 'KR', 'US', 'TW', 'GB', 'SG', 'TH', 'VN', 'HK'];
$affordable = (int) floor($remaining / $costPerRegion);
return array_slice(
array_intersect($priority, $regions),
0,
$affordable
);
}
return $regions;
}
Monitoring Dashboard Output
At the end of each fetch, output a quota report:
function printQuotaReport(QuotaTracker $quota, string $keyId): void {
$used = $quota->usedToday($keyId);
$remaining = $quota->remainingToday($keyId);
$status = $quota->status($keyId);
$pct = round(($used / 10000) * 100, 1);
echo "\n=== Quota Report ===";
echo "\nKey: ..." . substr($keyId, -8);
echo "\nUsed: {$used} / 10000 ({$pct}%)";
echo "\nRemaining: {$remaining}";
echo "\nStatus: {$status}";
echo "\n===================\n";
}
With this system, TopVideoHub stays well within quota limits while keeping content fresh across all 9 regions. The key is being smart about what you fetch and when.
Top comments (0)