From 2b848e804df320b1c7a050c86652013986a42b85 Mon Sep 17 00:00:00 2001 From: Joscha Henningsen <44805696+joschahenningsen@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:27:46 +0100 Subject: [PATCH] (feat): add cache for movies (#466) * add cache for movies * initialize cache for server and tests * extract cacheKey to variable * lint --- server/backend/movie.go | 35 +++++++++++++++++++++++++---------- server/backend/movie_test.go | 15 ++++++++++++--- server/backend/rpcserver.go | 2 ++ 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/server/backend/movie.go b/server/backend/movie.go index b07d8104..ca92af1b 100644 --- a/server/backend/movie.go +++ b/server/backend/movie.go @@ -2,6 +2,8 @@ package backend import ( "context" + "fmt" + "time" pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" "github.com/TUM-Dev/Campus-Backend/server/model" @@ -12,16 +14,9 @@ import ( ) func (s *CampusServer) ListMovies(ctx context.Context, req *pb.ListMoviesRequest) (*pb.ListMoviesReply, error) { - var movies []model.Movie - tx := s.db.WithContext(ctx). - Joins("File"). - Order("date ASC") - if req.OldestDateAt.GetSeconds() != 0 || req.OldestDateAt.GetNanos() != 0 { - tx = tx.Where("date > ?", req.OldestDateAt.AsTime()) - } - if err := tx.Find(&movies, "kino > ?", req.LastId).Error; err != nil { - log.WithError(err).Error("Error while fetching movies from database") - return nil, status.Error(codes.Internal, "Error while fetching movies from database") + movies, err := s.getMovies(ctx, req.LastId, req.OldestDateAt.AsTime()) + if err != nil { + return nil, err } var movieResponse []*pb.Movie for _, movie := range movies { @@ -47,3 +42,23 @@ func (s *CampusServer) ListMovies(ctx context.Context, req *pb.ListMoviesRequest Movies: movieResponse, }, nil } + +func (s *CampusServer) getMovies(ctx context.Context, lastID int32, oldestDateAt time.Time) ([]model.Movie, error) { + cacheKey := fmt.Sprintf("%d-%d", lastID, oldestDateAt.Second()) + if movies, ok := s.moviesCache.Get(cacheKey); ok { + return movies, nil + } + var movies []model.Movie + tx := s.db.WithContext(ctx). + Joins("File"). + Order("date ASC") + if oldestDateAt.Second() != 0 || oldestDateAt.Nanosecond() != 0 { + tx = tx.Where("date > ?", oldestDateAt) + } + if err := tx.Find(&movies, "kino > ?", lastID).Error; err != nil { + log.WithError(err).Error("Error while fetching movies from database") + return nil, status.Error(codes.Internal, "Error while fetching movies from database") + } + s.moviesCache.Add(cacheKey, movies) + return movies, nil +} diff --git a/server/backend/movie_test.go b/server/backend/movie_test.go index 68bd96c9..6bcc6838 100644 --- a/server/backend/movie_test.go +++ b/server/backend/movie_test.go @@ -9,6 +9,8 @@ import ( "github.com/DATA-DOG/go-sqlmock" pb "github.com/TUM-Dev/Campus-Backend/server/api/tumdev" + "github.com/TUM-Dev/Campus-Backend/server/model" + "github.com/hashicorp/golang-lru/v2/expirable" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/protobuf/types/known/timestamppb" @@ -81,7 +83,7 @@ var ( const ListMoviesQuery = "SELECT `movies`.`kino`,`movies`.`date`,`movies`.`created`,`movies`.`title`,`movies`.`year`,`movies`.`runtime`,`movies`.`genre`,`movies`.`director`,`movies`.`actors`,`movies`.`rating`,`movies`.`description`,`movies`.`trailer`,`movies`.`cover`,`movies`.`link`,`movies`.`location`,`File`.`file` AS `File__file`,`File`.`name` AS `File__name`,`File`.`path` AS `File__path`,`File`.`downloads` AS `File__downloads`,`File`.`url` AS `File__url`,`File`.`downloaded` AS `File__downloaded` FROM `movies` LEFT JOIN `files` `File` ON `movies`.`cover` = `File`.`file` WHERE kino > ? ORDER BY date ASC" func (s *MovieSuite) Test_ListMoviesAll() { - server := CampusServer{db: s.DB} + server := s.getCampusTestServer() s.mock.ExpectQuery(regexp.QuoteMeta(ListMoviesQuery)). WithArgs(-1). WillReturnRows(sqlmock.NewRows([]string{"kino", "date", "created", "title", "year", "runtime", "genre", "director", "actors", "rating", "description", "trailer", "cover", "link", "location", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"}). @@ -93,7 +95,7 @@ func (s *MovieSuite) Test_ListMoviesAll() { } func (s *MovieSuite) Test_ListMoviesOne() { - server := CampusServer{db: s.DB} + server := s.getCampusTestServer() s.mock.ExpectQuery(regexp.QuoteMeta(ListMoviesQuery)). WithArgs(1). WillReturnRows(sqlmock.NewRows([]string{"kino", "date", "created", "title", "year", "runtime", "genre", "director", "actors", "rating", "description", "trailer", "cover", "link", "location", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"}). @@ -104,7 +106,7 @@ func (s *MovieSuite) Test_ListMoviesOne() { } func (s *MovieSuite) Test_ListMoviesNone() { - server := CampusServer{db: s.DB} + server := s.getCampusTestServer() s.mock.ExpectQuery(regexp.QuoteMeta(ListMoviesQuery)). WithArgs(42). WillReturnRows(sqlmock.NewRows([]string{"kino", "date", "created", "title", "year", "runtime", "genre", "director", "actors", "rating", "description", "trailer", "cover", "link", "location", "File__file", "File__name", "File__path", "File__downloads", "File__url", "File__downloaded"})) @@ -122,3 +124,10 @@ func (s *MovieSuite) AfterTest(_, _ string) { func TestMovieSuite(t *testing.T) { suite.Run(t, new(MovieSuite)) } + +func (s *MovieSuite) getCampusTestServer() *CampusServer { + return &CampusServer{ + db: s.DB, + moviesCache: expirable.NewLRU[string, []model.Movie](1024, nil, time.Minute*30), + } +} diff --git a/server/backend/rpcserver.go b/server/backend/rpcserver.go index 6e793865..22844bc4 100644 --- a/server/backend/rpcserver.go +++ b/server/backend/rpcserver.go @@ -18,6 +18,7 @@ type CampusServer struct { deviceBuf *deviceBuffer // deviceBuf stores all devices from recent request and flushes them to db newsSourceCache *expirable.LRU[string, []model.NewsSource] newsCache *expirable.LRU[string, []model.News] + moviesCache *expirable.LRU[string, []model.Movie] } // Verify that CampusServer implements the pb.CampusServer interface @@ -31,5 +32,6 @@ func New(db *gorm.DB) *CampusServer { feedbackEmailLastReuestAt: &sync.Map{}, newsSourceCache: expirable.NewLRU[string, []model.NewsSource](1, nil, time.Hour*6), newsCache: expirable.NewLRU[string, []model.News](1024, nil, time.Minute*30), + moviesCache: expirable.NewLRU[string, []model.Movie](1024, nil, time.Minute*30), } }