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_POOLandGGPHT_POOLconnection pools (defined insrc/invidious.cr, lines 89-97) maintain persistent TCP sockets tohttps://www.youtube.comandhttps://yt3.ggpht.comfor thumbnail fetching. These pools are instantiated insrc/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:
RefreshChannelsJobRefreshFeedsJobStatisticsRefreshJob
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_typecalls (lines 34-36 insrc/invidious.cr) register short-lived objects forPreferencesandInvidious::Useron every request. - In-Memory Caches: Popular video lists stored in the class-level
POPULAR_VIDEOSchannel consume a few megabytes of RAM and refresh periodically viaPullPopularVideosJob.
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:
- Match Pool Size to Network Capacity: Set
CONFIG.pool_sizebetween 10-20 to balance outbound throughput against socket consumption and rate-limit risk. - Limit Background Thread Count: Keep
CONFIG.channel_threadsandCONFIG.feed_threadslow (2-4 threads each) to prevent CPU starvation of the Kemal HTTP workers. - PostgreSQL Connection Sizing: Ensure
max_connectionsexceeds the sum of your pool size plus background job workers to prevent connection exhaustion. - Enable Static Asset Caching: Verify production mode is active to utilize the
StaticAssetsHandlerand 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_POOLandGGPHT_POOLconnection pools insrc/invidious/yt_backend/connection_pool.crare the primary scalability levers for outbound YouTube traffic. - Background jobs (
RefreshChannelsJob,RefreshFeedsJob) drive CPU and database write load; limit viachannel_threadsandfeed_threadsto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →