Add thumbnail filename cache and reuse db connections

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2020-05-28 16:26:22 +02:00
parent 0527dd655f
commit 52473a1ca9
9 changed files with 63 additions and 19 deletions

View File

@@ -431,9 +431,9 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
return return
} }
start := time.Now()
typeName := c.Param("type") typeName := c.Param("type")
uid := c.Param("uid") uid := c.Param("uid")
start := time.Now()
thumbType, ok := thumb.Types[typeName] thumbType, ok := thumb.Types[typeName]
@@ -468,7 +468,7 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
log.Warnf("album: %s is missing", txt.Quote(f.FileName)) log.Warnf("album: %s is missing", txt.Quote(f.FileName))
report("album", f.Update("FileMissing", true)) logError("album", f.Update("FileMissing", true))
return return
} }

View File

@@ -14,7 +14,7 @@ import (
var log = event.Log var log = event.Log
func report(prefix string, err error) { func logError(prefix string, err error) {
if err != nil { if err != nil {
log.Errorf("%s: %s", prefix, err.Error()) log.Errorf("%s: %s", prefix, err.Error())
} }

View File

@@ -44,7 +44,7 @@ func GetDownload(router *gin.RouterGroup, conf *config.Config) {
c.Data(404, "image/svg+xml", brokenIconSvg) c.Data(404, "image/svg+xml", brokenIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
report("download", f.Update("FileMissing", true)) logError("download", f.Update("FileMissing", true))
return return
} }

View File

@@ -167,9 +167,9 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
return return
} }
start := time.Now()
typeName := c.Param("type") typeName := c.Param("type")
labelUID := c.Param("uid") labelUID := c.Param("uid")
start := time.Now()
thumbType, ok := thumb.Types[typeName] thumbType, ok := thumb.Types[typeName]
@@ -203,7 +203,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg) c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
report("label", f.Update("FileMissing", true)) logError("label", f.Update("FileMissing", true))
return return
} }

View File

@@ -133,7 +133,7 @@ func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg) c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
report("photo", f.Update("FileMissing", true)) logError("photo", f.Update("FileMissing", true))
return return
} }

View File

@@ -1,18 +1,26 @@
package api package api
import ( import (
"fmt"
"net/http" "net/http"
"path" "path"
"path/filepath" "path/filepath"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
type ThumbCache struct {
FileName string
ShareName string
}
// GET /api/v1/t/:hash/:token/:type // GET /api/v1/t/:hash/:token/:type
// //
// Parameters: // Parameters:
@@ -25,17 +33,41 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
return return
} }
start := time.Now()
fileHash := c.Param("hash") fileHash := c.Param("hash")
typeName := c.Param("type") typeName := c.Param("type")
thumbType, ok := thumb.Types[typeName] thumbType, ok := thumb.Types[typeName]
if !ok { if !ok {
log.Errorf("photo: invalid thumb type %s", txt.Quote(typeName)) log.Errorf("thumbnail: invalid type %s", txt.Quote(typeName))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg) c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return return
} }
gc := service.Cache()
cacheKey := fmt.Sprintf("thumbnail:%s:%s", fileHash, typeName)
if cacheData, ok := gc.Get(cacheKey); ok {
log.Debugf("cache hit for %s [%s]", cacheKey, time.Since(start))
cached := cacheData.(ThumbCache)
if !fs.FileExists(cached.FileName) {
log.Errorf("thumbnail: %s not found", fileHash)
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return
}
if c.Query("download") != "" {
c.FileAttachment(cached.FileName, cached.ShareName)
} else {
c.File(cached.FileName)
}
return
}
f, err := query.FileByHash(fileHash) f, err := query.FileByHash(fileHash)
if err != nil { if err != nil {
@@ -62,16 +94,16 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
fileName := path.Join(conf.OriginalsPath(), f.FileName) fileName := path.Join(conf.OriginalsPath(), f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("photo: file %s is missing", txt.Quote(f.FileName)) log.Errorf("thumbnail: file %s is missing", txt.Quote(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg) c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
report("photo", f.Update("FileMissing", true)) logError("thumbnail", f.Update("FileMissing", true))
if f.AllFilesMissing() { if f.AllFilesMissing() {
log.Infof("photo: deleting photo, all files missing for %s", txt.Quote(f.FileName)) log.Infof("thumbnail: deleting photo, all files missing for %s", txt.Quote(f.FileName))
report("photo", f.RelatedPhoto().Delete(false)) logError("thumbnail", f.RelatedPhoto().Delete(false))
} }
return return
@@ -79,7 +111,7 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157 // Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.ExceedsLimit() && c.Query("download") == "" { if thumbType.ExceedsLimit() && c.Query("download") == "" {
log.Debugf("photo: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height) log.Debugf("thumbnail: using original, size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName) c.File(fileName)
@@ -95,15 +127,19 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
} }
if err != nil { if err != nil {
log.Errorf("photo: %s", err) log.Errorf("thumbnail: %s", err)
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg) c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return return
} else if thumbnail == "" { } else if thumbnail == "" {
log.Errorf("photo: thumbnail name for %s is empty - bug?", filepath.Base(fileName)) log.Errorf("thumbnail: thumbnail name for %s is empty - bug?", filepath.Base(fileName))
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg) c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return return
} }
// Cache thumbnail filename.
gc.Set(cacheKey, ThumbCache{thumbnail, f.ShareFileName()}, time.Hour*24)
log.Debugf("cached %s [%s]", cacheKey, time.Since(start))
if c.Query("download") != "" { if c.Query("download") != "" {
c.FileAttachment(thumbnail, f.ShareFileName()) c.FileAttachment(thumbnail, f.ShareFileName())
} else { } else {

View File

@@ -66,7 +66,7 @@ func GetVideo(router *gin.RouterGroup, conf *config.Config) {
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg) c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
report("video", f.Update("FileMissing", true)) logError("video", f.Update("FileMissing", true))
return return
} }

View File

@@ -94,7 +94,7 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
log.Infof("zip: added %s as %s", txt.Quote(f.FileName), txt.Quote(fileAlias)) log.Infof("zip: added %s as %s", txt.Quote(f.FileName), txt.Quote(fileAlias))
} else { } else {
log.Warnf("zip: file %s is missing", txt.Quote(f.FileName)) log.Warnf("zip: file %s is missing", txt.Quote(f.FileName))
report("zip", f.Update("FileMissing", true)) logError("zip", f.Update("FileMissing", true))
} }
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"io/ioutil" "io/ioutil"
"runtime"
"strings" "strings"
"time" "time"
@@ -141,7 +142,14 @@ func (c *Config) connectToDatabase(ctx context.Context) error {
db.LogMode(false) db.LogMode(false)
db.SetLogger(log) db.SetLogger(log)
db.DB().SetMaxIdleConns(0)
if runtime.NumCPU() > 4 {
db.DB().SetMaxIdleConns(runtime.NumCPU())
} else {
db.DB().SetMaxIdleConns(4)
}
db.DB().SetConnMaxLifetime(time.Minute)
db.DB().SetMaxOpenConns(c.DatabaseConns()) db.DB().SetMaxOpenConns(c.DatabaseConns())
c.db = db c.db = db