Files
pgsql-jellyfin/docs/MULTI_INSTANCE_QUICKSTART.md
T
wjones 77e30685bb Complete multi-instance support: Phases 3–6 & deployment
- 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.
2026-03-05 16:10:26 -05:00

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!** 🚀