mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-09 06:46:37 +08:00
Optimize home/library loading and SQLite query performance
- Replace home preview N+1 queries with a single window-function batch query - Cache library previews and series card lists (15s TTL, shared with media cache) - Tune SQLite: larger page cache, mmap, wal_autocheckpoint, ANALYZE on migrate - Defer history fetch on library page until last_played sort is selected - Yield to browser between paginated library loads to keep UI responsive Co-authored-by: truewhile <truewhile@users.noreply.github.com>
This commit is contained in:
@@ -34,6 +34,40 @@ func (s *MediaService) mediaListCacheKey(libraryID string, libraryIDs []string,
|
||||
return "media:list:" + hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func (s *MediaService) libraryPreviewCacheKey(libraries []model.Library, cardLimit int, filter repository.MediaQueryFilter) string {
|
||||
libIDs := make([]string, len(libraries))
|
||||
for i, lib := range libraries {
|
||||
libIDs[i] = lib.ID
|
||||
}
|
||||
sort.Strings(libIDs)
|
||||
allowed := append([]string(nil), filter.AllowedLibraryIDs...)
|
||||
hidden := append([]string(nil), filter.HiddenLibraryIDs...)
|
||||
sort.Strings(allowed)
|
||||
sort.Strings(hidden)
|
||||
sum := sha1.Sum([]byte(strings.Join([]string{
|
||||
"preview",
|
||||
strings.Join(libIDs, ","),
|
||||
fmt.Sprintf("%d:%t", cardLimit, filter.IncludeNSFW),
|
||||
strings.Join(allowed, ","),
|
||||
strings.Join(hidden, ","),
|
||||
}, "|")))
|
||||
return "media:preview:" + hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func (s *MediaService) seriesCardsCacheKey(libraryID string, visibility MediaVisibility) string {
|
||||
allowed := append([]string(nil), visibility.AllowedLibraryIDs...)
|
||||
hidden := append([]string(nil), visibility.HiddenLibraryIDs...)
|
||||
sort.Strings(allowed)
|
||||
sort.Strings(hidden)
|
||||
sum := sha1.Sum([]byte(strings.Join([]string{
|
||||
libraryID,
|
||||
fmt.Sprintf("%t", visibility.IncludeNSFW),
|
||||
strings.Join(allowed, ","),
|
||||
strings.Join(hidden, ","),
|
||||
}, "|")))
|
||||
return "media:series-cards:" + hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func (s *MediaService) mediaCacheTTLSeconds() int {
|
||||
if s == nil || s.cfg == nil || s.cfg.Cache.MediaTTLSeconds < 1 {
|
||||
return 15
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
"github.com/ShukeBta/MMTL/internal/repository"
|
||||
@@ -14,6 +15,10 @@ type LibraryPreviewItem struct {
|
||||
Cards []SeriesCard `json:"cards"`
|
||||
}
|
||||
|
||||
type libraryPreviewCacheValue struct {
|
||||
Items []LibraryPreviewItem `json:"items"`
|
||||
}
|
||||
|
||||
// ListLibraries returns every library configured on the server.
|
||||
func (s *MediaService) ListLibraries(ctx context.Context) ([]model.Library, error) {
|
||||
return s.repo.Library.List(ctx)
|
||||
@@ -29,6 +34,18 @@ func (s *MediaService) ListLibrariesWithPreview(ctx context.Context, libraries [
|
||||
return out, nil
|
||||
}
|
||||
|
||||
visibility = ExpandMediaVisibilityForMergedCloudLibraries(ctx, s.repo, visibility)
|
||||
filter := repository.MediaQueryFilter{
|
||||
IncludeNSFW: visibility.IncludeNSFW,
|
||||
AllowedLibraryIDs: visibility.AllowedLibraryIDs,
|
||||
HiddenLibraryIDs: visibility.HiddenLibraryIDs,
|
||||
}
|
||||
cacheKey := s.libraryPreviewCacheKey(libraries, cardLimit, filter)
|
||||
var cached libraryPreviewCacheValue
|
||||
if s.cache != nil && s.cache.GetJSON(ctx, cacheKey, &cached) {
|
||||
return cached.Items, nil
|
||||
}
|
||||
|
||||
libIDs := make([]string, 0, len(libraries))
|
||||
for i, lib := range libraries {
|
||||
out[i] = LibraryPreviewItem{
|
||||
@@ -39,13 +56,6 @@ func (s *MediaService) ListLibrariesWithPreview(ctx context.Context, libraries [
|
||||
libIDs = append(libIDs, lib.ID)
|
||||
}
|
||||
|
||||
visibility = ExpandMediaVisibilityForMergedCloudLibraries(ctx, s.repo, visibility)
|
||||
filter := repository.MediaQueryFilter{
|
||||
IncludeNSFW: visibility.IncludeNSFW,
|
||||
AllowedLibraryIDs: visibility.AllowedLibraryIDs,
|
||||
HiddenLibraryIDs: visibility.HiddenLibraryIDs,
|
||||
}
|
||||
|
||||
counts, err := s.repo.Media.CountByLibraries(ctx, libIDs, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -64,15 +74,32 @@ func (s *MediaService) ListLibrariesWithPreview(ctx context.Context, libraries [
|
||||
fetchCount = 200
|
||||
}
|
||||
|
||||
recentByLibrary, err := s.repo.Media.ListRecentByLibraries(ctx, libIDs, fetchCount, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allPreviewItems := make([]model.Media, 0, len(libIDs)*fetchCount)
|
||||
for i := range out {
|
||||
if out[i].Total == 0 {
|
||||
continue
|
||||
}
|
||||
items, _, err := s.repo.Media.ListByLibrariesFiltered(ctx, []string{out[i].ID}, 0, fetchCount, filter)
|
||||
if err != nil {
|
||||
items := recentByLibrary[out[i].ID]
|
||||
if len(items) == 0 {
|
||||
continue
|
||||
}
|
||||
allPreviewItems = append(allPreviewItems, items...)
|
||||
}
|
||||
s.attachLibraryMetadata(ctx, allPreviewItems)
|
||||
|
||||
for i := range out {
|
||||
if out[i].Total == 0 {
|
||||
continue
|
||||
}
|
||||
items := recentByLibrary[out[i].ID]
|
||||
if len(items) == 0 {
|
||||
continue
|
||||
}
|
||||
s.attachLibraryMetadata(ctx, items)
|
||||
cards := groupMediaSeriesCards(items)
|
||||
if len(cards) > cardLimit {
|
||||
cards = cards[:cardLimit]
|
||||
@@ -83,6 +110,10 @@ func (s *MediaService) ListLibrariesWithPreview(ctx context.Context, libraries [
|
||||
out[i].Cards = cards
|
||||
}
|
||||
|
||||
if s.cache != nil {
|
||||
s.cache.SetJSON(ctx, cacheKey, libraryPreviewCacheValue{Items: out}, time.Duration(s.mediaCacheTTLSeconds())*time.Second)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ import (
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
)
|
||||
|
||||
type seriesCardsCacheValue struct {
|
||||
Cards []SeriesCard `json:"cards"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type SeriesCard struct {
|
||||
Key string `json:"key"`
|
||||
Rep model.Media `json:"rep"`
|
||||
@@ -25,12 +30,25 @@ type seriesCardGroup struct {
|
||||
}
|
||||
|
||||
func (s *MediaService) ListLibrarySeriesCards(ctx context.Context, libraryID string, visibility MediaVisibility) ([]SeriesCard, int64, error) {
|
||||
visibility = ExpandMediaVisibilityForMergedCloudLibraries(ctx, s.repo, visibility)
|
||||
cacheKey := s.seriesCardsCacheKey(libraryID, visibility)
|
||||
var cached seriesCardsCacheValue
|
||||
if s.cache != nil && s.cache.GetJSON(ctx, cacheKey, &cached) {
|
||||
return cached.Cards, cached.Total, nil
|
||||
}
|
||||
rows, _, err := s.listAllMediaVisible(ctx, libraryID, visibility)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
cards := groupMediaSeriesCards(rows)
|
||||
return cards, int64(len(cards)), nil
|
||||
if cards == nil {
|
||||
cards = []SeriesCard{}
|
||||
}
|
||||
total := int64(len(cards))
|
||||
if s.cache != nil {
|
||||
s.cache.SetJSON(ctx, cacheKey, seriesCardsCacheValue{Cards: cards, Total: total}, time.Duration(s.mediaCacheTTLSeconds())*time.Second)
|
||||
}
|
||||
return cards, total, nil
|
||||
}
|
||||
|
||||
func (s *MediaService) ListRecentSeriesCards(ctx context.Context, limit int, visibility MediaVisibility) ([]SeriesCard, error) {
|
||||
|
||||
Reference in New Issue
Block a user