Enunciado del Problema
Diseña una plataforma de streaming de video como YouTube o Netflix. El sistema debe manejar subidas de video, procesamiento, almacenamiento y entrega a millones de espectadores concurrentes. Este problema es desafiante porque el video es el tipo de contenido más intensivo en ancho de banda en internet, requiriendo decisiones arquitectónicas cuidadosas sobre codificación, almacenamiento y entrega.
Paso 1: Requisitos
Requisitos Funcionales
- Los usuarios pueden subir videos (hasta 1 hora, múltiples formatos)
- Los videos se transcodifican en múltiples resoluciones y formatos
- Streaming de bitrate adaptativo basado en condiciones de red del espectador
- Reproducción de video con búsqueda, pausa y reanudación
- Generación de miniaturas y preview de video
- Sistema básico de recomendación
Requisitos No Funcionales
- Soporte para 1 mil millones de visualizaciones diarias de video
- Tiempo rápido de inicio (<2 segundos al primer frame)
- Alta disponibilidad (99.99%)
- Entrega global con baja latencia
- Almacenamiento y ancho de banda eficientes en costos
Estimaciones de Escala
| Métrica | Estimación |
|---|---|
| Videos subidos por día | 500,000 |
| Tamaño promedio de video (original) | 500 MB |
| Volumen diario de subidas | ~250 TB |
| Visualizaciones diarias | 1 mil millones |
| Duración promedio de visualización | 5 minutos |
| Espectadores concurrentes pico | ~5 millones |
Paso 2: Pipeline de Subida y Procesamiento de Video
El pipeline de subida es un proceso de múltiples etapas que transforma un video crudo subido en múltiples formatos listos para streaming. Esta es la parte más intensiva en cómputo del sistema.
// Pipeline de subida y procesamiento
interface VideoUpload {
id: string;
userId: string;
originalFileName: string;
originalUrl: string; // Ubicación en S3 del archivo subido
status: "uploading" | "processing" | "ready" | "failed";
metadata: VideoMetadata;
createdAt: Date;
}
interface VideoMetadata {
title: string;
description: string;
tags: string[];
duration: number; // segundos
resolution: string; // "1920x1080"
codec: string;
frameRate: number;
fileSize: number; // bytes
}
interface ProcessedVideo {
videoId: string;
variants: VideoVariant[];
thumbnails: string[];
manifest: string; // URL del manifiesto HLS/DASH
}
interface VideoVariant {
resolution: string; // "1080p", "720p", "480p", "360p"
bitrate: number; // kbps
codec: string;
segmentUrls: string[]; // URLs de segmentos de video
}
// Flujo de subida
class VideoUploadService {
async initiateUpload(userId: string, metadata: Partial<VideoMetadata>): Promise<UploadSession> {
// Generar una URL pre-firmada de S3 para subida directa
// El cliente sube directamente a S3 (evita nuestros servidores)
const uploadId = generateId();
const s3Key = `uploads/${userId}/${uploadId}/original`;
const presignedUrl = await this.s3.getSignedUrl("putObject", {
Bucket: "video-uploads",
Key: s3Key,
Expires: 3600, // 1 hora
ContentType: "video/*",
});
// Crear registro de subida
await this.db.videoUploads.insert({
id: uploadId,
userId,
status: "uploading",
originalUrl: `s3://video-uploads/${s3Key}`,
metadata,
});
return {
uploadId,
presignedUrl,
// Para archivos grandes, usar subida multipart
multipartUrls: await this.generateMultipartUrls(s3Key),
};
}
// Activado por notificación de evento S3 cuando se completa la subida
async onUploadComplete(uploadId: string): Promise<void> {
await this.db.videoUploads.update(uploadId, { status: "processing" });
// Enviar al pipeline de procesamiento
await this.messageQueue.publish("video-processing", {
uploadId,
stages: ["validate", "transcode", "thumbnail", "manifest"],
});
}
}
Paso 3: Transcodificación y Bitrate Adaptativo
La transcodificación convierte el video original en múltiples resoluciones y bitrates. Esto permite al reproductor cambiar entre niveles de calidad basándose en el ancho de banda del espectador -- una técnica llamada Streaming de Bitrate Adaptativo (ABR).
Perfiles Estándar de Transcodificación
| Calidad | Resolución | Bitrate (video) | Caso de Uso |
|---|---|---|---|
| 4K UHD | 3840x2160 | 15-25 Mbps | Smart TVs, gama alta |
| 1080p HD | 1920x1080 | 4-8 Mbps | Escritorio, buen WiFi |
| 720p HD | 1280x720 | 2-4 Mbps | Móvil en WiFi |
| 480p SD | 854x480 | 1-2 Mbps | Móvil en 4G |
| 360p | 640x360 | 0.5-1 Mbps | Conexiones lentas |
// Pipeline de transcodificación (ejecuta en workers con GPU)
class TranscodingService {
private profiles = [
{ name: "1080p", width: 1920, height: 1080, bitrate: 5000 },
{ name: "720p", width: 1280, height: 720, bitrate: 2500 },
{ name: "480p", width: 854, height: 480, bitrate: 1200 },
{ name: "360p", width: 640, height: 360, bitrate: 700 },
];
async transcodeVideo(uploadId: string, sourceUrl: string): Promise<VideoVariant[]> {
const variants: VideoVariant[] = [];
// Obtener info del video fuente
const sourceInfo = await this.probe(sourceUrl);
// Solo transcodificar a resoluciones <= resolución fuente
const applicableProfiles = this.profiles.filter(
(p) => p.height <= sourceInfo.height
);
// Procesar cada perfil (puede paralelizarse entre workers)
for (const profile of applicableProfiles) {
const segments = await this.transcodeToProfile(sourceUrl, profile);
variants.push({
resolution: profile.name,
bitrate: profile.bitrate,
codec: "h264", // o h265/AV1 para mejor compresión
segmentUrls: segments,
});
}
return variants;
}
// Cada video se divide en segmentos pequeños (2-10 segundos)
// Esto permite cambio de bitrate adaptativo en los límites de segmento
private async transcodeToProfile(
sourceUrl: string,
profile: TranscodeProfile
): Promise<string[]> {
// Usando FFmpeg internamente:
// ffmpeg -i input.mp4 \
// -vf scale=1280:720 \
// -b:v 2500k \
// -hls_time 6 \
// -hls_segment_filename "segment_%03d.ts" \
// output.m3u8
const outputDir = `processed/${profile.name}/`;
const segmentDuration = 6; // segundos
return this.ffmpeg.transcode({
input: sourceUrl,
output: outputDir,
width: profile.width,
height: profile.height,
bitrate: profile.bitrate,
segmentDuration,
format: "hls", // HTTP Live Streaming
});
}
}
Paso 4: Protocolos de Streaming (HLS y DASH)
El streaming moderno de video usa protocolos de streaming adaptativo basados en HTTP. Los dos protocolos dominantes son HLS (HTTP Live Streaming, creado por Apple) y DASH (Dynamic Adaptive Streaming over HTTP, estándar abierto).
HLS vs DASH
| Característica | HLS | DASH |
|---|---|---|
| Creado Por | Apple | MPEG (estándar abierto) |
| Formato de Manifiesto | .m3u8 (texto) | .mpd (XML) |
| Formato de Segmento | .ts o .fmp4 | .m4s (fMP4) |
| Soporte de Navegador | Nativo en Safari, via JS en otros | Via JS (dash.js, Shaka) |
| Soporte de Codec | H.264, H.265, AV1 | Cualquier codec |
| DRM | FairPlay | Widevine, PlayReady |
// Estructura de manifiesto HLS (simplificado)
// Master playlist (video.m3u8) - apunta a playlists variantes
const masterPlaylist = `
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720
720p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=854x480
480p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=700000,RESOLUTION=640x360
360p/playlist.m3u8
`;
// Variant playlist (720p/playlist.m3u8) - lista segmentos
const variantPlaylist = `
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXTINF:6.0,
segment_001.ts
#EXTINF:6.0,
segment_002.ts
#EXTINF:6.0,
segment_003.ts
#EXTINF:4.5,
segment_004.ts
#EXT-X-ENDLIST
`;
// El reproductor:
// 1. Descarga el master playlist
// 2. Selecciona una variante basada en el ancho de banda actual
// 3. Descarga segmentos de esa variante
// 4. Si el ancho de banda cambia, cambia a una variante diferente en el siguiente límite de segmento
Paso 5: CDN para Entrega de Video
La entrega de video es la parte más intensiva en ancho de banda del sistema. Un CDN es esencial para entregar segmentos de video desde ubicaciones edge cercanas a los espectadores, reduciendo latencia y carga del origen.
- Videos populares: Cacheados en ubicaciones edge a nivel mundial. La tasa de aciertos de caché debe ser 90%+.
- Contenido de larga cola: Videos menos populares pueden servirse desde cachés regionales u origen.
- Caché por niveles: Edge -> Caché Regional -> Origen. Cada nivel reduce solicitudes al origen.
- Pre-calentamiento: Para contenido viral anticipado, empujar segmentos a ubicaciones edge proactivamente.
Paso 6: Generación de Miniaturas
class ThumbnailService {
async generateThumbnails(
videoUrl: string,
videoId: string,
duration: number
): Promise<string[]> {
const thumbnails: string[] = [];
// Generar miniaturas a intervalos regulares
const intervals = this.calculateIntervals(duration);
// ej., para un video de 60s: [0, 10, 20, 30, 40, 50]
for (const timestamp of intervals) {
// Extraer frame usando FFmpeg
// ffmpeg -i input.mp4 -ss 10 -frames:v 1 -vf scale=320:180 thumb.jpg
const thumbnailUrl = await this.extractFrame(videoUrl, timestamp, {
width: 320,
height: 180,
format: "webp", // Más pequeño que JPEG
});
thumbnails.push(thumbnailUrl);
}
// Generar preview de video (miniatura animada / sprite sheet)
const spriteSheet = await this.generateSpriteSheet(videoUrl, intervals);
// Almacenar URLs de miniaturas en metadatos del video
await this.db.videos.update(videoId, {
thumbnails,
defaultThumbnail: thumbnails[Math.floor(thumbnails.length / 3)],
spriteSheet,
});
return thumbnails;
}
}
Paso 7: Optimización de Almacenamiento y Costos
Desglose de Costos de Almacenamiento
- Subidas originales: Almacenadas temporalmente, eliminadas después del procesamiento (ahorra 500MB por video)
- Variantes transcodificadas: 5x el tamaño original (múltiples resoluciones). Un video de 500MB se convierte en ~2.5GB
- Almacenamiento por niveles: Mover videos viejos y raramente accedidos a almacenamiento más barato (S3 Glacier, almacenamiento frío)
- Eficiencia de codec: AV1 ofrece 30-50% mejor compresión que H.264 pero es más lento para codificar. Usar para videos populares.
// Estrategia de almacenamiento por niveles
class StorageTierManager {
// Los videos se clasifican en niveles basados en frecuencia de visualización
async tierVideo(videoId: string): Promise<void> {
const stats = await this.getViewStats(videoId);
const daysSinceUpload = this.daysSince(stats.uploadedAt);
const recentViews = stats.viewsLast30Days;
if (recentViews > 1000) {
// Caliente: mantener todas las variantes en almacenamiento rápido + CDN
await this.moveToTier(videoId, "hot"); // S3 Standard
} else if (recentViews > 10) {
// Tibio: mantener variantes populares, mover otras a almacenamiento más barato
await this.moveToTier(videoId, "warm"); // S3 Infrequent Access
// Solo mantener 720p y 360p en almacenamiento rápido
await this.archiveVariants(videoId, ["1080p", "4k"]);
} else if (daysSinceUpload > 365) {
// Frío: archivar al almacenamiento más barato, transcodificar bajo demanda
await this.moveToTier(videoId, "cold"); // S3 Glacier
// Mantener solo 360p, re-transcodificar mayor resolución bajo demanda
}
}
}
// Optimización de costos de ancho de banda:
// 1. Usar codecs eficientes (AV1 > H.265 > H.264) para contenido popular
// 2. Servir miniaturas WebP/AVIF en lugar de JPEG (50% más pequeñas)
// 3. Estimación de ancho de banda del lado del cliente para evitar sobre-servir calidad
// 4. Limitar calidad máxima basada en tamaño de pantalla (no servir 4K a teléfonos)
Resumen de Arquitectura
- Subida: Directo a S3 con URLs pre-firmadas, evitando servidores de aplicación
- Procesamiento: Pipeline asíncrono via cola de mensajes, workers GPU para transcodificación
- Almacenamiento: S3 con almacenamiento por niveles, CDN para entrega, clasificación basada en popularidad
- Streaming: HLS/DASH con bitrate adaptativo, segmentos de 6 segundos
- Entrega: CDN multi-nivel (edge -> regional -> origen)
- Metadatos: PostgreSQL para metadatos de video, Elasticsearch para búsqueda