Performance Implications of Running Invidious: A Technical Deep Dive

Invidious is a single-process, asynchronous Crystal application that achieves high concurrency through event-driven fibers, but its performance is constrained by outbound connection pool sizing, background job thread limits, and PostgreSQL write throughput.

Invidious serves as a lightweight alternative YouTube front-end that aggregates video metadata and user sessions through a Crystal-based web server. Understanding the performance implications of running Invidious is essential for administrators scaling instances beyond a few hundred simultaneous users, as the architecture balances CPU-bound JSON serialization against I/O-heavy database operations and persistent network connections to YouTube's infrastructure.

Architecture Overview

The Invidious server operates as three distinct subsystems that share the same Crystal runtime:

  • HTTP Front-End (Kemal): Handles inbound HTTP requests and API calls using Crystal’s event-driven fibers, providing high concurrency with minimal thread overhead. The server configuration resides in src/invidious.cr (lines 47-48), where the max request line size is explicitly enlarged to 16 KB to accommodate long YouTube URLs.
  • Outbound Connection Pools: The YT_POOL and GGPHT_POOL connection pools (defined in src/invidious.cr, lines 89-97) maintain persistent TCP sockets to https://www.youtube.com and https://yt3.ggpht.com for thumbnail fetching. These pools are instantiated in src/invidious/yt_backend/connection_pool.cr.
  • PostgreSQL Backend: Persists channel metadata, video statistics, and user preferences. The database schema is defined in migration files such as src/invidious/database/migrations/0002_create_videos_table.cr, with indexes that directly impact query latency during heavy read operations.

Concurrency and Threading Model

Invid leverages Crystal’s fibers (lightweight, cooperative threads) rather than OS threads, but CPU saturation remains a risk during metadata refresh cycles.

Background Job Workers

At startup, Invidious spawns several background jobs that run in dedicated fibers:

  • RefreshChannelsJob
  • RefreshFeedsJob
  • StatisticsRefreshJob

These jobs are registered in src/invidious.cr (lines 165-177) and respect the CONFIG.channel_threads and CONFIG.feed_threads parameters:


# src/invidious.cr – job registration

if CONFIG.channel_threads > 0
  Invidious::Jobs.register Invidious::Jobs::RefreshChannelsJob.new(PG_DB)
end

Each thread increases the rate at which channel metadata is refreshed, but also consumes CPU cycles and outbound network capacity. Command-line flags allow runtime tuning:


# CLI flags for thread configuration

parser.on("-c THREADS", "--channel-threads=THREADS",
          "Number of threads for refreshing channels (default: #{CONFIG.channel_threads})") do |number|
  CONFIG.channel_threads = number.to_i
end

parser.on("-f THREADS", "--feed-threads=THREADS",
          "Number of threads for refreshing feeds (default: #{CONFIG.feed_threads})") do |number|
  CONFIG.feed_threads = number.to_i
end

Static Asset Handling

In production mode (env = "production"), Kemal disables its built-in static file server in favor of a custom StaticAssetsHandler defined in src/invidious/http_server/static_assets_handler.cr. This handler injects aggressive caching headers (Cache-Control: max-age=2629800) to reduce repeat disk I/O, though the enlarged request buffer size (16 KB) consumes slightly more memory per connection.

Network I/O Bottlenecks

Outbound network performance depends heavily on the connection pool configuration stored in CONFIG.pool_size.

YouTube Connection Pooling

The YoutubeConnectionPool implementation reuses TLS sessions across persistent sockets to minimize handshake overhead. However, each concurrent connection holds a TCP socket and associated buffers:


# src/invidious.cr – connection pool instantiation

YT_POOL     = YoutubeConnectionPool.new(URI.parse("https://www.youtube.com"), capacity: CONFIG.pool_size)
GGPHT_POOL  = YoutubeConnectionPool.new(URI.parse("https://yt3.ggpht.com"), capacity: CONFIG.pool_size)

When user traffic spikes, undersized pools become the primary bottleneck. Increasing pool_size improves throughput but consumes additional memory and may trigger YouTube rate-limiting. For high-traffic deployments, scaling horizontally (multiple Invidious containers behind a load balancer) is preferable to massively increasing pool size on a single instance.

Database Performance Characteristics

PostgreSQL serves as both the metadata cache and user data store, creating distinct load patterns.

Write-Heavy Background Operations

The RefreshChannelsJob and RefreshFeedsJob write large volumes of JSON-encoded video metadata into tables such as videos, channels, and playlists. These operations are disk-I/O intensive and can saturate write throughput on modest hardware. Proper PostgreSQL tuning—including shared_buffers, work_mem, and max_connections settings—prevents the database from becoming the bottleneck.

Read-Heavy API Serialization

End-user API calls trigger JSON serialization logic in src/invidious/jsonify/api_v1/video_json.cr. This path is CPU-bound during heavy load. The application caps pagination at MAX_ITEMS_PER_PAGE = 1500 (line 71) to prevent excessive payload generation from monopolizing CPU time.

Memory Footprint

Typical Invidious instances consume less memory than full-stack video platforms, but certain features warrant attention:

  • Per-Request Context Objects: The add_context_storage_type calls (lines 34-36 in src/invidious.cr) register short-lived objects for Preferences and Invidious::User on every request.
  • In-Memory Caches: Popular video lists stored in the class-level POPULAR_VIDEOS channel consume a few megabytes of RAM and refresh periodically via PullPopularVideosJob.

A modest deployment (2 CPU cores, 2 GB RAM) comfortably serves several hundred simultaneous users, provided background job threads and connection pools remain conservatively sized.

Performance Tuning Recommendations

To optimize the performance implications of running Invidious, adjust these specific parameters in your config.yml or via CLI flags:

  1. Match Pool Size to Network Capacity: Set CONFIG.pool_size between 10-20 to balance outbound throughput against socket consumption and rate-limit risk.
  2. Limit Background Thread Count: Keep CONFIG.channel_threads and CONFIG.feed_threads low (2-4 threads each) to prevent CPU starvation of the Kemal HTTP workers.
  3. PostgreSQL Connection Sizing: Ensure max_connections exceeds the sum of your pool size plus background job workers to prevent connection exhaustion.
  4. Enable Static Asset Caching: Verify production mode is active to utilize the StaticAssetsHandler and its 30-day cache headers for compiled assets.

Summary

  • Invidious achieves concurrency through Crystal fibers rather than OS threads, minimizing memory overhead per connection.
  • The YT_POOL and GGPHT_POOL connection pools in src/invidious/yt_backend/connection_pool.cr are the primary scalability levers for outbound YouTube traffic.
  • Background jobs (RefreshChannelsJob, RefreshFeedsJob) drive CPU and database write load; limit via channel_threads and feed_threads to maintain responsiveness.
  • PostgreSQL performance depends on proper indexing (defined in migration files) and write-heavy workload tuning.
  • Horizontal scaling behind a load balancer is the recommended approach for high-traffic instances, as each container shares the same PostgreSQL backend.

Frequently Asked Questions

How much RAM does an Invidious instance require?

A typical instance serving a few hundred users requires approximately 2 GB of RAM, though this increases with larger connection pools (CONFIG.pool_size) and higher background thread counts. Each outbound connection to YouTube maintains socket buffers, while background jobs cache JSON metadata for popular videos in memory.

Why does my instance slow down during channel refreshes?

The RefreshChannelsJob performs CPU-intensive JSON serialization and write-heavy SQL operations against the videos and channels tables. If CONFIG.channel_threads is set too high, these jobs compete with Kemal’s HTTP request fibers for CPU time, causing latency spikes. Reduce the thread count or schedule refreshes during low-traffic periods.

Can I run multiple Invidious instances behind a load balancer?

Yes, horizontal scaling is the recommended approach for high-traffic deployments. Multiple Invidious containers can share a single PostgreSQL backend, effectively distributing the CPU load of JSON serialization and HTTP request handling. The primary bottleneck shifts to database write throughput, which requires PostgreSQL tuning rather than application changes.

How does the connection pool size affect YouTube rate limits?

Each connection in YT_POOL represents a persistent TCP socket to YouTube. While larger pools improve concurrent request throughput, they also increase the volume of traffic originating from your IP address. If CONFIG.pool_size is set too high, YouTube may impose rate limits or temporary blocks. A pool size of 10-20 connections typically balances performance with rate-limit safety.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →