Postgres perf, path migration, and shutdown race fixes

- Major query performance boost: replaced correlated subqueries with DistinctBy() in BaseItemRepository, added episode deduplication index, and increased default command timeout to 120s.
- Fixed LibraryMonitor shutdown race: added disposal checks and exception handling in FileRefresher to prevent ObjectDisposedException.
- Added PowerShell and SQL utilities for fixing Linux paths in config files and database; new scripts for WebDir and path migration.
- Auto-fix for WebDir in startup.json on load; new helper scripts and docs.
- Added automated weekly DB performance monitoring scripts and reporting.
- Expanded documentation and README for all fixes, scripts, and migration steps.
- Updated SQL scripts for index creation and diagnostics; improved output handling.
- Updated db-config defaults and added new diagnostic/report files.
This commit is contained in:
2026-03-05 08:56:42 -05:00
parent 0eb44818ff
commit 7eb2b445cb
44 changed files with 3504 additions and 23 deletions
@@ -598,18 +598,20 @@ public sealed class BaseItemRepository
var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
{
var tempQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.FirstOrDefault()).Select(e => e!.Id);
dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
// to prevent correlated subqueries. See docs/database-query-optimization.md
dbQuery = dbQuery.DistinctBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey });
}
else if (enableGroupByPresentationUniqueKey)
{
var tempQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id);
dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
// PERFORMANCE FIX: Use DistinctBy() to generate DISTINCT ON instead of correlated subquery
// This improves performance from 165s to <50ms. See docs/database-query-optimization.md
dbQuery = dbQuery.DistinctBy(e => e.PresentationUniqueKey);
}
else if (filter.GroupBySeriesPresentationUniqueKey)
{
var tempQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id);
dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
dbQuery = dbQuery.DistinctBy(e => e.SeriesPresentationUniqueKey);
}
else
{
@@ -1659,10 +1661,11 @@ public sealed class BaseItemRepository
ExcludeItemIds = filter.ExcludeItemIds
};
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
// to prevent correlated subqueries. Generates DISTINCT ON for PostgreSQL.
var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter)
.GroupBy(e => e.PresentationUniqueKey)
.Select(e => e.FirstOrDefault())
.Select(e => e!.Id);
.DistinctBy(e => e.PresentationUniqueKey)
.Select(e => e.Id);
var query = context.BaseItems
.Include(e => e.TrailerTypes)
@@ -1827,10 +1830,11 @@ public sealed class BaseItemRepository
ExcludeItemIds = filter.ExcludeItemIds
};
// PERFORMANCE FIX: Use DistinctBy() instead of GroupBy().Select().FirstOrDefault()
// to prevent correlated subqueries. Generates DISTINCT ON for PostgreSQL.
var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter)
.GroupBy(e => e.PresentationUniqueKey)
.Select(e => e.FirstOrDefault())
.Select(e => e!.Id);
.DistinctBy(e => e.PresentationUniqueKey)
.Select(e => e.Id);
var query = context.BaseItems
.Include(e => e.TrailerTypes)