DEV Community

ahmet gedik
ahmet gedik

Posted on

Building a Global Video Streaming Platform with PHP

In this article, I'll walk through the architecture of TrendVidStream, a video streaming platform that aggregates trending content from 8 countries: US, UK, Switzerland, Denmark, UAE, Belgium, Czech Republic, and Finland.

Architecture Overview

The platform runs on PHP 8.3 with SQLite as the sole database, deployed across 4 LiteSpeed servers. Each server handles different global regions with its own cron schedule.

┌─────────────────────────────────────────────────────────────┐
│                     TrendVidStream Architecture              │
├─────────────────────────────────────────────────────────────┤
│  YouTube API  ──>  PHP Fetcher  ──>  SQLite DB             │
│       │                  │              │                    │
│  8 Regions        Normalize &       3-Tier Cache           │
│  per cron          Score            ├─ LiteSpeed            │
│                                     ├─ PHP File             │
│                                     └─ PHP Data             │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Data Model

The core data model is simple. Videos have an ID, title, description, region, category, and metadata. SQLite stores everything:

<?php

class Database
{
    private PDO $pdo;

    public function __construct(string $dbPath)
    {
        $this->pdo = new PDO("sqlite:$dbPath");
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->pdo->exec('PRAGMA journal_mode=WAL');
        $this->pdo->exec('PRAGMA synchronous=NORMAL');
    }

    public function upsertVideo(array $video): void
    {
        $stmt = $this->pdo->prepare('
            INSERT INTO videos (id, title, description, thumbnail, channel_id, channel_title, category_id, region, published_at, fetched_at)
            VALUES (:id, :title, :description, :thumbnail, :channel_id, :channel_title, :category_id, :region, :published_at, :fetched_at)
            ON CONFLICT(id, region) DO UPDATE SET
                title = excluded.title,
                thumbnail = excluded.thumbnail,
                fetched_at = excluded.fetched_at
        ');
        $stmt->execute($video);
    }
}
Enter fullscreen mode Exit fullscreen mode

Region Configuration

Each server has its own region assignments:

<?php

enum Region: string
{
    case US = 'US';
    case GB = 'GB';
    case CH = 'CH';
    case DK = 'DK';
    case AE = 'AE';
    case BE = 'BE';
    case CZ = 'CZ';
    case FI = 'FI';
}

// Server-specific config
const FETCH_REGIONS = [Region::US, Region::GB, Region::CH, Region::DK, Region::AE, Region::BE, Region::CZ, Region::FI];
Enter fullscreen mode Exit fullscreen mode

The Fetch Pipeline

The cron job runs a multi-step pipeline:

<?php

class VideoFetcher
{
    public function run(): void
    {
        // Step 1: Fetch popular videos
        $this->fetchPopular();

        // Step 2: Update categories
        $this->updateCategories();

        // Step 3: Region-specific trending
        foreach (FETCH_REGIONS as $region) {
            $this->fetchTrending($region);
            usleep(500000); // Rate limiting
        }

        // Step 4: Refresh stale entries
        $this->refreshStale();

        // Step 5: Cleanup old data
        $this->cleanup();

        // Step 6: Rebuild search index
        $this->rebuildIndex();
    }
}
Enter fullscreen mode Exit fullscreen mode

Caching Strategy

The 3-tier cache keeps the platform fast:

<?php

class Cache
{
    const int TTL_CATEGORIES = 86400;  // 24 hours
    const int TTL_HOME = 10800;        // 3 hours
    const int TTL_WATCH = 21600;       // 6 hours
    const int TTL_SEARCH = 600;        // 10 minutes

    public function get(string $key): mixed
    {
        $file = $this->path($key);
        if (!file_exists($file)) return null;

        $data = unserialize(file_get_contents($file));
        if ($data['expires'] < time()) {
            unlink($file);
            return null;
        }
        return $data['value'];
    }

    public function set(string $key, mixed $value, int $ttl): void
    {
        file_put_contents($this->path($key), serialize([
            'value' => $value,
            'expires' => time() + $ttl,
        ]));
    }
}
Enter fullscreen mode Exit fullscreen mode

You can see this architecture in action at TrendVidStream, where it handles trending video aggregation from all 8 regions.

Conclusion

PHP 8.3 + SQLite + LiteSpeed is a powerful combination for content platforms. The stack is simple, cheap to host, and performs well. Sometimes boring technology is the best technology.

Check out trendvidstream.com to see the platform live.

Why This Stack Works for Global Platforms

The combination of PHP 8.3, SQLite, and LiteSpeed might seem unconventional for a platform serving 8 countries, but it excels for several reasons.

First, deployment is trivially simple. Each server is self-contained with its own database file. There are no cross-server dependencies, no shared database connections, and no replication to manage. When we deploy to all 4 servers simultaneously via FTP, each one gets a complete, independent application.

Second, the cost is remarkably low. Four shared hosting plans total under $50 per month. Compare this to a managed Kubernetes cluster, a PostgreSQL database, and a Redis cache, which could easily cost $200-500 per month for a similar workload.

Third, SQLite performs excellently for our read-heavy workload. With WAL mode enabled and proper indexing, query times are measured in single-digit milliseconds. The 3-tier cache means most requests never hit the database at all.

The architecture behind TrendVidStream proves that global platforms do not require complex infrastructure. What matters is choosing the right tool for the actual workload, not the theoretical one.

What is Next

Future improvements include adding more regions, implementing cross-region video deduplication, and building a public API for researchers interested in comparative trending analysis. The modular architecture makes these additions straightforward without rearchitecting the core system.

Top comments (0)