77e30685bb
- Implements Phases 3–6: session isolation, cache coordination, primary election, and file system monitor coordination for Jellyfin with PostgreSQL. - Adds new database entities (Instance, DistributedLock, FileSystemChange) and EF model configurations. - Includes SQL migration scripts and EF migration for all required tables, columns, and helper functions. - Updates Device entity and JellyfinDbContext for multi-instance tracking. - Integrates new DI services for instance registry, distributed locks, cache coordinator, and primary election. - Adds publishing profiles (Win/Linux/FrameworkDependent) and automation script for deployment. - Extensive documentation for architecture, setup, and publishing. - All changes are backward compatible and build successfully.
512 lines
12 KiB
Markdown
512 lines
12 KiB
Markdown
# Quick Start: Multi-Instance Jellyfin
|
|
|
|
## What Is This?
|
|
|
|
Run **multiple Jellyfin instances** that share the same PostgreSQL database, allowing you to:
|
|
|
|
- 🚀 **Scale horizontally** - Add more instances for more users
|
|
- ⚡ **Load balance** - Distribute streaming/transcoding across servers
|
|
- 🛠️ **Specialize** - Dedicate instances to specific tasks (scanning, serving, transcoding)
|
|
- 🔄 **High availability** - If one instance fails, others continue
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
✅ PostgreSQL 12+ database
|
|
✅ All instances on **same OS** with **identical library paths**
|
|
✅ Shared network storage accessible from all instances
|
|
✅ Jellyfin with multi-instance support (this branch)
|
|
|
|
---
|
|
|
|
## Quick Setup (2 Instances)
|
|
|
|
### Step 1: Set Up Shared Database
|
|
|
|
```bash
|
|
# Create PostgreSQL database
|
|
createdb -U postgres jellyfin
|
|
|
|
# Create user
|
|
psql -U postgres -c "CREATE USER jellyfin WITH PASSWORD 'your_secure_password';"
|
|
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE jellyfin TO jellyfin;"
|
|
```
|
|
|
|
### Step 2: Configure Instance 1 (Primary)
|
|
|
|
Create `instance1/config/startup.json`:
|
|
|
|
```json
|
|
{
|
|
"InstanceId": "11111111-1111-1111-1111-111111111111",
|
|
"InstanceName": "Jellyfin-Primary",
|
|
"DatabaseProvider": "Postgres",
|
|
"ConnectionStrings": {
|
|
"DefaultConnection": "Host=localhost;Database=jellyfin;Username=jellyfin;Password=your_secure_password"
|
|
},
|
|
"DataPath": "C:/Jellyfin/instance1/data",
|
|
"WebDir": "wwwroot",
|
|
"CachePath": "C:/Jellyfin/instance1/cache",
|
|
"LogPath": "C:/Jellyfin/instance1/logs",
|
|
"EnableMultiInstance": true,
|
|
"InstanceConfiguration": {
|
|
"HttpPort": 8096,
|
|
"HttpsPort": 8920,
|
|
"CanBecomePrimary": true,
|
|
"Capabilities": {
|
|
"CanScan": true,
|
|
"CanTranscode": true,
|
|
"CanServeApi": true,
|
|
"CanStream": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 3: Configure Instance 2 (Secondary)
|
|
|
|
Create `instance2/config/startup.json`:
|
|
|
|
```json
|
|
{
|
|
"InstanceId": "22222222-2222-2222-2222-222222222222",
|
|
"InstanceName": "Jellyfin-Secondary",
|
|
"DatabaseProvider": "Postgres",
|
|
"ConnectionStrings": {
|
|
"DefaultConnection": "Host=localhost;Database=jellyfin;Username=jellyfin;Password=your_secure_password"
|
|
},
|
|
"DataPath": "C:/Jellyfin/instance2/data",
|
|
"WebDir": "wwwroot",
|
|
"CachePath": "C:/Jellyfin/instance2/cache",
|
|
"LogPath": "C:/Jellyfin/instance2/logs",
|
|
"EnableMultiInstance": true,
|
|
"InstanceConfiguration": {
|
|
"HttpPort": 8097,
|
|
"HttpsPort": 8921,
|
|
"CanBecomePrimary": false,
|
|
"Capabilities": {
|
|
"CanScan": false,
|
|
"CanTranscode": true,
|
|
"CanServeApi": true,
|
|
"CanStream": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 4: Run Database Migration
|
|
|
|
```bash
|
|
# Run ONCE from any instance directory
|
|
dotnet ef database update -p src/Jellyfin.Database/Jellyfin.Database.Providers.Postgres -s Jellyfin.Server
|
|
```
|
|
|
|
Or manually:
|
|
|
|
```bash
|
|
psql -U jellyfin -d jellyfin -f docs/multi_instance_migration.sql
|
|
```
|
|
|
|
### Step 5: Start Instances
|
|
|
|
```powershell
|
|
# Terminal 1 - Instance 1
|
|
cd instance1
|
|
dotnet lib/Release/net11.0/jellyfin.dll
|
|
|
|
# Terminal 2 - Instance 2
|
|
cd instance2
|
|
dotnet lib/Release/net11.0/jellyfin.dll
|
|
```
|
|
|
|
### Step 6: Verify
|
|
|
|
Check the database:
|
|
|
|
```sql
|
|
SELECT * FROM library."Instances";
|
|
```
|
|
|
|
You should see both instances registered!
|
|
|
|
---
|
|
|
|
## Load Balancer Configuration
|
|
|
|
### Nginx Example
|
|
|
|
```nginx
|
|
upstream jellyfin_backend {
|
|
least_conn;
|
|
server localhost:8096 max_fails=3 fail_timeout=30s;
|
|
server localhost:8097 max_fails=3 fail_timeout=30s;
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name jellyfin.example.com;
|
|
|
|
location / {
|
|
proxy_pass http://jellyfin_backend;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# WebSocket support
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
}
|
|
```
|
|
|
|
### HAProxy Example
|
|
|
|
```haproxy
|
|
frontend jellyfin_front
|
|
bind *:80
|
|
default_backend jellyfin_back
|
|
|
|
backend jellyfin_back
|
|
balance leastconn
|
|
option httpchk GET /health
|
|
server instance1 localhost:8096 check
|
|
server instance2 localhost:8097 check
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture Patterns
|
|
|
|
### Pattern 1: Load Balanced API
|
|
|
|
```
|
|
┌─────────────┐
|
|
│Load Balancer│
|
|
└──────┬──────┘
|
|
│
|
|
┌──────────┴──────────┐
|
|
│ │
|
|
┌────▼────┐ ┌────▼────┐
|
|
│Instance1│ │Instance2│
|
|
│(Primary)│ │(Second) │
|
|
│Port 8096│ │Port 8097│
|
|
└────┬────┘ └────┬────┘
|
|
│ │
|
|
└──────────┬──────────┘
|
|
│
|
|
┌─────▼─────┐
|
|
│PostgreSQL │
|
|
│ Database │
|
|
└───────────┘
|
|
```
|
|
|
|
**Use Case:** Scale web API and streaming
|
|
**Capabilities:**
|
|
- Instance1: `CanScan=true, CanTranscode=true, CanServeApi=true, CanStream=true`
|
|
- Instance2: `CanScan=false, CanTranscode=true, CanServeApi=true, CanStream=true`
|
|
|
|
### Pattern 2: Dedicated Scanner
|
|
|
|
```
|
|
┌────────────┐
|
|
│ Scanner │ ← Runs library scans (Primary)
|
|
│(Background)│ No public API
|
|
│ Instance │
|
|
└─────┬──────┘
|
|
│
|
|
┌─────▼─────┐
|
|
│PostgreSQL │
|
|
│ Database │
|
|
└─────┬─────┘
|
|
│
|
|
┌────────┴────────┐
|
|
│ │
|
|
┌───▼───┐ ┌────▼────┐
|
|
│Serve1 │ │ Serve2 │ ← Serve only
|
|
│API/ │ │ API/ │ No scanning
|
|
│Stream │ │ Stream │
|
|
└───────┘ └─────────┘
|
|
```
|
|
|
|
**Use Case:** Separate scanning from serving
|
|
**Capabilities:**
|
|
- Scanner: `CanScan=true, CanTranscode=false, CanServeApi=false, CanStream=false`
|
|
- Serve1/2: `CanScan=false, CanTranscode=true, CanServeApi=true, CanStream=true`
|
|
|
|
### Pattern 3: Geo-Distributed (Same Paths)
|
|
|
|
```
|
|
┌─────────────┐
|
|
│ PostgreSQL │ ← Accessible from all locations
|
|
│ (Central) │
|
|
└──────┬──────┘
|
|
│
|
|
┌─────────┼─────────┐
|
|
│ │ │
|
|
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
|
|
│Office1│ │Office2│ │Office3│
|
|
│8096 │ │8096 │ │8096 │
|
|
└───────┘ └───────┘ └───────┘
|
|
```
|
|
|
|
**Use Case:** Multiple offices with local instances
|
|
**Requirement:** All must have identical library paths (e.g., `Z:\Media`)
|
|
|
|
---
|
|
|
|
## Configuration Options
|
|
|
|
### Instance-Specific (startup.json)
|
|
|
|
```json
|
|
{
|
|
"InstanceId": "auto-generated-or-specified",
|
|
"InstanceName": "Human-readable name",
|
|
"EnableMultiInstance": true,
|
|
"InstanceConfiguration": {
|
|
"HttpPort": 8096,
|
|
"HttpsPort": 8920,
|
|
"CanBecomePrimary": true,
|
|
"Capabilities": {
|
|
"CanScan": true, // Library scanning
|
|
"CanTranscode": true, // Transcoding
|
|
"CanServeApi": true, // HTTP API
|
|
"CanStream": true // Media streaming
|
|
},
|
|
"HeartbeatInterval": 30, // Seconds between heartbeats
|
|
"LockTimeout": 300 // Max seconds to hold a lock
|
|
}
|
|
}
|
|
```
|
|
|
|
### Shared (Database)
|
|
|
|
- Library paths
|
|
- User accounts
|
|
- Metadata settings
|
|
- Playback settings
|
|
|
|
---
|
|
|
|
## Monitoring & Maintenance
|
|
|
|
### Check Instance Health
|
|
|
|
```sql
|
|
SELECT
|
|
"InstanceId",
|
|
"Hostname",
|
|
"HttpPort",
|
|
"Status",
|
|
"IsPrimary",
|
|
"LastHeartbeat",
|
|
EXTRACT(EPOCH FROM (NOW() - "LastHeartbeat")) AS seconds_since_heartbeat
|
|
FROM library."Instances"
|
|
WHERE "Status" = 'Active'
|
|
ORDER BY "LastHeartbeat" DESC;
|
|
```
|
|
|
|
### Cleanup Stale Instances
|
|
|
|
```sql
|
|
SELECT library.cleanup_stale_instances();
|
|
```
|
|
|
|
### View Primary Instance
|
|
|
|
```sql
|
|
SELECT library.get_primary_instance();
|
|
```
|
|
|
|
### Force Primary Election
|
|
|
|
```sql
|
|
SELECT library.elect_primary_instance();
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Multiple instances become primary
|
|
|
|
**Cause:** Race condition or manual database changes
|
|
**Solution:**
|
|
|
|
```sql
|
|
-- Reset all to secondary
|
|
UPDATE library."Instances" SET "IsPrimary" = FALSE;
|
|
|
|
-- Elect new primary
|
|
SELECT library.elect_primary_instance();
|
|
```
|
|
|
|
### Issue: Instance not registering
|
|
|
|
**Symptoms:** Instance starts but doesn't appear in database
|
|
|
|
**Check:**
|
|
1. Database connection string correct?
|
|
2. Migration applied? (`SELECT * FROM library."Instances"` should work)
|
|
3. `EnableMultiInstance=true` in startup.json?
|
|
4. Logs show registration errors?
|
|
|
|
### Issue: Stale instances not cleaning up
|
|
|
|
**Symptoms:** Failed instances still show as Active
|
|
|
|
**Solution:**
|
|
```sql
|
|
-- Manual cleanup
|
|
UPDATE library."Instances"
|
|
SET "Status" = 'Failed'
|
|
WHERE "LastHeartbeat" < NOW() - INTERVAL '5 minutes';
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Tips
|
|
|
|
### 1. Connection Pooling
|
|
|
|
```json
|
|
{
|
|
"ConnectionStrings": {
|
|
"DefaultConnection": "Host=localhost;Database=jellyfin;Username=jellyfin;Password=***;Pooling=true;MinPoolSize=5;MaxPoolSize=50"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Dedicated Transcoding Instances
|
|
|
|
- Set `CanServeApi=false, CanStream=false` on transcode-only instances
|
|
- Allocate more CPU/RAM to these instances
|
|
- Use separate cache directories per instance
|
|
|
|
### 3. Primary Instance
|
|
|
|
- Give primary instance more CPU for scanning
|
|
- Reduce `CanTranscode` capability on primary
|
|
- Primary should be on fastest/most reliable server
|
|
|
|
### 4. Database Optimization
|
|
|
|
```sql
|
|
-- Add more connections
|
|
ALTER SYSTEM SET max_connections = 200;
|
|
|
|
-- Increase work memory
|
|
ALTER SYSTEM SET work_mem = '256MB';
|
|
|
|
-- Reload config
|
|
SELECT pg_reload_conf();
|
|
```
|
|
|
|
---
|
|
|
|
## Security Best Practices
|
|
|
|
### 1. Network Security
|
|
|
|
- Use PostgreSQL SSL connections
|
|
- Firewall database access to known instance IPs
|
|
- Use strong database passwords
|
|
|
|
### 2. Instance Isolation
|
|
|
|
- Run each instance under separate OS user
|
|
- Use separate cache/log directories
|
|
- Limit transcoding temp paths
|
|
|
|
### 3. Access Control
|
|
|
|
```sql
|
|
-- Create separate database user per instance
|
|
CREATE USER instance1_user WITH PASSWORD 'secure_password_1';
|
|
CREATE USER instance2_user WITH PASSWORD 'secure_password_2';
|
|
|
|
-- Grant permissions
|
|
GRANT ALL ON SCHEMA library TO instance1_user;
|
|
GRANT ALL ON SCHEMA library TO instance2_user;
|
|
```
|
|
|
|
---
|
|
|
|
## Migration from Single Instance
|
|
|
|
### Step 1: Backup
|
|
|
|
```bash
|
|
# Backup database
|
|
pg_dump jellyfin > jellyfin_backup_$(date +%Y%m%d).sql
|
|
|
|
# Backup config
|
|
cp -r config config_backup
|
|
```
|
|
|
|
### Step 2: Update Config
|
|
|
|
Add to `startup.json`:
|
|
|
|
```json
|
|
{
|
|
"EnableMultiInstance": true,
|
|
"InstanceConfiguration": {
|
|
"CanBecomePrimary": true,
|
|
"Capabilities": {
|
|
"CanScan": true,
|
|
"CanTranscode": true,
|
|
"CanServeApi": true,
|
|
"CanStream": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 3: Run Migration
|
|
|
|
```bash
|
|
dotnet ef database update
|
|
```
|
|
|
|
### Step 4: Restart
|
|
|
|
Your single instance will now register itself in the Instances table!
|
|
|
|
---
|
|
|
|
## Current Limitations (Phase 1)
|
|
|
|
⚠️ **Phase 1 Only - Foundation Complete, Services In Progress**
|
|
|
|
### ✅ What Works
|
|
- Database schema for instance registration
|
|
- Entity model complete
|
|
- Build succeeds
|
|
|
|
### ❌ Not Yet Implemented
|
|
- Automatic instance registration on startup
|
|
- Heartbeat mechanism
|
|
- Primary election logic
|
|
- Distributed locking
|
|
- Cache coordination
|
|
- Session isolation
|
|
|
|
**Status:** Schema ready, awaiting service implementation (Phases 2-6)
|
|
|
|
---
|
|
|
|
## Get Help
|
|
|
|
- **Documentation:** See `docs/MULTI_INSTANCE_SUPPORT_PLAN.md` for architecture
|
|
- **Implementation Status:** See `docs/MULTI_INSTANCE_SUPPORT_SUMMARY.md`
|
|
- **Issues:** Report on GitHub with `multi-instance` label
|
|
- **Testing:** Use branch `multi-instance-testing`
|
|
|
|
---
|
|
|
|
**Ready to scale your Jellyfin deployment? Start with 2 instances and expand from there!** 🚀
|