Trade-offs & Alternatives

📖 10 min read 📄 Part 7 of 10

Video Conferencing System - Tradeoffs and Alternatives

WebRTC Architecture Tradeoffs

SFU vs MCU vs P2P Comparison

Aspect SFU (Selective Forwarding Unit) MCU (Multipoint Control Unit) P2P (Peer-to-Peer)
Server CPU Low (no transcoding) High (mixing/transcoding) None
Client CPU High (multiple decode) Low (single decode) Medium
Bandwidth High (multiple streams) Low (single stream) Highest (mesh)
Scalability Good (500+ users) Limited (50-100 users) Poor (5-10 users)
Quality Best (original quality) Degraded (compression) Variable
Latency Low (150ms) Medium (300ms) Lowest (50ms)
Cost Medium High Low

SFU Architecture (Chosen Approach)

Pros:
✅ Preserves original video quality
✅ Lower server CPU requirements
✅ Scales to large meetings (1000+ participants)
✅ Flexible client-side rendering
✅ Better for screen sharing

Cons:
❌ Higher client CPU usage (multiple decoding)
❌ Higher bandwidth requirements
❌ Complex client implementation
❌ Battery drain on mobile devices

Implementation Details:

// SFU handles selective forwarding
class SFUServer {
    forwardStream(fromParticipant, toParticipants, streamType) {
        // No transcoding - just forward the stream
        const stream = fromParticipant.getStream(streamType);
        
        toParticipants.forEach(participant => {
            if (participant.wantsStream(streamType, fromParticipant.id)) {
                participant.sendStream(stream);
            }
        });
    }
}

MCU Alternative

Pros:
✅ Lower client CPU/bandwidth usage
✅ Consistent quality for all participants
✅ Easier client implementation
✅ Better for low-end devices

Cons:
❌ High server CPU requirements
❌ Quality degradation from mixing
❌ Limited scalability
❌ Higher latency
❌ Complex server implementation

When to Use MCU:

  • Large webinars (1000+ viewers, few speakers)
  • Low-bandwidth environments
  • Legacy device support
  • Recording optimization

Media Transport Protocols

WebRTC vs Traditional Streaming

Protocol WebRTC RTMP HLS WebSocket
Latency 150ms 3-5s 10-30s 50ms
Browser Support Native Plugin Native Native
Firewall Traversal Excellent Poor Good Good
Scalability Medium High Very High Low
Quality Adaptive Fixed Adaptive Variable
Complexity High Medium Low Low

WebRTC (Chosen for Real-time)

// WebRTC Peer Connection Setup
const peerConnection = new RTCPeerConnection({
    iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { 
            urls: 'turn:turn.example.com:3478',
            username: 'user',
            credential: 'pass'
        }
    ]
});

// Adaptive bitrate based on network conditions
peerConnection.getSenders().forEach(sender => {
    const params = sender.getParameters();
    params.encodings[0].maxBitrate = adaptiveBitrate;
    sender.setParameters(params);
});

HLS Alternative for Recordings

// HLS for recorded content playback
const hlsConfig = {
    segments: 6,        // 6-second segments
    targetDuration: 10, // 10-second target duration
    adaptiveBitrate: true,
    qualities: [
        { resolution: '1080p', bitrate: 4000000 },
        { resolution: '720p',  bitrate: 2000000 },
        { resolution: '480p',  bitrate: 1000000 }
    ]
};

Database Architecture Tradeoffs

SQL vs NoSQL for Different Use Cases

Data Type PostgreSQL (SQL) Cassandra (NoSQL) Redis (Cache)
User Profiles ✅ ACID compliance ❌ Eventual consistency ❌ Volatile
Meeting Metadata ✅ Complex queries ❌ Limited queries ❌ Memory limits
Chat Messages ❌ Write scaling ✅ High write throughput ✅ Real-time
Analytics ❌ Time series ✅ Time series optimized ❌ Not persistent
Session State ❌ High latency ❌ Overkill ✅ Fast access

PostgreSQL for Core Data

-- Strong consistency for critical data
BEGIN TRANSACTION;
    INSERT INTO meetings (title, host_user_id) VALUES ('Team Call', 'user123');
    INSERT INTO meeting_participants (meeting_id, user_id, role) 
    VALUES (currval('meetings_meeting_id_seq'), 'user123', 'host');
COMMIT;

Pros: ACID compliance, complex queries, mature ecosystem Cons: Write scaling limitations, higher latency

Cassandra Alternative for Analytics

-- Optimized for time-series data
CREATE TABLE meeting_metrics (
    meeting_id UUID,
    timestamp TIMESTAMP,
    metric_name TEXT,
    metric_value DOUBLE,
    PRIMARY KEY (meeting_id, timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC);

Pros: Linear scalability, time-series optimization, high availability Cons: Limited query flexibility, eventual consistency

Authentication Strategies

JWT vs Session-Based Authentication

Aspect JWT Tokens Session Cookies
Scalability Stateless (better) Stateful (session store)
Security Token exposure risk Server-side validation
Performance No DB lookup DB lookup required
Revocation Complex Immediate
Mobile Support Excellent Limited
Size Larger payload Small cookie

JWT Implementation (Chosen)

// JWT with refresh token strategy
const authTokens = {
    accessToken: jwt.sign(
        { userId, permissions }, 
        secret, 
        { expiresIn: '15m' }
    ),
    refreshToken: jwt.sign(
        { userId, tokenVersion }, 
        refreshSecret, 
        { expiresIn: '7d' }
    )
};

// Automatic token refresh
class AuthManager {
    async refreshTokenIfNeeded(token) {
        const decoded = jwt.decode(token);
        const timeUntilExpiry = decoded.exp * 1000 - Date.now();
        
        if (timeUntilExpiry < 5 * 60 * 1000) { // 5 minutes
            return await this.refreshToken();
        }
        return token;
    }
}

OAuth 2.0 Alternative

// OAuth 2.0 with PKCE for enhanced security
const authUrl = `https://auth.example.com/oauth/authorize?
    response_type=code&
    client_id=${clientId}&
    redirect_uri=${redirectUri}&
    scope=meetings:read meetings:write&
    code_challenge=${codeChallenge}&
    code_challenge_method=S256`;

When to Use OAuth: Enterprise SSO, third-party integrations, compliance requirements

Recording Architecture Tradeoffs

Server-Side vs Client-Side Recording

Approach Server-Side Recording Client-Side Recording
Quality Consistent, high Variable, depends on client
CPU Load High server load Distributed to clients
Bandwidth Efficient (single stream) Inefficient (multiple uploads)
Reliability High (controlled environment) Lower (client failures)
Privacy Centralized (compliance) Distributed (privacy)
Cost High server costs Lower server costs

Server-Side Recording (Chosen)

class ServerSideRecorder {
    async startRecording(meetingId) {
        const compositor = new MediaCompositor({
            width: 1920,
            height: 1080,
            framerate: 30,
            layout: 'grid' // or 'speaker', 'gallery'
        });
        
        // Combine all participant streams
        const participants = await this.getParticipants(meetingId);
        participants.forEach(p => {
            compositor.addStream(p.videoStream, p.audioStream);
        });
        
        // Encode and store
        const encoder = new H264Encoder({ bitrate: 4000000 });
        const recordingStream = compositor.pipe(encoder);
        
        return this.saveToStorage(recordingStream, meetingId);
    }
}

Distributed Recording Alternative

// Each client records locally, server stitches later
class DistributedRecorder {
    async startClientRecording(participantId) {
        const mediaRecorder = new MediaRecorder(localStream, {
            mimeType: 'video/webm;codecs=vp9,opus',
            videoBitsPerSecond: 2000000
        });
        
        // Upload chunks periodically
        mediaRecorder.ondataavailable = (event) => {
            this.uploadChunk(participantId, event.data);
        };
    }
    
    async stitchRecording(meetingId) {
        const chunks = await this.getRecordingChunks(meetingId);
        return this.combineChunks(chunks);
    }
}

Network Architecture Alternatives

CDN vs Edge Computing vs Regional Deployment

Approach Global CDN Edge Computing Regional Clusters
Latency Low (cached content) Lowest (compute at edge) Medium (regional)
Cost Medium High Low
Complexity Low High Medium
Scalability Excellent Good Limited
Real-time Poor Excellent Good

Edge Computing for WebRTC (Chosen)

// Cloudflare Workers for WebRTC optimization
export default {
    async fetch(request, env) {
        if (request.url.includes('/webrtc/')) {
            return handleWebRTCRequest(request, env);
        }
        return fetch(request);
    }
};

async function handleWebRTCRequest(request, env) {
    const country = request.cf.country;
    const colo = request.cf.colo;
    
    // Route to nearest media server
    const mediaServer = await findNearestServer(country, colo);
    
    // Optimize SDP for regional characteristics
    const optimizedRequest = await optimizeForRegion(request, country);
    
    return fetch(mediaServer.url, optimizedRequest);
}

Regional Deployment Alternative

# Traditional regional deployment
Regions:
  us-east-1:
    media_servers: 50
    signaling_servers: 10
    database: primary
    
  eu-west-1:
    media_servers: 30
    signaling_servers: 6
    database: replica
    
  ap-southeast-1:
    media_servers: 20
    signaling_servers: 4
    database: replica

Mobile vs Desktop Optimization

Native Apps vs Progressive Web Apps

Platform Native App Progressive Web App
Performance Excellent Good
Development Cost High (multiple platforms) Low (single codebase)
App Store Required Optional
Updates Store approval Instant
Device Integration Full access Limited
Offline Support Excellent Good
Battery Usage Optimized Higher

Native Mobile Implementation

// iOS WebRTC optimization
class iOSWebRTCManager {
    func optimizeForMobile() {
        // Use hardware encoding when available
        let videoEncoderFactory = RTCDefaultVideoEncoderFactory()
        videoEncoderFactory.preferredCodec = RTCVideoCodecInfo(name: kRTCVideoCodecH264Name)
        
        // Optimize for battery life
        let constraints = RTCMediaConstraints(
            mandatoryConstraints: [
                "maxFrameRate": "15", // Lower frame rate
                "maxWidth": "640",    // Lower resolution
                "maxHeight": "480"
            ],
            optionalConstraints: nil
        )
    }
}

PWA Alternative

// Progressive Web App with service worker
class PWAVideoConf {
    async initialize() {
        // Register service worker for offline support
        if ('serviceWorker' in navigator) {
            await navigator.serviceWorker.register('/sw.js');
        }
        
        // Use WebRTC with mobile optimizations
        const constraints = {
            video: {
                width: { ideal: 640 },
                height: { ideal: 480 },
                frameRate: { ideal: 15, max: 30 }
            },
            audio: {
                echoCancellation: true,
                noiseSuppression: true,
                autoGainControl: true
            }
        };
    }
}

Monitoring and Observability Tradeoffs

Push vs Pull Metrics Collection

Approach Push (StatsD/DataDog) Pull (Prometheus)
Network Higher (constant push) Lower (on-demand)
Reliability Fire-and-forget Guaranteed collection
Scalability Good Excellent
Real-time Excellent Good
Storage External service Self-hosted
Cost Higher (SaaS) Lower (self-hosted)

Prometheus Implementation (Chosen)

// Prometheus metrics collection
const promClient = require('prom-client');

const meetingGauge = new promClient.Gauge({
    name: 'active_meetings_total',
    help: 'Total number of active meetings',
    labelNames: ['region', 'server_id']
});

const participantHistogram = new promClient.Histogram({
    name: 'meeting_participants_duration',
    help: 'Duration of participant sessions',
    buckets: [60, 300, 900, 1800, 3600, 7200] // seconds
});

// Expose metrics endpoint
app.get('/metrics', (req, res) => {
    res.set('Content-Type', promClient.register.contentType);
    res.end(promClient.register.metrics());
});

DataDog Alternative

// DataDog StatsD integration
const StatsD = require('node-statsd');
const client = new StatsD();

class DataDogMetrics {
    recordMeetingJoin(meetingId, participantCount) {
        client.increment('meeting.join', 1, {
            meeting_id: meetingId,
            region: process.env.AWS_REGION
        });
        
        client.gauge('meeting.participants', participantCount, {
            meeting_id: meetingId
        });
    }
}

Decision Matrix

Architecture Decision Framework

Decision Criteria Weights:
  Performance: 30%
  Scalability: 25%
  Cost: 20%
  Complexity: 15%
  Reliability: 10%

SFU vs MCU Decision:
  SFU Score: 8.2/10
    - Performance: 9/10 (low latency)
    - Scalability: 9/10 (1000+ users)
    - Cost: 7/10 (medium server cost)
    - Complexity: 6/10 (complex client)
    - Reliability: 8/10 (distributed load)
  
  MCU Score: 6.8/10
    - Performance: 7/10 (higher latency)
    - Scalability: 5/10 (limited users)
    - Cost: 5/10 (high server cost)
    - Complexity: 8/10 (simple client)
    - Reliability: 9/10 (centralized)

Winner: SFU Architecture

Technology Selection Summary

Final Architecture Decisions:

Media Architecture: SFU (Selective Forwarding Unit)
  Reason: Best balance of quality, scalability, and performance

Transport Protocol: WebRTC
  Reason: Native browser support, low latency, adaptive quality

Database Strategy: Polyglot Persistence
  - PostgreSQL: Core data (ACID compliance)
  - Redis: Sessions and real-time data
  - Cassandra: Analytics and time-series

Authentication: JWT with refresh tokens
  Reason: Stateless, mobile-friendly, scalable

Recording: Server-side with distributed processing
  Reason: Consistent quality, compliance, reliability

Deployment: Edge computing with regional clusters
  Reason: Optimal latency with cost efficiency

Monitoring: Prometheus + Grafana
  Reason: Open source, scalable, cost-effective