Fix database initialization detection - check for TABLES not just schemas
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"operationId": "02bc6448-a69d-4ff4-a0ec-0db07ec3a09a",
|
||||
"description": "Upgrade solution or project to new version of .NET",
|
||||
"startTime": "2026-03-05T22:18:32.3921759Z",
|
||||
"lastUpdateTime": "2026-03-08T14:46:31.3820237Z",
|
||||
"lastUpdateTime": "2026-03-08T15:04:52.4729284Z",
|
||||
"stage": "Execution",
|
||||
"properties": {},
|
||||
"folderPath": ""
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
yes-- Jellyfin index cleanup plan (conservative, phased)
|
||||
-- Generated: 2026-03-08
|
||||
-- Notes:
|
||||
-- 1) This script does NOT run automatically; execute sections manually.
|
||||
-- 2) Use DROP INDEX CONCURRENTLY to avoid long blocking.
|
||||
-- 3) Do not run inside an explicit transaction block.
|
||||
-- 4) Re-check stats after each phase before continuing.
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Baseline: capture current index usage before any change
|
||||
--------------------------------------------------------------------------- */
|
||||
SELECT
|
||||
now() AS captured_at,
|
||||
schemaname,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname IN ('library', 'authentication', 'users', 'activitylog', 'displaypreferences')
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Phase 1 (lowest risk): redundant MediaStreamInfos non-unique indexes
|
||||
Rationale: all 0 scans since last stats reset, strong overlap among keys.
|
||||
--------------------------------------------------------------------------- */
|
||||
-- Run one statement at a time and observe workload/latency between statements.
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library."IX_MediaStreamInfos_StreamIndex_StreamType_Language";
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library."IX_MediaStreamInfos_StreamIndex_StreamType";
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library."IX_MediaStreamInfos_StreamIndex";
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Verification checkpoint after Phase 1
|
||||
--------------------------------------------------------------------------- */
|
||||
SELECT
|
||||
now() AS check_time,
|
||||
schemaname,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND relname = 'MediaStreamInfos'
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Phase 2 (medium risk): overlapping BaseItemProviders helper indexes
|
||||
Rationale: all 0 scans since last stats reset; candidates may be redundant
|
||||
with PK and alternative access paths, but monitor query plans after each.
|
||||
--------------------------------------------------------------------------- */
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library.baseitemproviders_providerid_idx;
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library.baseitemproviders_providervalue_idx;
|
||||
|
||||
-- Optional in Phase 2b (only if workload remains healthy after Phase 2):
|
||||
-- DROP INDEX CONCURRENTLY IF EXISTS library."IX_BaseItemProviders_ProviderId_ProviderValue_ItemId";
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Final review query: confirm remaining low-scan indexes
|
||||
--------------------------------------------------------------------------- */
|
||||
SELECT
|
||||
schemaname,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
|
||||
pg_get_indexdef(indexrelid) AS index_definition
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname IN ('library', 'authentication', 'users', 'activitylog', 'displaypreferences')
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Explicitly excluded from removal in this plan:
|
||||
- Unique and PK-backed indexes (constraint critical)
|
||||
- IX_Users_Username
|
||||
- IX_DeviceOptions_DeviceId
|
||||
--------------------------------------------------------------------------- */
|
||||
@@ -0,0 +1,183 @@
|
||||
-- Jellyfin strict index-removal runbook
|
||||
-- Strategy: exactly ONE DROP per maintenance window.
|
||||
-- Generated: 2026-03-08
|
||||
--
|
||||
-- Important:
|
||||
-- 1) Execute only one window per maintenance period.
|
||||
-- 2) Keep each statement standalone (no BEGIN/COMMIT around CONCURRENTLY).
|
||||
-- 3) If regression appears, run that window's rollback CREATE INDEX CONCURRENTLY.
|
||||
|
||||
/* =====================================================================
|
||||
Shared baseline (run before each window)
|
||||
===================================================================== */
|
||||
SELECT
|
||||
now() AS captured_at,
|
||||
schemaname,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname IN ('library', 'authentication', 'users', 'activitylog', 'displaypreferences')
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
/* =====================================================================
|
||||
WINDOW 1 (lowest risk first)
|
||||
Target: library.IX_MediaStreamInfos_StreamIndex_StreamType_Language
|
||||
===================================================================== */
|
||||
-- Drop:
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library."IX_MediaStreamInfos_StreamIndex_StreamType_Language";
|
||||
|
||||
-- Verify impact:
|
||||
SELECT
|
||||
now() AS check_time,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND relname = 'MediaStreamInfos'
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
-- Rollback (recreate):
|
||||
CREATE INDEX CONCURRENTLY "IX_MediaStreamInfos_StreamIndex_StreamType_Language"
|
||||
ON library."MediaStreamInfos" USING btree ("StreamIndex", "StreamType", "Language");
|
||||
|
||||
/* =====================================================================
|
||||
WINDOW 2
|
||||
Target: library.IX_MediaStreamInfos_StreamIndex_StreamType
|
||||
===================================================================== */
|
||||
-- Drop:
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library."IX_MediaStreamInfos_StreamIndex_StreamType";
|
||||
|
||||
-- Verify impact:
|
||||
SELECT
|
||||
now() AS check_time,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND relname = 'MediaStreamInfos'
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
-- Rollback (recreate):
|
||||
CREATE INDEX CONCURRENTLY "IX_MediaStreamInfos_StreamIndex_StreamType"
|
||||
ON library."MediaStreamInfos" USING btree ("StreamIndex", "StreamType");
|
||||
|
||||
/* =====================================================================
|
||||
WINDOW 3
|
||||
Target: library.IX_MediaStreamInfos_StreamIndex
|
||||
===================================================================== */
|
||||
-- Drop:
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library."IX_MediaStreamInfos_StreamIndex";
|
||||
|
||||
-- Verify impact:
|
||||
SELECT
|
||||
now() AS check_time,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND relname = 'MediaStreamInfos'
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
-- Rollback (recreate):
|
||||
CREATE INDEX CONCURRENTLY "IX_MediaStreamInfos_StreamIndex"
|
||||
ON library."MediaStreamInfos" USING btree ("StreamIndex");
|
||||
|
||||
/* =====================================================================
|
||||
WINDOW 4 (medium risk)
|
||||
Target: library.baseitemproviders_providerid_idx
|
||||
===================================================================== */
|
||||
-- Drop:
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library.baseitemproviders_providerid_idx;
|
||||
|
||||
-- Verify impact:
|
||||
SELECT
|
||||
now() AS check_time,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND relname = 'BaseItemProviders'
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
-- Rollback (recreate):
|
||||
CREATE INDEX CONCURRENTLY baseitemproviders_providerid_idx
|
||||
ON library."BaseItemProviders" USING btree ("ProviderId", "ItemId");
|
||||
|
||||
/* =====================================================================
|
||||
WINDOW 5 (medium risk)
|
||||
Target: library.baseitemproviders_providervalue_idx
|
||||
===================================================================== */
|
||||
-- Drop:
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library.baseitemproviders_providervalue_idx;
|
||||
|
||||
-- Verify impact:
|
||||
SELECT
|
||||
now() AS check_time,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND relname = 'BaseItemProviders'
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
-- Rollback (recreate):
|
||||
CREATE INDEX CONCURRENTLY baseitemproviders_providervalue_idx
|
||||
ON library."BaseItemProviders" USING btree ("ProviderValue", "ProviderId");
|
||||
|
||||
/* =====================================================================
|
||||
WINDOW 6 (highest risk among current candidates; keep for last)
|
||||
Target: library.IX_BaseItemProviders_ProviderId_ProviderValue_ItemId
|
||||
===================================================================== */
|
||||
-- Drop:
|
||||
DROP INDEX CONCURRENTLY IF EXISTS library."IX_BaseItemProviders_ProviderId_ProviderValue_ItemId";
|
||||
|
||||
-- Verify impact:
|
||||
SELECT
|
||||
now() AS check_time,
|
||||
relname AS table_name,
|
||||
indexrelname AS index_name,
|
||||
idx_scan,
|
||||
idx_tup_read,
|
||||
idx_tup_fetch,
|
||||
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
||||
FROM pg_stat_user_indexes
|
||||
WHERE schemaname = 'library'
|
||||
AND relname = 'BaseItemProviders'
|
||||
ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC;
|
||||
|
||||
-- Rollback (recreate):
|
||||
CREATE INDEX CONCURRENTLY "IX_BaseItemProviders_ProviderId_ProviderValue_ItemId"
|
||||
ON library."BaseItemProviders" USING btree ("ProviderId", "ProviderValue", "ItemId");
|
||||
|
||||
/* =====================================================================
|
||||
Explicit exclusions (do not remove in this runbook)
|
||||
=====================================================================
|
||||
- Any PK_* index
|
||||
- Any UNIQUE index enforcing constraints
|
||||
- users.IX_Users_Username
|
||||
- authentication.IX_DeviceOptions_DeviceId
|
||||
===================================================================== */
|
||||
+10
-9
@@ -391,17 +391,18 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
||||
wasConnectionOpened = true;
|
||||
}
|
||||
|
||||
// Check if the library schema exists (indicates database is initialized)
|
||||
var schemaCheckQuery = "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = 'library'";
|
||||
await using (var schemaCheckCommand = connection.CreateCommand())
|
||||
// Check if database has tables (not just schemas)
|
||||
// Query for a critical table that must exist - if it doesn't, database needs initialization
|
||||
var tableCheckQuery = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'users' AND table_name = 'Users' AND table_type = 'BASE TABLE'";
|
||||
await using (var tableCheckCommand = connection.CreateCommand())
|
||||
{
|
||||
schemaCheckCommand.CommandText = schemaCheckQuery;
|
||||
var schemaCount = (long?)await schemaCheckCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
tableCheckCommand.CommandText = tableCheckQuery;
|
||||
var usersTableExists = (long?)await tableCheckCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (schemaCount == 0)
|
||||
if (usersTableExists == 0)
|
||||
{
|
||||
// Database is empty - initialize from SQL script
|
||||
logger.LogInformation("Database is empty (library schema not found). Initializing from SQL script...");
|
||||
// Database tables don't exist - initialize from SQL script
|
||||
logger.LogInformation("Database tables not found (users.Users table missing). Initializing from SQL script...");
|
||||
|
||||
var schemaScriptPath = Path.Combine(AppContext.BaseDirectory, "sql", "schema_init", "create_database_schema.sql");
|
||||
|
||||
@@ -440,7 +441,7 @@ public sealed class PostgresDatabaseProvider : IJellyfinDatabaseProvider
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("Database schema exists (library schema found)");
|
||||
logger.LogInformation("Database tables exist (users.Users table found)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user