Introduction
Keeping video content fresh across multiple regions requires a well-designed cron system. At ViralVidVault, a single cron command handles fetching trending videos from 7 European regions, refreshing stale content, and cleaning up old records. Here's how it all fits together.
The Cron Architecture
One script, multiple steps, executed sequentially:
<?php
// cron/fetch_videos.php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
$startTime = microtime(true);
$db = new Database(__DIR__ . '/../data/videos.db');
$youtube = new YouTubeClient($_ENV['YOUTUBE_API_KEY']);
$logger = new CronLogger(__DIR__ . '/../data/cron.log');
$regions = explode(',', $_ENV['FETCH_REGIONS'] ?? 'US,GB,PL,NL,SE,NO,AT');
try {
// STEP 1: Fetch globally popular videos
$logger->info('Step 1: Fetching popular videos...');
$popular = $youtube->getPopular(maxResults: 50);
$db->upsertVideos($popular);
$logger->info("Step 1 complete: " . count($popular) . " popular videos");
// STEP 2: Sync categories
$logger->info('Step 2: Syncing categories...');
$categories = $youtube->getCategories('US');
$db->syncCategories($categories);
$logger->info("Step 2 complete: " . count($categories) . " categories");
// STEP 3: Regional trending loop
$logger->info('Step 3: Fetching regional trending...');
$totalRegional = 0;
foreach ($regions as $region) {
$region = trim($region);
$trending = $youtube->getTrending($region, maxResults: 25);
$db->upsertVideos($trending, region: $region);
$totalRegional += count($trending);
$logger->info(" [{$region}] " . count($trending) . " videos");
usleep(300000); // 300ms between regions
}
$logger->info("Step 3 complete: {$totalRegional} regional videos");
// STEP 4: Refresh stale content
$logger->info('Step 4: Checking stale content...');
$checker = new FreshnessChecker($db->getPdo(), $youtube);
$freshResult = $checker->checkBatch(batchSize: 50);
$logger->info("Step 4 complete: {$freshResult->staleCount} stale, {$freshResult->freshCount} fresh");
// STEP 5: Cleanup old content
$logger->info('Step 5: Cleaning up...');
$cleaner = new ContentCleaner($db->getPdo());
$cleanReport = $cleaner->run();
$logger->info("Step 5 complete: {$cleanReport}");
// STEP 6: Rebuild search index
$logger->info('Step 6: Rebuilding search index...');
$db->rebuildSearchIndex();
$logger->info('Step 6 complete');
} catch (\Throwable $e) {
$logger->error("Cron failed: {$e->getMessage()}");
$logger->error($e->getTraceAsString());
exit(1);
}
$elapsed = round(microtime(true) - $startTime, 1);
$logger->info("Cron complete in {$elapsed}s");
The Logger
<?php
class CronLogger
{
private string $logFile;
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message): void
{
$this->write('INFO', $message);
}
public function error(string $message): void
{
$this->write('ERROR', $message);
}
private function write(string $level, string $message): void
{
$timestamp = date('Y-m-d H:i:s');
$line = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
echo $line; // Also output to stdout for cron email
}
}
Crontab Configuration
Different sites can run at different intervals:
# ViralVidVault - every 7 hours
35 */7 * * * cd /home/user/viralvidvault && php cron/fetch_videos.php >> data/cron_output.log 2>&1
Environment-Based Region Configuration
Each deployment defines its own regions via environment variables:
# .env for viralvidvault.com
FETCH_REGIONS=US,GB,PL,NL,SE,NO,AT
YOUTUBE_API_KEY=your_key_here
// Reading regions from environment
$regions = array_map('trim', explode(',', $_ENV['FETCH_REGIONS'] ?? 'US,GB'));
Handling API Quota Across Regions
With 7 regions and various API calls per cron run, quota management matters:
<?php
class QuotaTracker
{
private int $used = 0;
private int $daily_limit;
public function __construct(int $dailyLimit = 10000)
{
$this->daily_limit = $dailyLimit;
}
public function track(string $operation, int $cost): void
{
$this->used += $cost;
}
public function canAfford(int $cost): bool
{
return ($this->used + $cost) <= $this->daily_limit;
}
public function remaining(): int
{
return max(0, $this->daily_limit - $this->used);
}
}
// Usage in cron
$quota = new QuotaTracker(dailyLimit: 10000);
foreach ($regions as $region) {
if (!$quota->canAfford(2)) { // 1 for trending + 1 for stats
$logger->info("Quota limit reached, skipping remaining regions");
break;
}
$trending = $youtube->getTrending($region);
$quota->track('trending', 1);
$db->upsertVideos($trending, region: $region);
}
$logger->info("Quota used: {$quota->remaining()} remaining");
Monitoring Cron Health
A simple health check endpoint:
// Route: /api/health
$lastCron = $db->query('SELECT MAX(fetched_at) as last FROM videos')->fetchColumn();
$hoursSince = (time() - strtotime($lastCron)) / 3600;
$healthy = $hoursSince < 24; // Alert if no fetch in 24h
header('Content-Type: application/json');
echo json_encode([
'status' => $healthy ? 'ok' : 'stale',
'last_fetch' => $lastCron,
'hours_since' => round($hoursSince, 1),
'video_count' => $db->query('SELECT COUNT(*) FROM videos WHERE is_active=1')->fetchColumn(),
]);
Real-World Stats
On viralvidvault.com, a typical cron run:
[2026-03-02 14:35:01] [INFO] Step 1: Fetching popular videos...
[2026-03-02 14:35:03] [INFO] Step 1 complete: 50 popular videos
[2026-03-02 14:35:03] [INFO] Step 2: Syncing categories...
[2026-03-02 14:35:04] [INFO] Step 2 complete: 18 categories
[2026-03-02 14:35:04] [INFO] Step 3: Fetching regional trending...
[2026-03-02 14:35:05] [INFO] [US] 25 videos
[2026-03-02 14:35:06] [INFO] [GB] 25 videos
[2026-03-02 14:35:06] [INFO] [PL] 25 videos
[2026-03-02 14:35:07] [INFO] [NL] 25 videos
[2026-03-02 14:35:08] [INFO] [SE] 25 videos
[2026-03-02 14:35:08] [INFO] [NO] 25 videos
[2026-03-02 14:35:09] [INFO] [AT] 25 videos
[2026-03-02 14:35:09] [INFO] Step 3 complete: 175 regional videos
[2026-03-02 14:35:09] [INFO] Step 4: Checking stale content...
[2026-03-02 14:35:11] [INFO] Step 4 complete: 3 stale, 47 fresh
[2026-03-02 14:35:11] [INFO] Step 5: Cleaning up...
[2026-03-02 14:35:12] [INFO] Step 5 complete: Cleanup: 22 removed
[2026-03-02 14:35:12] [INFO] Step 6: Rebuilding search index...
[2026-03-02 14:35:13] [INFO] Step 6 complete
[2026-03-02 14:35:13] [INFO] Cron complete in 12.1s
12 seconds for a full 7-region fetch, stale check, cleanup, and indexing. SQLite and PHP handle it with ease.
Key Takeaways
- One cron script, sequential steps — keeps it simple and debuggable
- Use environment variables for region configuration
- Add rate limiting between region fetches
- Track API quota to avoid exceeding limits
- Log everything for post-mortem debugging
- Build a health check endpoint for monitoring
Part of the "Building ViralVidVault" series. Explore the results at ViralVidVault.
Top comments (0)